"""Add system_settings table for AI provider configuration. Revision ID: 0005 Revises: 0004 Create Date: 2026-06-04 Changes: 1. Create system_settings table with one row per AI provider. Stores: api_key_enc (Fernet-encrypted), base_url, model_name, context_chars, is_active flag, and timestamps. 2. Add UNIQUE constraint on provider_id (uq_system_settings_provider_id) to enforce one row per provider name. Design notes (07-RESEARCH.md Pattern 1): - One row per provider (not key-value store) — typed columns, enforced uniqueness. - id column uses gen_random_uuid() server default. - api_key_enc is Text nullable (NULL for local providers like Ollama/LMStudio). - is_active = TRUE for the single active provider; all others FALSE. - Encryption uses HKDF with info=b"ai-provider-settings" (domain separation from cloud credentials which use info=b"cloud-credentials" — same master key, different derived keys; see services/ai_config.py). """ from __future__ import annotations import sqlalchemy as sa from sqlalchemy.dialects.postgresql import UUID as PG_UUID from alembic import op # revision identifiers, used by Alembic. revision = "0005" down_revision = "0004" branch_labels = None depends_on = None def upgrade() -> None: op.create_table("system_settings", sa.Column( "id", PG_UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()"), ), sa.Column("provider_id", sa.String, nullable=False), sa.Column("api_key_enc", sa.Text, nullable=True), sa.Column("base_url", sa.Text, nullable=True), sa.Column( "model_name", sa.Text, nullable=False, server_default="", ), sa.Column( "context_chars", sa.Integer, nullable=False, server_default="8000", ), sa.Column( "is_active", sa.Boolean, nullable=False, server_default="false", ), sa.Column( "created_at", sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.Column( "updated_at", sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.UniqueConstraint("provider_id", name="uq_system_settings_provider_id"), ) def downgrade() -> None: op.drop_table("system_settings")