feat(07-01): services/ai_config.py HKDF helpers, ProviderConfig loader, env seed
- Create backend/services/ai_config.py with:
- _derive_ai_settings_key(master_key, provider_id): fresh HKDF per call, salt=provider_id.encode("utf-8"), info=b"ai-provider-settings" (domain-separated from b"cloud-credentials")
- encrypt_api_key / decrypt_api_key: Fernet round-trip without JSON wrapping
- load_provider_config(session): reads is_active=True row from system_settings, decrypts api_key_enc; returns stub ProviderConfig (real class lands in Plan 02)
- seed_system_settings_from_env(session): idempotent insert of default provider on first boot
- _ProviderConfigStub: minimal Pydantic model stub until ai/provider_config.py exists (Plan 02)
- Update backend/main.py: import seed_system_settings_from_env, call in lifespan with try/except so missing table (pre-migration fresh boot) doesn't crash startup
- Round-trip smoke test: encrypt_api_key → decrypt_api_key == original plaintext
This commit is contained in:
@@ -22,6 +22,7 @@ from api.documents import router as documents_router
|
||||
from api.topics import router as topics_router
|
||||
from config import settings
|
||||
from db.session import AsyncSessionLocal, engine
|
||||
from services.ai_config import seed_system_settings_from_env
|
||||
from services.logging import setup_logging
|
||||
from services.rate_limiting import account_limiter
|
||||
|
||||
@@ -158,6 +159,19 @@ async def lifespan(app: FastAPI):
|
||||
async with AsyncSessionLocal() as session:
|
||||
await bootstrap_admin(session)
|
||||
|
||||
# AI provider seed (D-04 / Phase 7): populate system_settings from env vars
|
||||
# on first boot. Wrapped in try/except so that a missing table (fresh container
|
||||
# before migrations run) does not crash startup — logs a warning and skips.
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
await seed_system_settings_from_env(session)
|
||||
await session.commit()
|
||||
except Exception as _seed_exc:
|
||||
import logging as _logging
|
||||
_logging.getLogger(__name__).warning(
|
||||
"AI provider seed skipped (table may not exist yet): %s", _seed_exc
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown: close pooled connections and Redis
|
||||
|
||||
Reference in New Issue
Block a user