Files
kite/.planning/STATE.md
T
curo1305 6bd57629ce docs(03-04): complete flat-file settings retirement and per-user AI classification plan
- 03-04-SUMMARY.md: Plan complete — classifier signature, env var defaults, security
  mitigations T-03-17/18/19/21 all resolved; DOC-03, DOC-05 requirements completed
- STATE.md: Advance to Plan 4/5 complete, add 5 key decisions from this plan
- ROADMAP.md: Mark 03-04-PLAN.md complete (Wave 4)
- REQUIREMENTS.md: Mark DOC-03 and DOC-05 as complete
2026-05-23 20:39:33 +02:00

8.9 KiB

gsd_state_version, milestone, milestone_name, current_phase, status, last_updated, progress
gsd_state_version milestone milestone_name current_phase status last_updated progress
1.0 v1.0 milestone 3 executing 2026-05-23T14:49:20.062Z
total_phases completed_phases total_plans completed_plans percent
5 2 15 13 40

Project State

Project: DocuVault Status: Phase 3 In Progress — Plan 04 Complete Current Phase: 3 Last Updated: 2026-05-23

Phase Status

Phase Name Status
1 Infrastructure Foundation ✓ Complete
2 Users & Authentication ✓ Complete (5/5 plans)
3 Document Migration & Multi-User Isolation In Progress (4/5 plans complete)
4 Folders, Sharing, Quotas & Document UX Not Started
5 Cloud Storage Backends Not Started

Current Position

Phase: 03-document-migration-multi-user-isolation — In Progress Plan: 4/5 complete (Plan 04: Flat-file settings retirement + per-user AI classification) Progress: ████░░░░░░ 53% (2/5 phases complete, 14/15 plans done)

Performance Metrics

Metric Value
Phases complete 1 / 5
Requirements mapped 54 / 54
Plans written 5 (Phase 1)
Plans complete 10 (5 Phase 1 + 5 Phase 2)

Accumulated Context

Key Decisions

Decision Rationale
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
user_id as refresh token family proxy No separate family_id column; user_id serves as family per RFC 9700 — simpler schema
pwdlib over passlib pwdlib actively maintained with clean Argon2Hasher API; passlib unmaintained
TOTP replay TTL=90s valid_window=1 covers ±30s (90s total) — TTL matches window
HIBP fail-open Network errors return False + log warning; auth never blocked by external service
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
Deferred Celery import in /password-reset send_reset_email.delay called via from tasks.email_tasks import send_reset_email inside handler body — same circular-import fix as document_tasks
TOTP QR code as otpauth:// link No QR library installed; plan permits manual secret display for MVP; functional flow complete without rendered QR image
ConfirmBlock no acknowledgment checkbox ConfirmBlock handles message + button pair; BackupCodesDisplay owns its separate acknowledgment checkbox — no overlap
ADMIN-07 enforced by omission No impersonation endpoint exists; AST check + test_admin_impersonation_not_found verify absence; violates privacy-first core value
_user_to_dict() whitelist for admin responses Explicit field whitelist prevents accidental password_hash/credentials_enc leakage from admin endpoints
Quota warning is 200 not 4xx Below-usage limit change is applied; warning=True advisory field returned — not a rejection
AdminQuotasTab fetches quotas per-user via Promise.allSettled adminListUsers() does not include quota fields; per-user endpoint parallelized; failed quotas filtered silently
Temp password via crypto.getRandomValues Browser-native CSPRNG; no external library; always satisfies AUTH-01 strength rules
batch_alter_table for NOT NULL in migration 0003 SQLite requires batch_alter_table for ALTER COLUMN; transparent passthrough on PostgreSQL — enables SQLite CI test runs
MinIO step in migration 0003 gated on MINIO_ENDPOINT Migration skips MinIO deletions when env var absent; enables safe SQLite test runs per T-03-02
raising=False for Phase 3 MinIO mock fixtures mock_minio_presigned + mock_minio_stat patch methods that don't exist until Plan 03-02; raising=False pre-installs them
Dual MinIO client (internal + public) Presigned URL HMAC signature must be computed with browser-visible hostname (localhost:9000); using internal Docker client (minio:9000) causes browser signature mismatch
Wave 2 user_id=None guard upload-url sets user_id=None + object_key "null-user/" prefix; confirm skips quota when user_id is None; Plan 03-03 removes both guards
SQLite quota xfail(strict=False) SQLite stores UUID as CHAR(32) without dashes; raw SQL WHERE user_id = :uid never matches str(uuid) dashed format — test-env limitation, not code defect
Celery mock required in /confirm tests extract_and_classify.delay() connects to Redis; monkeypatch blocks it in unit tests; MagicMock pattern established for all confirm endpoint tests
get_regular_user raises 403 for admin Admin is authenticated but must not access document content; 401 would falsely imply unauthenticated — 403 is correct for role rejection
Cross-user doc access returns 404 not 403 Combining "not found" and "wrong owner" into 404 prevents attacker from learning which doc IDs exist for other users (D-16, T-03-11)
CASE WHEN replaces GREATEST in quota decrement SQLite lacks GREATEST scalar function; CASE WHEN used_bytes > :delta THEN used_bytes - :delta ELSE 0 END is semantically equivalent and SQLite-compatible
load_topics_for_user uses or_(user_id == x, user_id.is_(None)) SQLAlchemy is_(None) not == None; or_() combines system topics and user's own topics for namespace-scoped query (D-17, DOC-04)
AI-suggested topics go in user namespace classifier passes user_id=doc.user_id to create_topic; AI-suggested topics are per-user not system-wide (D-11)
Celery task signature unchanged for ai_provider Task receives only document_id; ai_provider/ai_model resolved inside _run via session.get(User, doc.user_id) — prevents broker injection (T-03-19)
_DEFAULT_SYSTEM_PROMPT in classifier.py System prompt env var is optional; hardcoded fallback kept in classifier module not config.py (D-13)
Default AI provider is ollama/llama3.2 Code defaults; overridable via DEFAULT_AI_PROVIDER / DEFAULT_AI_MODEL env vars (D-15)
/settings route kept as static placeholder SettingsView shows admin-managed card; route not removed to avoid UX regression (Risk 6)

Open Questions

  • Verify cloud SDK minor versions on PyPI before Phase 5 pinning

Blockers

None.

Session Continuity

Updated at each phase transition.

Field Value
Last session 2026-05-23 — Executed Plan 03-04 (flat-file settings retirement, per-user AI classification, frontend placeholder)
Next action Run /gsd:execute-phase 3 to execute Plan 03-05
Pending decisions None
Resume file .planning/phases/03-document-migration-multi-user-isolation/03-05-PLAN.md