Files
ObsiGate/docs/features/archi-refonte-85.md
T

4.7 KiB

#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.