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
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:
@@ -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.
|
||||
Reference in New Issue
Block a user