Tasks: 3/3 complete. Commits:4febe2f,4eb3177,0fd6930. Created system_settings table (Alembic 0005), SystemSettings ORM, HKDF AI key encryption (info=b"ai-provider-settings"), load_provider_config(), seed startup hook, and 13 Wave 0 xfail stubs across 4 new test files.
8.4 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 07-redo-and-optimize-llm-integration | 01 | backend/ai-config |
|
|
|
|
|
|
Phase 7 Plan 01: Database, Encryption, and Test Scaffolding Foundation Summary
HKDF/Fernet encryption layer for AI provider API keys stored in a new system_settings DB table; Wave 0 xfail test stubs providing the full Phase 7 testing skeleton; startup seed from env vars.
Tasks Completed
| Task | Description | Commit | Files |
|---|---|---|---|
| 1 | Wave 0 xfail stubs — 13 new stubs across 4 new files + 1 append | 4febe2f |
test_ai_providers.py, test_ai_config.py, test_admin_ai_config.py, test_document_tasks.py, test_documents.py |
| 2 | Alembic migration 0005 + SystemSettings ORM model | 4eb3177 |
migrations/versions/0005_system_settings.py, db/models.py |
| 3 | services/ai_config.py — HKDF helpers, loader, env seed + main.py wiring | 0fd6930 |
services/ai_config.py, main.py |
What Was Built
Task 1: Wave 0 Test Scaffold
Created four new test files and appended one stub to an existing file:
test_ai_providers.py— 7 stubs: test_generic_openai_json_mode, test_anthropic_structured_output, test_get_provider_typed, test_client_singleton, test_context_chars_truncation, test_smart_truncation, test_gemini_fallback_to_parse_classificationtest_ai_config.py— 2 stubs: test_load_provider_config, test_api_key_encrypt_decrypttest_admin_ai_config.py— 2 stubs: test_get_never_returns_key, test_put_writes_active_providertest_document_tasks.py— 2 stubs: test_retry_backoff, test_exhaustion_sets_failed_statustest_documents.py— 1 appended stub: test_reclassify_requeues_celery
All stubs use @pytest.mark.xfail(strict=False, reason="Wave 0 stub — promoted in Plan 07-XX") per the xfail(strict=False) STATE.md decision. Bodies are single pytest.xfail() calls only.
D-14 regression guard verified: docker-compose.yml contains 3 host.docker.internal:host-gateway entries (backend + celery-worker + celery-worker-two), satisfying the minimum-2 requirement.
Task 2: Alembic Migration 0005 + SystemSettings ORM
backend/migrations/versions/0005_system_settings.py creates the system_settings table with:
id: UUID PK withgen_random_uuid()server defaultprovider_id: String NOT NULL UNIQUE (uq_system_settings_provider_id constraint)api_key_enc: Text NULL (Fernet-encrypted; NULL for local providers like Ollama)base_url: Text NULLmodel_name: Text NOT NULL, default ''context_chars: Integer NOT NULL, default 8000is_active: Boolean NOT NULL, default falsecreated_at/updated_at: TIMESTAMPTZ NOT NULL, default now()
backend/db/models.py gains the SystemSettings(Base) ORM model with identical column declarations using Mapped[] type syntax, mirroring the CloudConnection pattern.
Task 3: services/ai_config.py
Complete encryption + loader + seed module:
_derive_ai_settings_key(master_key, provider_id): Creates FRESH HKDF on every call (AlreadyFinalized guard),salt=provider_id.encode("utf-8"),info=b"ai-provider-settings"(domain-separated fromb"cloud-credentials")encrypt_api_key(master_key, provider_id, api_key):Fernet.encrypt(api_key.encode()).decode()— no JSON wrappingdecrypt_api_key(master_key, provider_id, api_key_enc):Fernet.decrypt(...).decode()— no JSON unwrappingload_provider_config(session): Readsis_active=Truerow, decrypts API key, returns_ProviderConfigStub(upgraded to realProviderConfiglazily once Plan 02 lands)seed_system_settings_from_env(session): Inserts default provider row fromsettings.default_ai_provider / default_ai_modelwhen no row exists for that provider_id; idempotent_ProviderConfigStub: Minimal Pydantic model stub; removed in Plan 03
backend/main.py updated to import and call seed_system_settings_from_env inside the async lifespan with try/except so that a missing table (pre-migration fresh container) logs a warning and skips rather than crashing.
Verification Results
- Round-trip smoke test:
encrypt_api_key(mk, 'openai', 'sk-test')→decrypt_api_key(mk, 'openai', ct) == 'sk-test'— PASS - Domain salt isolation: ciphertext for
provider_id="openai"vsprovider_id="anthropic"differs — PASS - AlreadyFinalized guard:
_derive_ai_settings_key()called twice without error — PASS - Domain separation:
info=b"ai-provider-settings"present;info=b"cloud-credentials"absent from ai_config.py — PASS SystemSettings.__table_args__containsUniqueConstraint(name="uq_system_settings_provider_id")— PASSfrom db.models import SystemSettingsexits 0 — PASS- Full suite: 1 failed (pre-existing test_extract_docx), 357 passed, 21 xfailed — PASS (no new failures)
Deviations from Plan
None — plan executed exactly as written.
Known Stubs
_ProviderConfigStubinservices/ai_config.pylines 44-50: intentional temporary stub; Plan 02 createsai/provider_config.pyandload_provider_config()will importProviderConfigfrom there via lazy import.
Threat Flags
No new threat surface introduced beyond what is described in the plan's <threat_model>. The system_settings table is accessed only by the seed_system_settings_from_env startup hook and the load_provider_config service function. No new API endpoints or network-accessible paths are added in this plan.
Self-Check: PASSED
Files created/modified:
- backend/migrations/versions/0005_system_settings.py — FOUND
- backend/db/models.py — FOUND (SystemSettings class present)
- backend/services/ai_config.py — FOUND
- backend/main.py — FOUND (seed call present)
- backend/tests/test_ai_providers.py — FOUND (7 xfail stubs)
- backend/tests/test_ai_config.py — FOUND (2 xfail stubs)
- backend/tests/test_admin_ai_config.py — FOUND (2 xfail stubs)
- backend/tests/test_document_tasks.py — FOUND (2 xfail stubs)
- backend/tests/test_documents.py — FOUND (test_reclassify_requeues_celery appended)
Commits: