model_assignments / chat_models /
agent_models. The lock is held by the DB
(FK RESTRICT), not just a code check — the test bypasses
the service layer and hits the database directly.
Platform
provider, built-in models, tool presets; not a hardcoded
fixture
@pytest.mark.integration — the whole layer needs a DB;
priority (p0 / p1) is orthogonal to
type
model_assignments → DELETE of ai_models rejected (FK RESTRICT → IntegrityError)chat_models → deletion / disabling rejected by RESTRICTagent_models → rejected by the same RESTRICTis_default=true in chat_models rejected by the partial uniqueis_default without setting a new one → forbidden (the "exactly one" invariant is enforced by the application)agent_models — the list shape is sharedai_providers → its ai_models go with it via ON DELETE CASCADECHECK (kind='cloud' OR base_url IS NOT NULL) — a local without an endpoint is rejected by the DBUNIQUE(provider_id, model_id) — the same model_id twice under a provider is rejected••••xxxx, not api_key_enc and not the plaintext keymodel_assignments.function is CHECKed against the vocabulary {harvester_embedding · query_rag}; INSERT with function='chat' / 'agent_engine' → rejected by the DB (assignment is possible only to system functions)model_usage.function='chat' goes through: usage is recorded across all four functions, including chat / agent_engine; two different vocabularies on one column → Function vocabularyUNIQUE(model_id, function, bucket_date) → one row, counters (request_count · input_tokens · output_tokens) growfunction / bucket_date → a separate aggregate rowmodel_id is nulled via ON DELETE SET NULL, the usage row survives — cost outlives itcost = input·price_input + output·price_output; any one price NULL → cost NULLUNIQUE(name) — a duplicate tool key is rejectedcredential_enc never leaves outward — only the is_set flag; config (JSONB) is non-secret and returned as issource ∈ (preset · custom · mcp · openapi) — anything else is rejected by the DBaccess ∈ (read_only · write) — anything else is rejectedis_system provider Platform — non-deletableis_system provider Platform bypassing the service layer — a direct DELETE against the database is rejected by the DB lock, not just a code checkmeta: bge-m3 {embedding_dim:1024, max_input_tokens:8192}, Qwen3-Embedding-0.6B {1024, 32768}web_search · fetch_url), both chat_enabled=false and agents_allowed=false••••xxxx / the is_set flag, not the secret200, the old one cleared atomically409, not 500 (the lock on a model in use)base_url, duplicate model_id, a bad tool source/access → 422, not 500model_type gates it: an embedding model in chat_models/agent_models or on query_rag → 422; a chat model on an embedding function → 422 — assignment across the wrong type is rejected, not 500 → Type governs assignmentGET/PATCH/DELETE of a provider / model / tool by a nonexistent {id} → 404, not 500Member on POST/PATCH/DELETE of the registry (providers · models · tools · assignments) → 403; anonymous → 401; parametrized over all write routes, not just discovery/v1/models) or local (Ollama /api/tags) provider; discovery is config, not inference500200; Member → 403; anonymous → 401 (parametrized over the registry routes)CHECK (id = 1), override / reset /
token cap — covered from the Admin side
(test_prompt_settings.py);
not duplicated here
The whole layer is integration with a DB. Priority (P0–P1) is
orthogonal to the directories and is set by markers
(pytest -m p0), not by separate folders.
tests/ai_foundation/module directory
conftest.pytestcontainers PG, provider / model factoriesintegration/with a DB, per-test rollback
model-lifecycle-lockRESTRICT lock on a model in usedefault-model · provider-cascadeexactly one default, cascade / CHECK / maskmodel-usage-upsert · tools-registryusage upsert, tool catalogseed-migrationseed + downgrade reversibility