| PostgreSQL + MinIO |
Multi-user quotas and horizontal scaling require shared, consistent state |
| HKDF per-user key derivation |
Single Fernet key would be catastrophic on leak — must be derived before first credential is stored |
| Presigned MinIO URL flow |
FastAPI handles metadata only; bytes never pass through the API layer |
| Atomic PostgreSQL quota UPDATE |
Never perform quota arithmetic in Python between two DB statements |
| JWT in httpOnly cookie |
Refresh token in httpOnly cookie; access token in Pinia memory only — never localStorage |
| Refresh token family revocation |
RFC 9700 — reuse of a rotated token revokes entire family and alerts user |
| BackgroundTasks replacement |
FastAPI BackgroundTasks is per-instance; replace with Celery+Redis or pgqueuer before horizontal scale |
| AuditLog metadata_ ORM attribute |
metadata is reserved on DeclarativeBase; ORM attribute is metadata_ with name="metadata" kwarg to avoid silent collision |
| documents.user_id nullable Phase 1 |
D-03 — no auth in Phase 1; Phase 2 migration adds NOT NULL after auth lands |
| groups stub table Phase 1 |
D-02 — groups is a v2 feature; table created now for schema completeness, no rows until Phase 2+ |
| SEQUENCES grants in migration |
GRANT USAGE/SELECT on sequences required for audit_log.id autoincrement nextval() by docuvault_app |
| Admin impersonation excluded |
Explicit architectural exclusion — no endpoint or UI pathway; violates privacy-first core value |
| Two-DSN PostgreSQL strategy |
DATABASE_URL (docuvault_app, DML only) + DATABASE_MIGRATE_URL (docuvault_migrate, DDL only); celery-worker gets only DATABASE_URL |
| MinIO healthcheck via mc ready local |
curl removed from MinIO Docker image since Oct 2023; mc is the correct in-container healthcheck tool |
| pydantic-settings v2 SettingsConfigDict |
SettingsConfigDict API used (not deprecated class Config form) for env var config |
| async_client fixture name |
Distinct from legacy sync client fixture to avoid collision; both coexist until Plan 05 |
| xfail(strict=False) for Wave 0 |
All pre-implementation scaffolds use strict=False so unexpected passes don't break CI |
| StorageBackend ABC + factory mirrors ai/ pattern |
5 abstract methods; get_storage_backend() factory; MinIOBackend wraps all sync Minio SDK calls in asyncio.to_thread() |
| STORE-02 key enforced in code |
MinIOBackend.put_object constructs {user_id}/{document_id}/{uuid4()}{ext}; no filename parameter — only extension passes through |
| null-user D-03 sentinel |
services/storage.save_upload uses user_id="null-user" in Phase 1 (no auth); Phase 2 replaces with str(current_user.id) |
| load_settings flat-file Phase 1 |
users.ai_provider/ai_model columns cannot be populated until Phase 2; settings remain flat-file JSON for Phase 1 |