78 lines
4.7 KiB
Markdown
78 lines
4.7 KiB
Markdown
# #85 — Refonte architecturale : découpage du monolithe & persistance d'état (phase 2)
|
|
|
|
> **Statut :** livré (T1→T10) — `backend/main.py` 4 827 → ~750 lignes, 14 routers,
|
|
> persistance partielle (stores verrouillés + rate-limit SQLite optionnel).
|
|
> Méthode : tranches à impact minimal, comportement inchangé, un domaine par
|
|
> commit, suite complète verte à chaque commit (1320 passed / 6 skipped).
|
|
|
|
## 1. Découpage du monolithe (T1→T9, comportement inchangé)
|
|
|
|
Chaque tranche déplace un domaine vers `backend/routers/` (handlers verbatim,
|
|
mêmes chemins/modèles/auth/tags OpenAPI), les modèles vers `backend/schemas.py`,
|
|
et ne committe que sur suite verte + `test_version` vert.
|
|
|
|
| Tranche | Domaine | Nouveau module | Version |
|
|
|---|---|---|---|
|
|
| T1 | health (`/api/health*`) | `routers/health.py` (+ `HealthResponse` → schemas) | 2.27.2 |
|
|
| T2 | webhooks CRUD | `routers/webhooks.py` | 2.27.3 |
|
|
| T3 | sharing (`/api/share*`, `/s/*`) | `routers/sharing.py` | 2.27.4 |
|
|
| T4 | backups (9 routes) | `routers/backups.py` (+ `Diff/Restore*` → schemas, `backend/sse.py`) | 2.27.5 |
|
|
| T5 | search (11 routes) | `routers/search.py` (+ modèles → schemas, `backend/search_executor.py`) | 2.27.6 |
|
|
| T6a | lecture fichiers | `routers/files_read.py` (+ modèles, `routers/helpers.py`) | 2.27.7 |
|
|
| T6b | mutations fichiers/dossiers | `routers/files_write.py` (+ 15 modèles → schemas) | 2.27.8 |
|
|
| T6c | media/pdf/export/guide | `routers/files_media.py` (Range helper → `helpers.py`) | 2.27.9 |
|
|
| T7 | config (12 routes) | `routers/config.py` (`_FALLBACK_MODELS` déplacé) | 2.27.10 |
|
|
| T8 | vaults + history + conflicts (13 routes) | `routers/vaults.py`, `history.py`, `conflicts.py` (+ `backend/watcher_state.py`) | 2.27.11 |
|
|
| T9 | realtime + render | `routers/realtime.py` (SSE + collab WS), `backend/render.py` | 2.27.12 |
|
|
|
|
`main.py` ne contient plus que l'assemblage : lifespan, middlewares, montage
|
|
des routers, racine `/api`, statique/SPA, 4 cales de compatibilité testées
|
|
(`_resolve_safe_path`, `_backup_file`, `_check_vault_writable`, `_get_backup_dir`).
|
|
|
|
Correctifs au passage : décorateur orphelin `/s/{token}` (double-enregistrement
|
|
de `/api/conflicts`), tag OpenAPI `media` inexistant (assignation par chemin
|
|
conservée), tests statiques frontend réalignés (`image-viewer`, `media-viewer`),
|
|
tests repointés vers les modules canoniques (`test_ai_models`, `test_api_main`).
|
|
|
|
## 2. Persistance d'état (T10)
|
|
|
|
| État | Avant | Après |
|
|
|---|---|---|
|
|
| JTI révoqués (`revoked_tokens.json`) | persisté, **sans verrou** | `RLock` (load/save/revoke/check) |
|
|
| `shares.json` | persisté, **sans verrou** | `RLock` (4 mutateurs) |
|
|
| `webhooks.json` + secrets | persistés, **sans verrou** | `RLock` (create/update/delete/secrets) |
|
|
| `api_keys.json` (tool-secrets) | persisté, **sans verrou** | `RLock` (set/delete) |
|
|
| Rate-limit auth | mémoire, mono-process | **inchangé par défaut** + option `OBSIGATE_RATELIMIT_DB` (SQLite WAL : mêmes fenêtres/budgets, partagé multi-workers, survit au redémarrage) |
|
|
| Index de recherche | mémoire, rebuild au démarrage | **conservé** (voir §3) |
|
|
| `users.json`, `api_tokens.json`, `vault_settings.json` | déjà verrouillés (BUG-029, #107) | inchangé |
|
|
|
|
Tests : `tests/test_store_locks.py` (4 — concurrence threads, pertes prouvées
|
|
sans verrou : 25/200 partages), `tests/test_ratelimit_store.py` (7 —
|
|
sémantique SQLite identique, persistance, concurrence 200/200).
|
|
|
|
Déjà existants et vérifiés (pas de code) : verrous `threading` + `asyncio`
|
|
de l'indexeur (`_index_lock`, `_async_index_lock`), contrat central des
|
|
outils IA — `backend/tools/registry.py` couvre déjà permissions
|
|
(`requires_vault`, `require_destructive_allowed`), quotas
|
|
(`check_and_record` par outil) et redaction (`redact_payload`) pour les
|
|
35 outils enregistrés via `@tool(`.
|
|
|
|
## 3. Décisions assumées (non fait, et pourquoi)
|
|
|
|
- **Index non persisté sur disque.** Le rebuild différentiel (#86 : réutilise
|
|
les entrées inchangées `size` + `mtime`) rend le démarrage rapide ; un
|
|
snapshot introduirait des risques de staleness/drift de format sans gain
|
|
mesuré. Réévaluer si le démarrage devient lent (vaults 50k+ fichiers).
|
|
- **Redis exclu.** SQLite WAL couvre le multi-workers mono-hôte sans nouvelle
|
|
infra ; Redis reste l'option multi-nœuds documentée (cf. `ratelimit.py`).
|
|
- **`.gitignore` (`_*.py` ignore les `__init__.py`).** Contourné par
|
|
`git add -f` comme les packages existants ; assainir la règle à part.
|
|
- Noms en `_` conservés (`backend/render.py`, stores) : déplacement verbatim,
|
|
zéro churn d'appels.
|
|
|
|
## 4. Reste connu (hors #85)
|
|
|
|
- CSP `unsafe-inline` (migration nonce, BUG-034 partiel) et `Secure` cookies → #87.
|
|
- `main.py` (~750 lignes) : lifespan, middlewares, statique/SPA — cible
|
|
d'extraction ultérieure si besoin, non bloquant.
|