docling-studio/docs/audit/reports/release-0.5.0/summary.md
Pier-Jean Malandrino efc27932dd refactor(audit): remediate 0.5.0 audit findings — clean architecture, security, DRY, SOLID, perf
Closes the 12 MAJ raised by the release/0.5.0 audit pipeline (cf.
docs/audit/reports/release-0.5.0/summary.md → summary-reaudit.md).

Volet 1 — Reasoning architecture (audits 01/02/06/07 strengthening)
  * Domain ports: LLMProvider, ReasoningRunner, ReasoningParseError
  * Domain DTOs: LLMProviderType, ReasoningResult, ReasoningIteration
  * infra/llm/ollama_provider.py — OllamaProvider with health_check
  * infra/docling_agent_reasoning.py — runner adapter, encapsulates the
    private _rag_loop call (tracked at docling-project/docling-agent#26),
    commits OLLAMA_HOST once at boot (eliminates the per-request env race),
    translates upstream IndexError into ReasoningParseError
  * api/reasoning.py — zero coupling to docling-agent / mellea / docling-core,
    consumes app.state.reasoning_runner via the port
  * main.py — DI wires OllamaProvider + DoclingAgentReasoningRunner at boot
    when REASONING_ENABLED=true and deps are importable
  * Rename RAG_* env vars → REASONING_*, endpoint /rag → /reasoning,
    type RAGResult → ReasoningResult, frontend feature flag wiring,
    i18n strings, tests, docs (BREAKING — pre-1.0 surface, no external
    consumers in production)
  * 17 new tests: adapter unit tests with sys.modules stubs, OllamaProvider
    httpx tests, R3 concurrent-host isolation, R6 multi-iteration trace
    serialization, R13 Protocol conformance via isinstance
  * E2E Karate scenario: nav-reasoning hidden when REASONING_ENABLED=false
  * README — Live Reasoning section (env vars, archi, link to issue #26)

Bloc B — Security (audit 08, dev-only context)
  * docker-compose.yml — DEV DEFAULTS header, OpenSearch DISABLE_SECURITY_PLUGIN
    flagged as dev-only with link to OpenSearch security docs
  * main.py — boot warning if NEO4J_URI is set with the default 'changeme'
    password, so prod operators can't silently inherit it

Bloc C — DRY frontend (audit 05)
  * shared/storage/keys.ts — STORAGE_KEYS centralised (theme, locale)
  * features/settings/store.ts — dead apiUrl ref + orphan i18n keys removed
  * api/schemas.py — DOCUMENT_STATUS_UPLOADED constant

Bloc D — Quality (audits 02/06/07/09/10/12)
  * domain/ports.py — DocumentConverter.supports_page_batching property
    (LSP fix, replaces isinstance(ServeConverter) check)
  * domain/ports.py — VectorStore.ping() (encapsulation, replaces
    _vector_store._client.info() reach-around)
  * api/analyses.py + api/ingestion.py — path params {job_id} → {analysis_id}
    aligned with the user-facing terminology (URLs unchanged)
  * api/documents.py — Path.read_bytes() + generate_preview() wrapped in
    asyncio.to_thread, unblocks the FastAPI event loop on /preview
  * infra/docling_tree.py — PEP 604 union for isinstance (Ruff UP038)
  * src/__tests__/integration/ — cross-feature integration test relocated
    out of features/history/ so feature folders stay self-contained
  * Tightened terminal `assert X is not None` checks (isinstance(.., datetime),
    exact value comparisons)

Validation
  * 446 backend pytest, 202 frontend vitest — all green
  * ruff + ruff format + ESLint + Prettier + vue-tsc clean
  * Re-audit verdict: 0 CRIT / 0 MAJ, score ~94/100, GO

Closes #200
2026-04-29 14:00:00 +02:00

106 lines
6.1 KiB
Markdown

# Synthese d'audit — Release 0.5.0
**Date** : 2026-04-28
**Branche** : `release/0.5.0`
**Commit audite** : `b2e0af3`
**Auditeur** : claude-code
---
## Tableau de bord
| # | Audit | Score | CRIT | MAJ | MIN | INFO | Verdict |
|----|----------------------|---------|------|-----|-----|------|------------------|
| 01 | Clean Architecture | 100 | 0 | 0 | 0 | 0 | GO |
| 02 | DDD | 91 | 0 | 1 | 1 | 0 | GO |
| 03 | Clean Code | 83 | 0 | 0 | 3 | 0 | GO |
| 04 | KISS | 87.5 | 0 | 0 | 1 | 2 | GO |
| 05 | DRY | 71 | 0 | 2 | 1 | 2 | GO CONDITIONNEL |
| 06 | SOLID | 85 | 0 | 1 | 1 | 0 | GO |
| 07 | Decouplage | 86 | 0 | 1 | 1 | 0 | GO CONDITIONNEL |
| 08 | Securite | 91 | 0 | 2 | 0 | 1 | GO CONDITIONNEL |
| 09 | Tests | 94 | 0 | 1 | 0 | 0 | GO |
| 10 | CI / Build | 91 | 0 | 1 | 0 | 0 | GO CONDITIONNEL |
| 11 | Documentation | **44** | **1**| 2 | 0 | 0 | **NO-GO** |
| 12 | Performance | 86.67 | 0 | 1 | 1 | 1 | GO CONDITIONNEL |
**Score global (moyenne simple)** : **84.2 / 100**
**Ecarts CRITICAL totaux** : **1**
**Ecarts MAJOR totaux** : **12**
**Ecarts MINOR totaux** : 8
**Ecarts INFO totaux** : 6
---
## Ecarts CRITICAL (tous audits confondus)
1. **[11] CHANGELOG.md sans section `[0.5.0]`** — `CHANGELOG.md:7`
La derniere section listee est `## [0.4.0] - 2026-04-13`. Aucune entree `[Unreleased]` ni `[0.5.0]`. Tagger 0.5.0 depuis ce HEAD livrerait un changelog mensonger qui omet les nouveautes de la release (reasoning-trace viewer, Neo4j graph storage, RAG endpoints, feature flags). **Bloquant absolu** par regle master §3.
---
## Top blockers (poids 3 / poids 2)
### Bloquant (poids 3)
- **[11] CHANGELOG sans section 0.5.0** — `CHANGELOG.md:7`
Ajouter `## [0.5.0] - 2026-04-28` avec sous-sections `Added` / `Changed` / `Fixed` listant les changements depuis 0.4.0.
### Majeurs (poids 2) — a remediar pour passer a GO
- **[11] `frontend/package.json` toujours a `0.4.0`** — `frontend/package.json:3`
Bumper a `"version": "0.5.0"`.
- **[11] Modifications de la release 0.5.0 non documentees** — `CHANGELOG.md`
Resolu en meme temps que le CRIT.
- **[08] Mot de passe Neo4j par defaut `"changeme"`** — `document-parser/infra/settings.py:30,133`
Forcer la lecture d'une variable d'environnement, supprimer le defaut, faire echouer le boot si absent en prod.
- **[08] OpenSearch security plugin desactive** — `docker-compose.yml:26`
`DISABLE_SECURITY_PLUGIN=true` doit etre off pour tout deploiement non-dev ; documenter explicitement le perimetre.
- **[05] URL d'API hardcodee en frontend** — `frontend/src/features/settings/store.ts:23`
Centraliser dans une constante `API_BASE_URL` lue depuis `import.meta.env`.
- **[05] Cles localStorage en clair, dispersees** — `frontend/src/features/settings/store.ts:24-27`
Centraliser dans une enum/objet `STORAGE_KEYS`.
- **[02] Ubiquitous language "job" vs "analysis"** — `document-parser/api/ingestion.py:49-67`
Aligner le vocabulaire HTTP sur le langage du domaine.
- **[06] LSP — `isinstance()` sur adaptateur** — `document-parser/services/analysis_service.py:356`
Supprimer le check de type concret ou exposer la capacite via le port.
- **[07] Couplage inter-feature dans les tests** — `frontend/src/features/history/navigation.test.ts:30-31,67,82-83,143-144,169-170,188`
Stubber les stores via injection au lieu d'imports directs.
- **[09] Assertions vagues `assert X is not None`** — 18 occurrences en backend
Ajouter une assertion sur la valeur attendue, pas seulement sur la presence.
- **[10] Ruff UP038 — syntaxe `isinstance()` union** — `document-parser/infra/docling_tree.py:101`
Appliquer la suggestion pyupgrade.
- **[12] I/O synchrone dans endpoint async** — `document-parser/api/documents.py:115-116`
Wrapper l'acces fichier dans `asyncio.to_thread(...)` pour ne pas bloquer la loop FastAPI.
---
## Quick wins (poids 1 — ameliorations rapides)
- **[03] Trois fichiers Vue > 300 lignes** — `frontend/src/views/StudioPage.vue` (~1450L), `frontend/src/.../ChunkPanel.vue` (~801L), `frontend/src/.../ResultTabs.vue` (~690L). Extraire des sous-composants.
- **[03] Fonctions > 30 lignes** (3 cas) et **fonctions > 4 parametres** (4 cas). Decoupage local.
- **[04] Wrapper trivial `_to_response`** — utiliser `Pydantic.model_validate()` directement.
- **[04] Accessors redondants dans `DocumentService`**.
- **[05] Magic string isolee** — `document-parser/api/schemas.py:49`.
- **[12] Polling avec setInterval/setTimeout imbriques** — `frontend/src/features/settings/store.ts:27-39` et store analysis.
---
## Verdict final : **NO-GO**
**Justification** : 1 ecart CRITICAL non resolu dans l'audit 11 (Documentation) → regle absolue du master.md §3 : `tout ecart [CRIT] non resolu = NO-GO quel que soit le score`.
Bien que le score global (84.2/100) soit confortablement au-dessus du seuil GO et que 11 audits sur 12 soient au moins en GO CONDITIONNEL, le release 0.5.0 **ne peut pas etre tague** dans cet etat.
### Conditions pour passer a GO
**Bloquant absolu** (1 action) :
1. Ajouter la section `## [0.5.0] - 2026-04-28` dans `CHANGELOG.md` avec `Added` / `Changed` / `Fixed`.
**Pour passer de GO CONDITIONNEL a GO** (4 actions, ~30 min) :
2. Bumper `frontend/package.json` a `0.5.0`.
3. Remplacer `"changeme"` par lecture d'env var obligatoire (`document-parser/infra/settings.py`).
4. Documenter / corriger la desactivation du security plugin OpenSearch (`docker-compose.yml`).
5. Centraliser l'URL d'API et les cles localStorage du frontend (`frontend/src/features/settings/store.ts`).
**Recommandation** : apres remediation des 5 points ci-dessus, re-auditer **uniquement** les audits 11, 08 et 05 (commande : `Re-audite uniquement les ecarts CRITICAL et MAJOR du rapport docs/audit/reports/release-0.5.0/summary.md`). Les autres MAJ peuvent etre planifies pour 0.5.1 sans bloquer le tag.