docling-studio/docs/audit/reports/release-0.5.0/05-dry.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

5.3 KiB

Rapport d'audit : DRY (Don't Repeat Yourself)

Release : 0.5.0 Date : 2026-04-28 Auditeur : claude-code


Score de compliance

Metrique Valeur
Items conformes 5 / 7
Score 71 / 100
Ecarts CRITICAL 0
Ecarts MAJOR 2
Ecarts MINOR 1
Ecarts INFO 2

Ecarts constates

[MAJ] Magic API URL hardcodé en frontend

  • Localisation : frontend/src/features/settings/store.ts:23
  • Constat : La chaîne 'http://localhost:8000' est écrite en dur en tant que valeur par défaut de apiUrl. Aucune centralization dans un fichier de configuration partagé.
  • Regle violee : Item 5.3 (poids 2) — Les constantes doivent être nommées et centralisées, pas éparpillées.
  • Remediation : Créer frontend/src/shared/config.ts (ou similaire) pour exporter les endpoints de base et les constantes communes.

[MAJ] Magic strings localStorage non centralisées en frontend

  • Localisation : frontend/src/features/settings/store.ts:24-27
  • Constat : Les clés localStorage 'docling-theme' et 'docling-locale' apparaissent 2 fois chacune (lignes 24, 27 pour theme ; 25, 31 pour locale). Ces chaînes ne sont pas extraites en constantes nommées.
  • Regle violee : Item 5.3 (poids 2) — Les magic strings doivent être nommées et centralisées.
  • Remediation : Créer des constantes nommées (e.g., STORAGE_KEY_THEME = 'docling-theme') dans frontend/src/shared/appConfig.ts ou un nouveau frontend/src/shared/constants.ts.

[MIN] Magic string "uploaded" non centralisé en backend

  • Localisation : document-parser/api/schemas.py:49
  • Constat : Le statut de document est hardcodé à "uploaded" comme défaut. Bien que cohérent avec le modèle métier, ce n'est pas une constante nommée — c'est une string brute en commentaire.
  • Regle violee : Item 5.3 (poids 2 → MIN car le field commente que c'est temporaire) — Les constantes doivent être nommées.
  • Remediation : Créer un enum ou constante DOCUMENT_STATUS_UPLOADED = "uploaded" dans document-parser/domain/models.py (déjà utilisé pour AnalysisStatus).

[INFO] Validation de table_mode et chunker_type répétée

  • Localisation : document-parser/api/schemas.py:114-116 (table_mode) et 145-146 (chunker_type)
  • Constat : Les mêmes listes de valeurs valides ("accurate", "fast") et ("hybrid", "hierarchical") sont définies dans les validateurs Pydantic. Ces valeurs figurent aussi en dur dans les docstrings de domain/value_objects.py (lignes 41, 67).
  • Regle violee : Item 5.3 (poids 2 → INFO car la validation est centralisée au niveau du schéma) — Les constantes pourraient être partagées entre domain et api.
  • Remediation : Créer un module document-parser/domain/constants.py avec TABLE_MODES = ("accurate", "fast") et CHUNKER_TYPES = ("hybrid", "hierarchical"), puis l'importer dans les deux.

[INFO] Logique de polling dupliquée entre features

  • Localisation : frontend/src/features/analysis/store.ts (ligne 67+) et frontend/src/features/ingestion/store.ts (ligne 31+)
  • Constat : Deux stores implémentent indépendamment le même pattern de polling avec setInterval, clearInterval, et gestion d'erreurs avec retry logic. Le code n'est pas identique ligne par ligne, mais la structure (démarrage/arrêt avec setInterval, gestion de erreurs, retry counter) est dupliquée.
  • Regle violee : Item 5.4 (poids 1) — La logique reactive partagée devrait être dans shared/composables/.
  • Remediation : Extraire un composable réutilisable (e.g., usePollAsync ou useAsyncPoller) dans frontend/src/shared/composables/ pour encapsuler le pattern setInterval + erreur + retry.

Points positifs

  • Centralisation réussie de la config HTTP : tous les appels apiFetch passent par frontend/src/shared/api/http.ts — aucun fetch() direct détecté.
  • Schemas Pydantic correctement séparés des modèles métier : api/schemas.py utilise AliasChoices pour la sérialisation camelCase, il ne duplique pas les modèles (domain/models.py reste pur).
  • Validation centralisée au niveau de l'API : les @field_validator de Pydantic sont le seul point d'entrée pour valider les options de pipeline et chunking.
  • Constantes d'état (AnalysisStatus enum) bien factorisées en domain/models.py — aucune duplication de "PENDING", "RUNNING", "COMPLETED", "FAILED".
  • Queries SQL factorisées : _SELECT_WITH_DOC réutilisée 3 fois dans persistence/analysis_repo.py.

Verdict partiel : GO CONDITIONNEL

Justification :

  • Score 71 / 100 (seuil: 80) → au-dessous de GO, mais aucun CRITICAL.
  • 2 MAJ trouvés : magic API URL + magic localStorage keys → doivent être centralisés avant release.
  • 1 MIN + 2 INFO pour amélioration future (pas bloquant immédiatement).

Conditions pour GO :

  1. Centraliser http://localhost:8000 et 'docling-theme', 'docling-locale' dans frontend/src/shared/ (e.g., config.ts ou constants.ts).
  2. Exécuter la re-vérification après correction pour atteindre ≥ 80.

Actions futures (prochain cycle) :

  • Créer document-parser/domain/constants.py pour les validations (table_mode, chunker_type).
  • Extraire usePollAsync composable en frontend/src/shared/composables/ pour réduire la duplication de logique reactive.