5 Decisions
jlightner edited this page 2026-04-04 10:31:50 -05:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Decisions

Architectural and pattern decisions made during Chrysopedia development. Append-only — to reverse a decision, add a new entry that supersedes it.

Architecture Decisions

# When Decision Choice Rationale
D001 Storage layer selection PostgreSQL + Qdrant + local filesystem PostgreSQL for JSONB, Qdrant already running on hypervisor, filesystem for transcript JSON
D002 Timestamp handling (asyncpg) datetime.now(timezone.utc).replace(tzinfo=None) asyncpg rejects timezone-aware datetimes for TIMESTAMP WITHOUT TIME ZONE columns
D004 Sync vs async in Celery tasks Sync openai, QdrantClient, SQLAlchemy in Celery Avoids nested event loop errors with gevent/eventlet workers
D005 Embedding failure handling Non-blocking — log errors, don't fail pipeline Qdrant may be unreachable; core output (PostgreSQL) is preserved
D007 M001/S04 Review mode toggle persistence Redis key chrysopedia:review_mode Redis already in stack; simpler than DB table for single boolean
D009 M001/S05 Search service pattern Separate async SearchService for FastAPI Keeps sync pipeline clients untouched; 300ms timeout + keyword fallback
D015 M002/S01 Docker network subnet 172.32.0.0/24 172.24.0.0/24 was taken by xpltd_docs_default
D016 M002/S01 Embedding service Ollama container (nomic-embed-text) OpenWebUI doesn't serve /v1/embeddings
D017 CSS theming 77 semantic custom properties, cyan accent Full variable-based palette for consistency and future theme switching
D018 M004/S04 Version snapshot failure handling Best-effort — failure doesn't block page update Follows D005 pattern for non-critical side effects
D019 M005/S02 Technique page layout CSS grid 2-column (1fr + 22rem sidebar), 64rem max-width Collapses at 768px; accommodates prose + sidebar
D023 M012/S01 Qdrant embedding text enrichment Prepend creator_name, join topic_tags Enables creator-name and tag-specific semantic search
D024 M014/S01 Sections with subsections content model Empty-string content for parent sections Avoids duplication; substance lives in subsection content fields
D025 M015 Search query storage PostgreSQL search_log + Redis cache (5-min TTL) Full history for analytics; Redis prevents DB hit on every homepage load

Phase 2 Decisions

# Decision Choice Rationale
D031 Phase 2 milestone structure 8 milestones (M018M025) with parallel frontend/backend slices Maps to Sprint 0-8 plan; deploy gate per milestone
D032 RAG framework LightRAG + Qdrant + NetworkX (MVP) Graph-enhanced retrieval; supports existing Qdrant; incremental updates
D033 Monetization Demo build with "Coming Soon" placeholders Recruit creators first; Stripe Connect deferred to Phase 3
D034 Documentation strategy Forgejo wiki, KB slice at end of every milestone Incremental docs stay current; final pass in M025
D035 File/object storage MinIO (S3-compatible) self-hosted Docker-native, signed URLs, fits existing infrastructure

Authentication & Infrastructure Decisions

# When Decision Choice Rationale
D036 M019/S02 JWT auth configuration HS256 with existing app_secret_key, 24h expiry, OAuth2PasswordBearer Reuses existing secret; integrates with FastAPI dependency injection
D037 Search impressions query Exact case-insensitive title match via EXISTS subquery against SearchLog MVP approach; expandable to ILIKE later
D038 Primary git remote git.xpltd.co (Forgejo) instead of github.com Consolidating on self-hosted Forgejo; wiki already there

Search & Retrieval Decisions

# When Decision Choice Rationale
D039 M021/S01 LightRAG scoring strategy Position-based (1.0 → 0.5 descending), sequential Qdrant fallback /query/data has no numeric relevance score
D040 M021/S02 Creator-scoped retrieval 4-tier cascade: creator → domain → global → none Progressive widening; ll_keywords for soft scoping; 3x oversampling for post-filter survival

M022 Decisions

# When Decision Choice Rationale
D041 M022/S05 Highlight scorer weight distribution 10 dimensions: original 7 reduced proportionally, 3 audio proxy dims get 0.22 total weight. Neutral fallback (0.5) when word_timings unavailable. Audio proxy signals from word-level timing data; neutral fallback preserves backward compatibility

UI/UX Decisions

# Decision Choice
D014 Creator equity Random default sort; no creator privileged
D020 Topics card differentiation 3px colored left border + dot
D021 M011 findings triage 12/16 approved; denied beginner paths, YouTube links, hide admin, CTA label
D030 ToC scroll-spy rootMargin 0px 0px -70% 0px — active when in top 30% of viewport

M023 Decisions

# When Decision Choice Rationale
D042 M023/S01 Rich text editor for creator posts Tiptap (headless, React) with StarterKit + Link + Placeholder. Store Tiptap JSON in JSONB column, render client-side via @tiptap/html. Headless architecture fits dark theme customization. JSON storage is lossless and enables future server-side rendering. No HTML sanitization needed.
D043 M023/S02 Personality weight modulation strategy 3-tier intensity (<0.4 subtle, 0.40.8 voice, ≥0.8 embody) with temperature scaling 0.30.5. Superseded by D044. Initial stepped approach; replaced by continuous interpolation.
D044 M023/S04 Personality weight modulation strategy (revision) 5-tier continuous interpolation. Progressive field inclusion: <0.2 no personality; 0.2+ tone; 0.4+ descriptors; 0.6+ phrases (count scaled); 0.8+ vocabulary/style; 0.9+ summary. Temperature: 0.3 + weight × 0.2. 3-tier step function had jarring transitions. Continuous interpolation with progressive field inclusion gives finer control. 0.00.19 dead zone ensures purely encyclopedic mode.

M025 Decisions

# When Decision Choice Rationale
D045 M025/S01 Signed unsubscribe token library PyJWT (already a dependency) over itsdangerous Avoids adding a new dependency; PyJWT supports exp claims natively for self-expiring tokens
D046 M025/S10 Sticky section bar for technique pages Built ReadingHeader with sentinel-based IntersectionObserver Sticky elements never leave viewport for standard IO detection; sentinel div pattern solves this reliably
D047 M025/S04 Rate limiter failure mode Fail-open (allow request on Redis error) Availability over strictness for a single-admin platform; rate limiting is cost protection, not security
D048 M025/S08 LLM fallback strategy Catch-and-retry with secondary client on transient errors Matches sync LLMClient pipeline pattern; propagates fallback_used through SSE and usage logging

See also: Architecture, Development-Guide