docs: discovery blueprint + multi-provider design (#8)
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 18s
CI / backend (pull_request) Successful in 52s
CI / frontend (pull_request) Successful in 15s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 55s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 54s

This commit was merged in pull request #8.
This commit is contained in:
2026-07-01 19:59:53 +02:00
parent 9ee5d757f5
commit 4ce2df0a2b
27 changed files with 2032 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
# 08 — Technical Architecture (Phase 7)
How the redesign is built on the **existing Clean Architecture** — extending seams, not
rewriting. Everything here preserves the dependency rule `Api → Infrastructure →
Application → Domain` and the security posture from [01](01-architecture-review.md).
> **Update — multi-provider platform.** This architecture now assumes the
> [multi-provider design](multi-provider/README.md): the Gmail-centric `Email`/`Sender`
> model generalises to **account-scoped, provider-normalised** entities behind
> `IEmailProvider`; the app becomes **small-team multi-user** (Admin/Member) with
> **OAuth-as-login**, settings, and feature flags; and **AI gating is feature-flag-driven**
> (`ai.enabled` system flag → user pref). See that folder for the provider abstraction,
> DB schema, security model, and admin/settings design; this doc remains the search/AI/
> caching/indexing reference they build on.
## Overall
```
React SPA (v2 shell)
│ REST (+ SSE for streaming AI/search)
Api ──────────────────────────────────────────────
Application: IInboxAi · ISearchService · SearchIntentParser · DTOs
Infrastructure:
├ Search: SearchService (structured + lexical rank + hybrid)
├ AI: IAiProvider / IEmbeddingProvider / model router / VRAM guard
├ Jobs: EmbeddingBackfill · AiEnrichment · IndexMaintenance (IHostedService + Channel queue)
├ Gmail/Sync/Cleanup/Analytics (existing)
└ Persistence: EF Core + Npgsql (+ pgvector)
Domain: entities/enums (+ Embedding, Entities, ThreadSummary, SavedSearch)
External (optional): Ollama (local, own container) · cloud AI (opt-in only)
```
## Search architecture
The 4-layer engine from [05](05-search-redesign.md):
- **Structured** (SQL WHERE) + **Lexical** (`websearch_to_tsquery` + `ts_rank_cd`, weighted
multi-field `tsvector`, `pg_trgm` fuzzy fallback) — **always on**.
- **Semantic** (pgvector, HNSW) + **AI-assisted** (intent parse, RAG) — **optional**.
- **Hybrid fusion (RRF)** merges lexical+semantic; boosts recency/sender/unread.
- **Keyset pagination** replaces offset; score-ordered top-N for ranked queries.
- `SearchIntentParser` (evolves `GmailQueryParser`): operators → rules-NL → optional LLM.
## AI architecture
- Provider interfaces + `IInboxAi` facade + **model router** (task→model via config) +
**prompt templates** (versioned files) + **VRAM guard** (serialises heavy vision jobs
behind unloading the 7B; embeddings stay hot). See [06](06-ai-strategy.md).
- **Streaming** via SSE for summaries/replies/RAG answers (perceived speed).
- **Bounded**: per-call timeout, `CancellationToken`, Polly fallback to Null path.
## Plugin / modularity architecture
- **Analyzer pipeline:** email enrichment is a set of `IEmailAnalyzer` plugins
(classifier, entity-extractor, summariser, phishing, dedup). Each declares required
capabilities (`Chat`/`Embeddings`/none) and is **skipped gracefully** if unavailable.
New AI features = new analyzers; no core changes.
- **Search providers** implement a common `ISearchLayer` so semantic/AI layers plug in.
- **AI providers** already pluggable (`IAiProvider`); adding a provider = one class + config.
- This is the "modular AI" requirement realised structurally.
## Caching
- **Embeddings & summaries**: persisted in Postgres (compute once, invalidate on new msg).
- **Search suggestions / recent**: cached per-user (memory + DB); debounced live search.
- **Query results**: short-TTL cache for identical repeated queries; ETag on read endpoints.
- **Model warmth**: Ollama `keep_alive` keeps the 7B hot between calls.
## Indexing
| Index | Column | Purpose |
|-------|--------|---------|
| GIN | `SearchVector` (weighted A/B/C/D) | Full-text |
| GIN `pg_trgm` | sender addr/name, subject | Fuzzy / substring (fixes non-sargable `.Contains`) |
| HNSW | `Email.Embedding` (pgvector) | Semantic k-NN |
| btree | `(UserId, SentAtUtc)`, `(UserId, ThreadId)` | Keyset pagination, threading |
- Built incrementally on sync; **EmbeddingBackfillWorker** batch-fills history (VRAM-aware, low priority).
## Background jobs
- Keep the existing `IHostedService` worker pattern; add a **`Channel<T>` in-process queue**
with a bounded concurrency worker for AI enrichment (summaries/entities/embeddings on new
mail). Idempotent, resumable, backpressured. (Upgrade path: durable queue if multi-node.)
- Jobs: `GmailSyncWorker` (exists), `DigestWorker` (exists), `EmbeddingBackfillWorker`,
`AiEnrichmentWorker`, `IndexMaintenanceWorker`.
## Database schema improvements
- **Widen** `Email.SearchVector` (subject/sender/filename/labels, weighted).
- **Add**: `Email.Embedding vector(768)`; tables `EmailEntities`, `ThreadSummary`,
`SavedSearch`, `SearchHistory`, `AiJob` (status/retry), `SenderImportance` (materialised).
- **Fix debt**: resolve the `Email↔EmailLabel` global-query-filter warning (optional nav or
matching filters); make FTS **language-aware** (detect → per-language config) instead of hardcoded English.
- **Scale**: consider per-`UserId` list partitioning of `Emails` if single users exceed ~1M rows.
## Scalability
- Single-user/self-host is the primary shape → vertical scaling + good indexes is enough.
- Connection pooling (Npgsql), keyset pagination, top-N-by-score, batched embeddings.
- **Ollama is the throughput bottleneck** → serialise/queue AI, cache aggressively, prefer
the small/embedding models for high-volume paths.
- Multi-tenant future: read replicas, per-user partitioning, durable job queue.
## Deployment
- Extend the Compose stack (from the CI/CD we built): add an **optional `ollama` service**
(profile `ai`) and a **pgvector-enabled Postgres image**. AI-off deployments omit the
profile entirely. Dev→staging auto-deploy already proven; prod is tag-gated.
## Offline support
- Data is **already local** (Postgres on the user's machine) — the product is local-first
by nature. SPA: service-worker cache for shell + last-viewed mail (read offline); queue
mutations (label/cleanup) to replay on reconnect. AI is inherently offline (Ollama local).
## Security model
- Retain: OAuth **read-only** scope, encrypted refresh tokens (Data Protection), **IDOR
global query filters**, SSRF egress guard, non-root containers, confirmed+previewed
destructive actions.
- **New AI concerns:**
- **Prompt injection** — email content is untrusted input to the LLM. Treat model output
as **advisory only**; never let it trigger actions directly; sanitise/format; the LLM
can *suggest* but a human/rule confirms (aligns with "AI never acts").
- **Attachment/vision** — sandbox parsing; size/type limits; never execute.
- **Local-only by default** — cloud provider is explicit opt-in with per-feature consent
and egress logging; the SSRF guard already constrains outbound calls.
- Secrets stay in `deploy/.env` / Actions secrets (never in git) — as established.