Files
kite/.planning/STATE.md
T
curo1305 a5994d9ff4 chore: commit pending phase-3 work and add TEST_ACCOUNTS.md
Includes planning artifacts (03-CONTEXT, 03-DISCUSSION-LOG, 03-02-SUMMARY),
integration test script, MinIO/auth/docker fixes, and local dev account reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 11:30:56 +02:00

9.6 KiB
Raw Blame History

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-23T23:47:54.258Z
total_phases completed_phases total_plans completed_plans percent
5 3 15 15 60

Project State

Project: DocuVault Status: Phase 3 In Progress — Plan 05 Tasks 1-2 Complete (awaiting human checkpoint) 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 (5/5 plans — checkpoint pending)
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: 5/5 tasks 1-2 done; Task 3 checkpoint awaiting human verification Progress: ████░░░░░░ 57% (2/5 phases complete, 14/15 plans committed; Phase 3 checkpoint pending)

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)
Plain anchor in quota rejection block used instead of to avoid import dependency in upload component
uploadProgress entries owned by parent Store does not clear uploadProgress map entries after upload; DropZone/parent clears on row dismiss
fetchQuota silent catch in auth store Silent catch keeps last-known values; QuotaBar owns loadFailed state and hides on error (UI-SPEC)
XHR PUT progress range 590 5 + Math.round(pct * 0.85) maps XHR 0-100 → visual 5-90; remaining 10% covers confirm + enqueue

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-05 (3-step XHR upload, QuotaBar, UploadProgress error block)
Next action Human checkpoint Task 3: test upload/quota/413 flow in browser; type "approved" or describe failures
Pending decisions None
Resume file .planning/phases/03-document-migration-multi-user-isolation/03-05-SUMMARY.md