Spaces:
Running
chore(versioning): reposition project as 0.9.0 (pre-1.0)
Browse filesSprint S0 — align version numbering with actual functional maturity.
What changed:
- pyproject.toml: fallback_version 1.0.0 → 0.9.0
- New single source of truth: picarones/domain/_version_fallback.py
(placed in domain layer to be importable from all outer layers
without violating dependency rules)
- Refactor 4 hardcoded fallbacks (init/evaluation/reports/pyproject)
to read from the single source — fixes pre-existing inconsistency
where __init__.py said "2.0.0" while others said "1.0.0"
- CHANGELOG: rename [2.0.0] entry to [0.9.0] with historical note;
add repositioning note + roadmap to 1.0 in header
- CLAUDE.md, README.md, architecture.md, api-stable.md, specification.md,
release-process.md, rollback.md, index.md, threat-model.md,
reproducibility-snapshots.md, backlog.md: align active references
- docs/archive/README.md: add preliminary note explaining the
"v2.0" → "0.9.0" mapping; archive content left intact (history)
- New docs/explanation/versioning.md: full SemVer pre-1.0 policy
- New tests/golden/fixtures/benchmark_result_v2.json: use
"<CURRENT_VERSION>" placeholder; test substitutes the same string
→ snapshot decoupled from version bumps
- Refactored test_doc_truthfulness.py to verify invariants
(no legacy packages, 8 layers documented) rather than mentions of
a specific version
Anti-drift guard-rails (3 new tests):
- test_single_version_source.py: verifies pyproject ↔ FALLBACK_VERSION
alignment, PEP 440 format, no other module defines a literal version
- test_no_hardcoded_version.py: scans active code/docs for hardcoded
Picarones versions outside whitelist (contextual patterns:
"Picarones X.Y.Z", "picarones==X.Y.Z", "Release X.Y.Z", "vX.Y.Z"
isolated — avoids WCAG/Pydantic/Python false positives)
- test_version_propagation.py: end-to-end check that __version__
appears in HTML report, JSON, CLI info, /api/status
Verification:
- 5156 tests passed, 0 failed, 20 skipped
- make lint: All checks passed
- picarones --version, picarones info, demo HTML all show the
current resolved version (0.10.0.dev277 without git tag, would
become 0.9.0 once v0.9.0 tag is posted)
No git tag posted in this sprint — D6.b: tag is the operator's
explicit gesture to trigger PyPI/Docker/GitHub Release.
https://claude.ai/code/session_01WYDbfkhKPeBZ15BTP4e9Ye
- CHANGELOG.md +54 -1
- CLAUDE.md +29 -9
- README.md +18 -15
- docs/archive/README.md +29 -8
- docs/explanation/architecture.md +12 -11
- docs/explanation/versioning.md +137 -0
- docs/index.md +3 -3
- docs/operations/release-process.md +15 -11
- docs/operations/rollback.md +14 -13
- docs/reference/api-stable.md +17 -7
- docs/reference/reproducibility-snapshots.md +1 -1
- docs/reference/specification.md +36 -20
- docs/roadmap/backlog.md +2 -2
- docs/security/threat-model.md +2 -2
- picarones/__init__.py +6 -4
- picarones/domain/_version_fallback.py +37 -0
- picarones/evaluation/benchmark_result.py +6 -5
- picarones/reports/html/snapshot.py +6 -2
- pyproject.toml +18 -12
- tests/architecture/test_doc_governance.py +3 -2
- tests/architecture/test_doc_paths.py +7 -1
- tests/architecture/test_doc_truthfulness.py +33 -27
- tests/architecture/test_no_hardcoded_version.py +237 -0
- tests/architecture/test_no_sprint_narrative_in_code.py +11 -5
- tests/architecture/test_single_version_source.py +123 -0
- tests/docs/test_readme_dual_lang.py +12 -6
- tests/golden/fixtures/benchmark_result_v2.json +1 -1
- tests/golden/test_benchmark_result_json_stable.py +6 -1
- tests/integration/test_version_propagation.py +130 -0
- tests/security/test_phase1_post_rewrite_wiring.py +1 -1
|
@@ -5,6 +5,52 @@ Tous les changements notables de ce projet sont documentés dans ce fichier.
|
|
| 5 |
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/).
|
| 6 |
La numérotation de version suit [Semantic Versioning](https://semver.org/lang/fr/).
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
## [Unreleased] — Migration Option B vers RunOrchestrator (mai 2026)
|
|
@@ -454,7 +500,14 @@ et round-trip JSON appauvri.
|
|
| 454 |
|
| 455 |
---
|
| 456 |
|
| 457 |
-
## [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
|
| 459 |
**Breaking changes** majeurs : suppression complète des paquets
|
| 460 |
legacy. L'architecture canonique 8 couches (`domain → formats →
|
|
|
|
| 5 |
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/).
|
| 6 |
La numérotation de version suit [Semantic Versioning](https://semver.org/lang/fr/).
|
| 7 |
|
| 8 |
+
Politique de versionning : voir
|
| 9 |
+
[`docs/explanation/versioning.md`](docs/explanation/versioning.md).
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
## Note de repositionnement (2026-05-23)
|
| 14 |
+
|
| 15 |
+
Le projet a été repositionné en **SemVer pré-1.0** à cette date. La
|
| 16 |
+
release précédemment dénommée **« 2.0.0 »** (clôture du rewrite
|
| 17 |
+
architectural en 8 couches, mai 2026) est désormais référencée comme
|
| 18 |
+
**`0.9.0`** dans ce changelog.
|
| 19 |
+
|
| 20 |
+
Justification : à la clôture du rewrite, le projet n'avait ni
|
| 21 |
+
interface utilisateur finalisée, ni parité importeurs, ni rapport
|
| 22 |
+
refondu — la dénomination « 2.0 » reflétait un cycle de refactor
|
| 23 |
+
interne, pas une release publique mûre. La sortie de `1.0.0`
|
| 24 |
+
deviendra un événement public marquant à la livraison de ces
|
| 25 |
+
chantiers.
|
| 26 |
+
|
| 27 |
+
Les sections historiques de ce fichier (et de la doc archive) qui
|
| 28 |
+
citent « v2.0 » ou « v1.x » dans leur **prose** n'ont pas été
|
| 29 |
+
réécrites : modifier le passé pour faire coïncider la terminologie
|
| 30 |
+
avec le présent serait une réécriture d'historique trompeuse. Lis
|
| 31 |
+
« v2.0 » dans ces sections comme « l'état du code à la clôture du
|
| 32 |
+
rewrite, devenu `0.9.0` au repositionnement ».
|
| 33 |
+
|
| 34 |
+
Seuls les **titres de version** (`## [X.Y.Z]`) ont été réalignés pour
|
| 35 |
+
que les tags git et les releases PyPI futures restent cohérents avec
|
| 36 |
+
le numéro courant.
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Roadmap vers 1.0.0
|
| 41 |
+
|
| 42 |
+
La sortie de `1.0.0` est conditionnée à la livraison de :
|
| 43 |
+
|
| 44 |
+
1. **Surface UI complète** — exposition de tous les champs du modèle
|
| 45 |
+
`BenchmarkRunRequest`, parité fonctionnelle avec la CLI
|
| 46 |
+
(`compare`, `robustness`, `history`).
|
| 47 |
+
2. **Parité importeurs corpus** — IIIF, Gallica, eScriptorium accessibles
|
| 48 |
+
depuis l'UI web.
|
| 49 |
+
3. **Refonte rapport HTML** — IA 4 onglets (Overview / Engines / Documents / Crosses).
|
| 50 |
+
|
| 51 |
+
Les versions intermédiaires `0.10.0`, `0.11.0`, etc. publient des
|
| 52 |
+
jalons techniques au fil du chantier.
|
| 53 |
+
|
| 54 |
---
|
| 55 |
|
| 56 |
## [Unreleased] — Migration Option B vers RunOrchestrator (mai 2026)
|
|
|
|
| 500 |
|
| 501 |
---
|
| 502 |
|
| 503 |
+
## [0.9.0] — Legacy retirement complete (mai 2026)
|
| 504 |
+
|
| 505 |
+
> Cette entrée porte le numéro de version dénommé **« 2.0.0 »**
|
| 506 |
+
> jusqu'au 2026-05-23, repositionné en `0.9.0` dans le cadre de
|
| 507 |
+
> l'alignement SemVer pré-1.0 (voir « Note de repositionnement » en
|
| 508 |
+
> tête de fichier). Le contenu n'a pas été réécrit : la prose
|
| 509 |
+
> mentionne « v2.0 » et « 1.x » telles qu'elles étaient employées à
|
| 510 |
+
> l'époque.
|
| 511 |
|
| 512 |
**Breaking changes** majeurs : suppression complète des paquets
|
| 513 |
legacy. L'architecture canonique 8 couches (`domain → formats →
|
|
@@ -4,6 +4,9 @@ Plateforme de benchmark OCR/HTR pour documents patrimoniaux.
|
|
| 4 |
Repo : github.com/maribakulj/Picarones
|
| 5 |
HuggingFace Space : huggingface.co/spaces/Ma-Ri-Ba-Ku/Picarones (Docker, port 7860)
|
| 6 |
|
|
|
|
|
|
|
|
|
|
| 7 |
---
|
| 8 |
|
| 9 |
## Architecture — 8 couches concentriques
|
|
@@ -17,12 +20,13 @@ domain → formats → evaluation → pipeline → adapters → app → reports
|
|
| 17 |
Règle d'import stricte : les dépendances vont uniquement de l'extérieur
|
| 18 |
vers l'intérieur. Vérifié par `tests/architecture/test_layer_dependencies.py`
|
| 19 |
+ `tests/architecture/test_no_legacy_imports_in_rewrite.py`
|
| 20 |
-
(`LEGACY_PACKAGES = ()`
|
| 21 |
|
| 22 |
Tous les paquets legacy (`picarones/{core,measurements,engines,modules,
|
| 23 |
report,llm,pipelines,cli,web,extras}/` + `adapters/legacy_engines/` +
|
| 24 |
`adapters/legacy_pipelines/` + `interfaces/{cli,web}/_legacy/`) ont été
|
| 25 |
-
**supprimés** au cours des sprints A-H (mai 2026
|
|
|
|
| 26 |
|
| 27 |
---
|
| 28 |
|
|
@@ -212,11 +216,14 @@ labels d'affichage (i18n), pas des identifiants API.
|
|
| 212 |
|
| 213 |
L'historique détaillé des **97+ sprints** du projet (de la fondation
|
| 214 |
S1 jusqu'au rewrite ciblé S27-S46, l'audit institutionnel S47-S59,
|
| 215 |
-
puis le retrait complet du legacy A-H
|
| 216 |
-
CHANGELOG.md à la racine.
|
| 217 |
|
| 218 |
-
**
|
| 219 |
-
canoniques est terminée. Plus aucun paquet legacy. Plus
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
**Chantier post-rewrite (mai 2026, branche `claude/fix-module-rewiring-MHssX`)** :
|
| 222 |
réconciliation des chemins UI/API/runner après audit révélant des
|
|
@@ -243,7 +250,9 @@ exécutées :
|
|
| 243 |
automatiquement.
|
| 244 |
- **Phase 5 (naming)** : `CompetitorConfig` → `PipelineConfig`,
|
| 245 |
`ocr_engine` → `engine_name` (rupture API, le field accepte aussi
|
| 246 |
-
des VLMs et `corpus`).
|
|
|
|
|
|
|
| 247 |
|
| 248 |
**Règles d'architecture** :
|
| 249 |
|
|
@@ -321,7 +330,7 @@ détecte, arbitre, rend.
|
|
| 321 |
- **Manifeste architecture** : [`docs/explanation/architecture.md`](docs/explanation/architecture.md).
|
| 322 |
- **API publique stable** : [`docs/reference/api-stable.md`](docs/reference/api-stable.md).
|
| 323 |
|
| 324 |
-
### Statut
|
| 325 |
|
| 326 |
L'architecture 8 couches est complète. Tous les paquets legacy
|
| 327 |
top-level (`core/`, `measurements/`, `engines/`, `modules/`,
|
|
@@ -335,4 +344,15 @@ que les sous-paquets transitoires (`adapters/legacy_engines/`,
|
|
| 335 |
| Foundation S1-S26 | ✅ | domain, formats, evaluation, narrative engine |
|
| 336 |
| Rewrite ciblé S27-S46 | ✅ | pipeline, app.services, adapters/ocr canonique, reports |
|
| 337 |
| Audit S47-S59 | ✅ | confidences, sécurité web, registry typé, baselines |
|
| 338 |
-
| Plan A-H (mai 2026) | ✅ | Retrait complet du legacy : core/measurements/engines/modules/report/llm/pipelines/cli/web/extras supprimés ; interfaces/{cli,web}/_legacy promus au niveau canonique ;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
Repo : github.com/maribakulj/Picarones
|
| 5 |
HuggingFace Space : huggingface.co/spaces/Ma-Ri-Ba-Ku/Picarones (Docker, port 7860)
|
| 6 |
|
| 7 |
+
**Version courante** : `0.9.0` (pré-1.0). Politique de versionning :
|
| 8 |
+
[`docs/explanation/versioning.md`](docs/explanation/versioning.md).
|
| 9 |
+
|
| 10 |
---
|
| 11 |
|
| 12 |
## Architecture — 8 couches concentriques
|
|
|
|
| 20 |
Règle d'import stricte : les dépendances vont uniquement de l'extérieur
|
| 21 |
vers l'intérieur. Vérifié par `tests/architecture/test_layer_dependencies.py`
|
| 22 |
+ `tests/architecture/test_no_legacy_imports_in_rewrite.py`
|
| 23 |
+
(`LEGACY_PACKAGES = ()` — plus aucun paquet legacy).
|
| 24 |
|
| 25 |
Tous les paquets legacy (`picarones/{core,measurements,engines,modules,
|
| 26 |
report,llm,pipelines,cli,web,extras}/` + `adapters/legacy_engines/` +
|
| 27 |
`adapters/legacy_pipelines/` + `interfaces/{cli,web}/_legacy/`) ont été
|
| 28 |
+
**supprimés** au cours des sprints A-H (mai 2026, clôture du rewrite
|
| 29 |
+
architectural — release `0.9.0`).
|
| 30 |
|
| 31 |
---
|
| 32 |
|
|
|
|
| 216 |
|
| 217 |
L'historique détaillé des **97+ sprints** du projet (de la fondation
|
| 218 |
S1 jusqu'au rewrite ciblé S27-S46, l'audit institutionnel S47-S59,
|
| 219 |
+
puis le retrait complet du legacy A-H aboutissant à `0.9.0`) est
|
| 220 |
+
dans le CHANGELOG.md à la racine.
|
| 221 |
|
| 222 |
+
**Release `0.9.0` (mai 2026)** : la migration vers l'architecture
|
| 223 |
+
8 couches canoniques est terminée. Plus aucun paquet legacy. Plus
|
| 224 |
+
aucun shim. Cette release portait en interne la dénomination « v2.0 »
|
| 225 |
+
jusqu'au repositionnement en SemVer pré-1.0 (voir
|
| 226 |
+
[`docs/explanation/versioning.md`](docs/explanation/versioning.md)).
|
| 227 |
|
| 228 |
**Chantier post-rewrite (mai 2026, branche `claude/fix-module-rewiring-MHssX`)** :
|
| 229 |
réconciliation des chemins UI/API/runner après audit révélant des
|
|
|
|
| 250 |
automatiquement.
|
| 251 |
- **Phase 5 (naming)** : `CompetitorConfig` → `PipelineConfig`,
|
| 252 |
`ocr_engine` → `engine_name` (rupture API, le field accepte aussi
|
| 253 |
+
des VLMs et `corpus`). Le worker `run_benchmark_thread_v2` propage
|
| 254 |
+
les nouveaux champs (le suffixe `_v2` est un nom de fonction
|
| 255 |
+
interne hérité, sans rapport avec la dénomination de version).
|
| 256 |
|
| 257 |
**Règles d'architecture** :
|
| 258 |
|
|
|
|
| 330 |
- **Manifeste architecture** : [`docs/explanation/architecture.md`](docs/explanation/architecture.md).
|
| 331 |
- **API publique stable** : [`docs/reference/api-stable.md`](docs/reference/api-stable.md).
|
| 332 |
|
| 333 |
+
### Statut `0.9.0` — mai 2026
|
| 334 |
|
| 335 |
L'architecture 8 couches est complète. Tous les paquets legacy
|
| 336 |
top-level (`core/`, `measurements/`, `engines/`, `modules/`,
|
|
|
|
| 344 |
| Foundation S1-S26 | ✅ | domain, formats, evaluation, narrative engine |
|
| 345 |
| Rewrite ciblé S27-S46 | ✅ | pipeline, app.services, adapters/ocr canonique, reports |
|
| 346 |
| Audit S47-S59 | ✅ | confidences, sécurité web, registry typé, baselines |
|
| 347 |
+
| Plan A-H (mai 2026) | ✅ | Retrait complet du legacy : core/measurements/engines/modules/report/llm/pipelines/cli/web/extras supprimés ; interfaces/{cli,web}/_legacy promus au niveau canonique ; release `0.9.0` (cycle de rewrite clôturé) |
|
| 348 |
+
|
| 349 |
+
### Roadmap vers 1.0.0
|
| 350 |
+
|
| 351 |
+
La sortie de `1.0.0` est conditionnée à la livraison de :
|
| 352 |
+
|
| 353 |
+
1. **Surface UI complète** — exposition de tous les champs `BenchmarkRunRequest`,
|
| 354 |
+
parité fonctionnelle avec la CLI (`compare`, `robustness`, `history`).
|
| 355 |
+
2. **Parité importeurs corpus** — IIIF, Gallica, eScriptorium accessibles en web.
|
| 356 |
+
3. **Refonte rapport HTML** — IA 4 onglets (Overview / Engines / Documents / Crosses).
|
| 357 |
+
|
| 358 |
+
Releases intermédiaires `0.10.0`, `0.11.0`, … publient des jalons techniques.
|
|
@@ -13,13 +13,13 @@ pinned: false
|
|
| 13 |
>
|
| 14 |
> **Outil de comparaison d'OCR / HTR / VLM et de post-correction pour documents patrimoniaux**
|
| 15 |
|
| 16 |
-
**Status (May 2026)** — version 1.
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
[
|
| 21 |
-
|
| 22 |
-
|
| 23 |
|
| 24 |
[](https://github.com/maribakulj/Picarones/actions/workflows/ci.yml) [](https://codecov.io/gh/maribakulj/Picarones)
|
| 25 |
[](https://www.python.org/downloads/)
|
|
@@ -333,12 +333,13 @@ picarones/
|
|
| 333 |
```
|
| 334 |
|
| 335 |
Strict 8-layer architecture: imports flow outer → inner. Enforced
|
| 336 |
-
by `tests/architecture/test_layer_dependencies.py`.
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
`
|
| 340 |
-
|
| 341 |
-
`
|
|
|
|
| 342 |
[`docs/explanation/architecture.md`](docs/explanation/architecture.md)
|
| 343 |
for the full manifesto and migration history under
|
| 344 |
`docs/archive/2026-migration/`.
|
|
@@ -439,9 +440,11 @@ Detailed history and current direction live in:
|
|
| 439 |
- [`docs/roadmap/backlog.md`](docs/roadmap/backlog.md) — live backlog
|
| 440 |
for current work.
|
| 441 |
- [`docs/archive/`](docs/archive/) — historical artefacts:
|
| 442 |
-
pre-
|
| 443 |
(`2026-audits/`), rewrite/migration plans (`2026-migration/`),
|
| 444 |
-
pre-
|
|
|
|
|
|
|
| 445 |
|
| 446 |
**Honest status (May 2026).** Several items historically presented as
|
| 447 |
"institutional readiness complete" are not at the level the README
|
|
|
|
| 13 |
>
|
| 14 |
> **Outil de comparaison d'OCR / HTR / VLM et de post-correction pour documents patrimoniaux**
|
| 15 |
|
| 16 |
+
**Status (May 2026)** — version **`0.9.0`** (pre-1.0, see
|
| 17 |
+
[versioning policy](docs/explanation/versioning.md)). Architecture
|
| 18 |
+
stable, functional surface under construction. The core (corpus,
|
| 19 |
+
runner, metrics, HTML report) is usable on a ground-truth corpus.
|
| 20 |
+
The [architectural rewrite](docs/archive/2026-roadmap/rewrite.md) is
|
| 21 |
+
**complete** ; remaining work towards `1.0.0` covers the web UI, the
|
| 22 |
+
HTML report refactor, and corpus importer parity.
|
| 23 |
|
| 24 |
[](https://github.com/maribakulj/Picarones/actions/workflows/ci.yml) [](https://codecov.io/gh/maribakulj/Picarones)
|
| 25 |
[](https://www.python.org/downloads/)
|
|
|
|
| 333 |
```
|
| 334 |
|
| 335 |
Strict 8-layer architecture: imports flow outer → inner. Enforced
|
| 336 |
+
by `tests/architecture/test_layer_dependencies.py`. Release `0.9.0`
|
| 337 |
+
(May 2026) marks the end of the architectural rewrite cycle: all
|
| 338 |
+
legacy top-level packages (`core/`, `measurements/`, `engines/`,
|
| 339 |
+
`llm/`, `pipelines/`, `report/`, `modules/`, `cli/`, `web/`,
|
| 340 |
+
`extras/`) and transitional sub-packages
|
| 341 |
+
(`adapters/legacy_engines/`, `adapters/legacy_pipelines/`,
|
| 342 |
+
`interfaces/{cli,web}/_legacy/`) have been removed. See
|
| 343 |
[`docs/explanation/architecture.md`](docs/explanation/architecture.md)
|
| 344 |
for the full manifesto and migration history under
|
| 345 |
`docs/archive/2026-migration/`.
|
|
|
|
| 440 |
- [`docs/roadmap/backlog.md`](docs/roadmap/backlog.md) — live backlog
|
| 441 |
for current work.
|
| 442 |
- [`docs/archive/`](docs/archive/) — historical artefacts:
|
| 443 |
+
pre-rewrite roadmap (`2026-roadmap/`), institutional audits
|
| 444 |
(`2026-audits/`), rewrite/migration plans (`2026-migration/`),
|
| 445 |
+
pre-rewrite changelog (`changelog-pre-v2.md`). The archived
|
| 446 |
+
documents use the internal `v2.0` codename for the rewrite that
|
| 447 |
+
was repositioned as `0.9.0` — see [`docs/archive/README.md`](docs/archive/README.md).
|
| 448 |
|
| 449 |
**Honest status (May 2026).** Several items historically presented as
|
| 450 |
"institutional readiness complete" are not at the level the README
|
|
@@ -2,21 +2,42 @@
|
|
| 2 |
|
| 3 |
> **Archived document.** Cette zone contient des documents
|
| 4 |
> **historiques** : audits, plans de migration, roadmaps obsolètes,
|
| 5 |
-
> changelogs antérieurs
|
| 6 |
-
> traçabilité et la mémoire institutionnelle du
|
| 7 |
-
> **pas** partie de la documentation active.
|
| 8 |
>
|
| 9 |
> Pour la documentation à jour, voir [`docs/index.md`](../index.md).
|
| 10 |
|
| 11 |
---
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
## Pourquoi cette séparation
|
| 14 |
|
| 15 |
-
À
|
| 16 |
-
legacy. Les artefacts qui décrivaient ces
|
| 17 |
-
session, plans de retrait, audits
|
| 18 |
-
restent utiles pour comprendre
|
| 19 |
-
ils ne décrivent pas l'état
|
|
|
|
| 20 |
|
| 21 |
Les laisser dans la documentation active créait deux confusions :
|
| 22 |
|
|
|
|
| 2 |
|
| 3 |
> **Archived document.** Cette zone contient des documents
|
| 4 |
> **historiques** : audits, plans de migration, roadmaps obsolètes,
|
| 5 |
+
> changelogs antérieurs au rewrite architectural complet. Ils sont
|
| 6 |
+
> conservés pour la traçabilité et la mémoire institutionnelle du
|
| 7 |
+
> projet, mais ne font **pas** partie de la documentation active.
|
| 8 |
>
|
| 9 |
> Pour la documentation à jour, voir [`docs/index.md`](../index.md).
|
| 10 |
|
| 11 |
---
|
| 12 |
|
| 13 |
+
## Note de repositionnement (2026-05-23)
|
| 14 |
+
|
| 15 |
+
Les documents archivés ici utilisent la dénomination interne
|
| 16 |
+
**« v2.0 »** pour désigner l'aboutissement du rewrite architectural
|
| 17 |
+
en 8 couches (mai 2026). Cette dénomination, qui reflétait une
|
| 18 |
+
logique de refactor interne plutôt qu'une release publique mûre, a
|
| 19 |
+
été **repositionnée en `0.9.0`** lors de l'alignement du projet sur
|
| 20 |
+
SemVer pré-1.0.
|
| 21 |
+
|
| 22 |
+
**Le contenu de l'archive n'a volontairement pas été réécrit** :
|
| 23 |
+
modifier le passé pour faire coïncider la terminologie avec le
|
| 24 |
+
présent serait une réécriture d'historique trompeuse. Lis donc
|
| 25 |
+
« v2.0 » dans les fichiers archivés comme « l'état du code à la
|
| 26 |
+
clôture du rewrite, devenu `0.9.0` au repositionnement ».
|
| 27 |
+
|
| 28 |
+
Politique de versionning actuelle : voir
|
| 29 |
+
[`docs/explanation/versioning.md`](../explanation/versioning.md).
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
## Pourquoi cette séparation
|
| 34 |
|
| 35 |
+
À l'aboutissement du rewrite architectural (mai 2026), le projet a
|
| 36 |
+
clôturé sa migration legacy. Les artefacts qui décrivaient ces
|
| 37 |
+
chantiers (handovers de session, plans de retrait, audits
|
| 38 |
+
institutionnels, status reports) restent utiles pour comprendre
|
| 39 |
+
**comment** le code a évolué, mais ils ne décrivent pas l'état
|
| 40 |
+
actuel.
|
| 41 |
|
| 42 |
Les laisser dans la documentation active créait deux confusions :
|
| 43 |
|
|
@@ -5,17 +5,18 @@
|
|
| 5 |
> sont les fichiers*. Pour la liste exhaustive des modules, lire
|
| 6 |
> directement le code — il est typé et documenté.
|
| 7 |
|
| 8 |
-
## Statut
|
| 9 |
|
| 10 |
-
À
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
| 15 |
|
| 16 |
Toute documentation, tout commentaire qui mentionne « deux
|
| 17 |
arborescences » ou « legacy en cours de retrait » est obsolète.
|
| 18 |
-
La seule cohabitation acceptable
|
| 19 |
**modules canoniques** : par exemple `evaluation/metric_registry`
|
| 20 |
(module-level, side-effect d'import) et `evaluation/registry/registry`
|
| 21 |
(instance-based) — deux patterns volontairement coexistants pour
|
|
@@ -200,9 +201,9 @@ on choisit explicitement entre :
|
|
| 200 |
- **Shim avec `DeprecationWarning`** (pour la stabilité d'API publique).
|
| 201 |
Le shim a une date de retrait inscrite dans le CHANGELOG.
|
| 202 |
|
| 203 |
-
À
|
| 204 |
`picarones/pipeline/spec.py` (réexporte `picarones.domain.pipeline_spec`),
|
| 205 |
-
dont la deprecation period expire
|
| 206 |
|
| 207 |
### Pas d'`except Exception: pass`
|
| 208 |
|
|
@@ -249,9 +250,9 @@ publication scientifique.
|
|
| 249 |
L'évolution de l'architecture est documentée :
|
| 250 |
|
| 251 |
- Backlog vivant : [`docs/roadmap/backlog.md`](../roadmap/backlog.md)
|
| 252 |
-
- Plans archivés (migration legacy → rewrite, terminée à
|
| 253 |
[`docs/archive/2026-migration/`](../archive/2026-migration/)
|
| 254 |
-
- Roadmap historique pré-
|
| 255 |
[`docs/archive/2026-roadmap/`](../archive/2026-roadmap/)
|
| 256 |
- Audits institutionnels : [`docs/archive/2026-audits/`](../archive/2026-audits/)
|
| 257 |
- Politique d'API publique : [`docs/reference/api-stable.md`](../reference/api-stable.md)
|
|
|
|
| 5 |
> sont les fichiers*. Pour la liste exhaustive des modules, lire
|
| 6 |
> directement le code — il est typé et documenté.
|
| 7 |
|
| 8 |
+
## Statut `0.9.0` — une seule arborescence canonique
|
| 9 |
|
| 10 |
+
À la release `0.9.0` (mai 2026, clôture du rewrite architectural),
|
| 11 |
+
Picarones a **une seule arborescence**. Tous les paquets
|
| 12 |
+
pré-rewrite ainsi que leurs sous-paquets transitoires ont été
|
| 13 |
+
supprimés au cours des sprints A-H. Pour le détail historique,
|
| 14 |
+
voir le [CHANGELOG section 0.9.0](../../CHANGELOG.md) et
|
| 15 |
+
[`docs/archive/2026-migration/`](../archive/2026-migration/).
|
| 16 |
|
| 17 |
Toute documentation, tout commentaire qui mentionne « deux
|
| 18 |
arborescences » ou « legacy en cours de retrait » est obsolète.
|
| 19 |
+
La seule cohabitation acceptable depuis `0.9.0` est celle entre
|
| 20 |
**modules canoniques** : par exemple `evaluation/metric_registry`
|
| 21 |
(module-level, side-effect d'import) et `evaluation/registry/registry`
|
| 22 |
(instance-based) — deux patterns volontairement coexistants pour
|
|
|
|
| 201 |
- **Shim avec `DeprecationWarning`** (pour la stabilité d'API publique).
|
| 202 |
Le shim a une date de retrait inscrite dans le CHANGELOG.
|
| 203 |
|
| 204 |
+
À `0.9.0` il reste **un seul shim** documenté :
|
| 205 |
`picarones/pipeline/spec.py` (réexporte `picarones.domain.pipeline_spec`),
|
| 206 |
+
dont la deprecation period expire à la prochaine minor `0.10.0`.
|
| 207 |
|
| 208 |
### Pas d'`except Exception: pass`
|
| 209 |
|
|
|
|
| 250 |
L'évolution de l'architecture est documentée :
|
| 251 |
|
| 252 |
- Backlog vivant : [`docs/roadmap/backlog.md`](../roadmap/backlog.md)
|
| 253 |
+
- Plans archivés (migration legacy → rewrite, terminée à `0.9.0`) :
|
| 254 |
[`docs/archive/2026-migration/`](../archive/2026-migration/)
|
| 255 |
+
- Roadmap historique pré-rewrite :
|
| 256 |
[`docs/archive/2026-roadmap/`](../archive/2026-roadmap/)
|
| 257 |
- Audits institutionnels : [`docs/archive/2026-audits/`](../archive/2026-audits/)
|
| 258 |
- Politique d'API publique : [`docs/reference/api-stable.md`](../reference/api-stable.md)
|
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Politique de versionning
|
| 2 |
+
|
| 3 |
+
> Comment Picarones numérote ses versions, et pourquoi.
|
| 4 |
+
|
| 5 |
+
## Résumé
|
| 6 |
+
|
| 7 |
+
Picarones suit **Semantic Versioning 2.0.0** strict :
|
| 8 |
+
`MAJOR.MINOR.PATCH` avec suffixes pré-release PEP 440 (`-rc1`,
|
| 9 |
+
`-beta1`, `-alpha1`).
|
| 10 |
+
|
| 11 |
+
**État courant** : `0.9.0` — phase pré-1.0. Architecture stable,
|
| 12 |
+
surface fonctionnelle en construction.
|
| 13 |
+
|
| 14 |
+
**Cible** : `1.0.0` — release publique avec UI, rapport refondu et
|
| 15 |
+
parité des importeurs livrés.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## SemVer pré-1.0 : qu'est-ce que ça change ?
|
| 20 |
+
|
| 21 |
+
En `0.x`, SemVer autorise **les ruptures sans préavis**. Picarones
|
| 22 |
+
les annonce explicitement dans le `CHANGELOG.md` mais ne garantit
|
| 23 |
+
pas de période de dépréciation. C'est précisément l'intérêt du
|
| 24 |
+
pré-1.0 : pouvoir corriger l'API publique sans empêcher la
|
| 25 |
+
progression.
|
| 26 |
+
|
| 27 |
+
Concrètement :
|
| 28 |
+
|
| 29 |
+
- les schémas JSON (`BenchmarkResult`, `run_manifest.json`,
|
| 30 |
+
`pipeline_results.jsonl`, `view_results.jsonl`) **peuvent évoluer**
|
| 31 |
+
entre `0.9.x` et `0.10.x` ;
|
| 32 |
+
- les noms de paramètres CLI **peuvent changer** ;
|
| 33 |
+
- les endpoints HTTP **peuvent être renommés ou retirés** ;
|
| 34 |
+
- les contrats internes (Pydantic models de `picarones.domain`)
|
| 35 |
+
**peuvent être modifiés**.
|
| 36 |
+
|
| 37 |
+
Chaque rupture est documentée dans le `CHANGELOG.md` sous la section
|
| 38 |
+
`### BREAKING`.
|
| 39 |
+
|
| 40 |
+
À partir de `1.0.0`, la politique se durcit : période de
|
| 41 |
+
dépréciation minimum d'une mineure, retrait à la majeure suivante.
|
| 42 |
+
|
| 43 |
+
---
|
| 44 |
+
|
| 45 |
+
## Mapping version ↔ tag git
|
| 46 |
+
|
| 47 |
+
Le numéro de version est dérivé du **tag git** par
|
| 48 |
+
`setuptools_scm`. Pas de version codée en dur dans `pyproject.toml`.
|
| 49 |
+
|
| 50 |
+
| Contexte | Version produite |
|
| 51 |
+
|---|---|
|
| 52 |
+
| Tag `v0.9.0` posé sur un commit | `0.9.0` |
|
| 53 |
+
| 5 commits après `v0.9.0` (sans nouveau tag) | `0.9.1.dev5+g<sha>` |
|
| 54 |
+
| Tag `v0.10.0-rc1` | `0.10.0rc1` (PEP 440) |
|
| 55 |
+
| Sans aucun tag (tarball, repo neuf) | `0.9.0` (fallback) |
|
| 56 |
+
|
| 57 |
+
Le `fallback_version` de `pyproject.toml` est synchronisé avec
|
| 58 |
+
`picarones/domain/_version_fallback.py:FALLBACK_VERSION`. Un test
|
| 59 |
+
d'architecture (`tests/architecture/test_single_version_source.py`)
|
| 60 |
+
vérifie cette cohérence.
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
## Roadmap vers 1.0.0
|
| 65 |
+
|
| 66 |
+
La sortie de `1.0.0` est conditionnée à la livraison de :
|
| 67 |
+
|
| 68 |
+
1. **Surface UI complète** — exposition de tous les champs du modèle
|
| 69 |
+
`BenchmarkRunRequest`, refonte du rapport HTML, parité fonctionnelle
|
| 70 |
+
avec la CLI (`compare`, `robustness`, `history`).
|
| 71 |
+
2. **Parité importeurs corpus** — IIIF, Gallica, eScriptorium accessibles
|
| 72 |
+
depuis l'UI web (en plus de HTR-United et HuggingFace déjà présents).
|
| 73 |
+
3. **Refonte rapport HTML** — passage à l'IA 4 onglets (Overview / Engines
|
| 74 |
+
/ Documents / Crosses) en gardant la stack Jinja2.
|
| 75 |
+
|
| 76 |
+
Les versions intermédiaires `0.10.0`, `0.11.0`, etc. peuvent être
|
| 77 |
+
publiées au fil du chantier ; elles sont des jalons techniques, pas
|
| 78 |
+
des releases publiques.
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## Pourquoi `0.9.0` plutôt que `2.0.x` ou `3.0.0` ?
|
| 83 |
+
|
| 84 |
+
L'historique interne du projet utilisait une dénomination « v2.0 »
|
| 85 |
+
pour désigner l'aboutissement du rewrite architectural en 8 couches
|
| 86 |
+
(mai 2026). Cette dénomination reflétait un cycle de refactor
|
| 87 |
+
interne, pas une maturité fonctionnelle publique : à ce moment, le
|
| 88 |
+
projet n'avait ni interface utilisateur finalisée, ni parité
|
| 89 |
+
importeurs, ni rapport refondu.
|
| 90 |
+
|
| 91 |
+
Le repositionnement en `0.9.0` (mai 2026) aligne le numéro de
|
| 92 |
+
version sur la maturité réelle : architecture solide, surface
|
| 93 |
+
fonctionnelle en construction. La sortie de `1.0.0` deviendra un
|
| 94 |
+
événement public marquant (UI complète, rapport refondu, parité
|
| 95 |
+
importeurs), ce qui correspond à l'usage habituel d'un saut
|
| 96 |
+
`0.x → 1.0`.
|
| 97 |
+
|
| 98 |
+
Ce choix est documenté dans la note de repositionnement de
|
| 99 |
+
[`docs/archive/README.md`](../archive/README.md) et dans la première
|
| 100 |
+
entrée du [`CHANGELOG.md`](../../CHANGELOG.md) post-repositionnement.
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## Source unique de vérité (anti-dérive)
|
| 105 |
+
|
| 106 |
+
La version courante est définie à **un seul endroit** : le tag git le
|
| 107 |
+
plus récent, lu par `setuptools_scm`.
|
| 108 |
+
|
| 109 |
+
Le fallback (utilisé en l'absence de tag) est défini à **un seul
|
| 110 |
+
endroit** : `picarones/domain/_version_fallback.py:FALLBACK_VERSION`. La
|
| 111 |
+
constante équivalente `fallback_version` dans `pyproject.toml` doit
|
| 112 |
+
rester synchronisée — un test d'architecture le vérifie.
|
| 113 |
+
|
| 114 |
+
**Règle de codage** : interdiction de coder un numéro de version en
|
| 115 |
+
dur ailleurs que dans ces deux fichiers. Tout consommateur doit
|
| 116 |
+
lire `picarones.__version__` (ou ses équivalents internes
|
| 117 |
+
`_resolve_picarones_version()` pour les couches qui ne peuvent pas
|
| 118 |
+
importer le package racine).
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## Procédure de release
|
| 123 |
+
|
| 124 |
+
Voir [`docs/operations/release-process.md`](../operations/release-process.md)
|
| 125 |
+
pour la procédure complète. Le résumé :
|
| 126 |
+
|
| 127 |
+
```bash
|
| 128 |
+
# 1. Mettre à jour CHANGELOG.md (section ## [X.Y.Z] — YYYY-MM-DD)
|
| 129 |
+
git commit -am "docs(changelog): release 0.10.0"
|
| 130 |
+
|
| 131 |
+
# 2. Tag annoté
|
| 132 |
+
git tag -a v0.10.0 -m "Picarones 0.10.0"
|
| 133 |
+
git push origin main v0.10.0
|
| 134 |
+
|
| 135 |
+
# 3. Le workflow .github/workflows/release.yml déclenche
|
| 136 |
+
# automatiquement build + TestPyPI + PyPI + Docker + GitHub Release.
|
| 137 |
+
```
|
|
@@ -137,15 +137,15 @@ Vous évaluez les implications RGPD avant signature.
|
|
| 137 |
|
| 138 |
| Document | Sujet |
|
| 139 |
|----------|-------|
|
| 140 |
-
| [`/CHANGELOG.md`](../CHANGELOG.md) | Journal des versions actives (
|
| 141 |
-
| [`archive/`](archive/) | Documents archivés (audits, migration, roadmap pré-
|
| 142 |
| [`roadmap/backlog.md`](roadmap/backlog.md) | Backlog vivant |
|
| 143 |
|
| 144 |
---
|
| 145 |
|
| 146 |
## Conventions
|
| 147 |
|
| 148 |
-
- **Une seule arborescence canonique (
|
| 149 |
`domain → formats → evaluation → pipeline → adapters → app → reports → interfaces`.
|
| 150 |
Les paquets legacy ont été supprimés en mai 2026.
|
| 151 |
- **Tout chemin `picarones/.../X.py` cité dans la doc doit exister**.
|
|
|
|
| 137 |
|
| 138 |
| Document | Sujet |
|
| 139 |
|----------|-------|
|
| 140 |
+
| [`/CHANGELOG.md`](../CHANGELOG.md) | Journal des versions actives (depuis `0.9.0`) |
|
| 141 |
+
| [`archive/`](archive/) | Documents archivés (audits, migration, roadmap pré-rewrite, changelog historique) |
|
| 142 |
| [`roadmap/backlog.md`](roadmap/backlog.md) | Backlog vivant |
|
| 143 |
|
| 144 |
---
|
| 145 |
|
| 146 |
## Conventions
|
| 147 |
|
| 148 |
+
- **Une seule arborescence canonique** (depuis `0.9.0`) :
|
| 149 |
`domain → formats → evaluation → pipeline → adapters → app → reports → interfaces`.
|
| 150 |
Les paquets legacy ont été supprimés en mai 2026.
|
| 151 |
- **Tout chemin `picarones/.../X.py` cité dans la doc doit exister**.
|
|
@@ -80,14 +80,14 @@ git checkout main
|
|
| 80 |
git pull --ff-only
|
| 81 |
|
| 82 |
# 2. Mettre à jour le CHANGELOG.md (Keep a Changelog)
|
| 83 |
-
# Ajouter une section ## [
|
| 84 |
git add CHANGELOG.md
|
| 85 |
-
git commit -m "docs(changelog): release
|
| 86 |
|
| 87 |
# 3. Tag annoté + push
|
| 88 |
-
git tag -a
|
| 89 |
git push origin main
|
| 90 |
-
git push origin
|
| 91 |
|
| 92 |
# 4. Surveiller le workflow Actions
|
| 93 |
gh run watch
|
|
@@ -110,19 +110,23 @@ Durée totale : ~15 min (multi-arch + 30s d'indexation TestPyPI).
|
|
| 110 |
|
| 111 |
## Versionnement
|
| 112 |
|
| 113 |
-
Picarones suit **Semantic Versioning 2.0.0** :
|
|
|
|
| 114 |
|
| 115 |
- `MAJOR.MINOR.PATCH` — incompatibilité, ajout, fix.
|
| 116 |
- Suffixes pré-release : `-rc1`, `-beta1`, `-alpha1`. Le workflow
|
| 117 |
les détecte et coche `prerelease=true` sur la GitHub Release.
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
`setuptools_scm` dérive automatiquement la version du tag git :
|
| 120 |
|
| 121 |
| Contexte | Version produite |
|
| 122 |
|---|---|
|
| 123 |
-
| Tag `
|
| 124 |
-
| 5 commits après `
|
| 125 |
-
| `
|
| 126 |
|
| 127 |
## Procédure d'urgence : hotfix sécurité
|
| 128 |
|
|
@@ -133,9 +137,9 @@ git checkout -b hotfix-cve-2026-XXXX main
|
|
| 133 |
# correctif minimal + test
|
| 134 |
git commit -m "fix(security): patch CVE-2026-XXXX"
|
| 135 |
# CHANGELOG bump
|
| 136 |
-
git commit -m "docs(changelog): release
|
| 137 |
-
git tag -a
|
| 138 |
-
git push origin hotfix-cve-2026-XXXX
|
| 139 |
# Le workflow release.yml gère le reste.
|
| 140 |
# Après merge : annonce sur SECURITY.md + courriel mainteneur.
|
| 141 |
```
|
|
|
|
| 80 |
git pull --ff-only
|
| 81 |
|
| 82 |
# 2. Mettre à jour le CHANGELOG.md (Keep a Changelog)
|
| 83 |
+
# Ajouter une section ## [0.10.0] — YYYY-MM-DD avec les changes
|
| 84 |
git add CHANGELOG.md
|
| 85 |
+
git commit -m "docs(changelog): release 0.10.0"
|
| 86 |
|
| 87 |
# 3. Tag annoté + push
|
| 88 |
+
git tag -a v0.10.0 -m "Picarones 0.10.0"
|
| 89 |
git push origin main
|
| 90 |
+
git push origin v0.10.0
|
| 91 |
|
| 92 |
# 4. Surveiller le workflow Actions
|
| 93 |
gh run watch
|
|
|
|
| 110 |
|
| 111 |
## Versionnement
|
| 112 |
|
| 113 |
+
Picarones suit **Semantic Versioning 2.0.0**. Politique complète :
|
| 114 |
+
[`docs/explanation/versioning.md`](../explanation/versioning.md).
|
| 115 |
|
| 116 |
- `MAJOR.MINOR.PATCH` — incompatibilité, ajout, fix.
|
| 117 |
- Suffixes pré-release : `-rc1`, `-beta1`, `-alpha1`. Le workflow
|
| 118 |
les détecte et coche `prerelease=true` sur la GitHub Release.
|
| 119 |
+
- **État courant : `0.x` (pré-1.0)** — les ruptures peuvent
|
| 120 |
+
intervenir sans période de dépréciation, annoncées dans le
|
| 121 |
+
`CHANGELOG.md`.
|
| 122 |
|
| 123 |
`setuptools_scm` dérive automatiquement la version du tag git :
|
| 124 |
|
| 125 |
| Contexte | Version produite |
|
| 126 |
|---|---|
|
| 127 |
+
| Tag `v0.10.0` | `0.10.0` |
|
| 128 |
+
| 5 commits après `v0.10.0` | `0.10.1.dev5+g<sha>` (dev seulement) |
|
| 129 |
+
| `v0.11.0-rc1` | `0.11.0rc1` (PEP 440) |
|
| 130 |
|
| 131 |
## Procédure d'urgence : hotfix sécurité
|
| 132 |
|
|
|
|
| 137 |
# correctif minimal + test
|
| 138 |
git commit -m "fix(security): patch CVE-2026-XXXX"
|
| 139 |
# CHANGELOG bump
|
| 140 |
+
git commit -m "docs(changelog): release 0.9.1"
|
| 141 |
+
git tag -a v0.9.1 -m "Picarones 0.9.1 (security)"
|
| 142 |
+
git push origin hotfix-cve-2026-XXXX v0.9.1
|
| 143 |
# Le workflow release.yml gère le reste.
|
| 144 |
# Après merge : annonce sur SECURITY.md + courriel mainteneur.
|
| 145 |
```
|
|
@@ -25,15 +25,15 @@ Code → schéma → config.
|
|
| 25 |
# Identifier la version actuelle
|
| 26 |
docker inspect picarones | grep Version
|
| 27 |
|
| 28 |
-
# Rollback vers la version précédente (ex :
|
| 29 |
-
docker pull ghcr.io/maribakulj/picarones:
|
| 30 |
docker stop picarones
|
| 31 |
docker rm picarones
|
| 32 |
docker run --name picarones \
|
| 33 |
-p 7860:7860 \
|
| 34 |
--env-file /etc/picarones/.env \
|
| 35 |
-v /var/lib/picarones/jobs.db:/app/jobs.db \
|
| 36 |
-
ghcr.io/maribakulj/picarones:
|
| 37 |
|
| 38 |
# Vérifier
|
| 39 |
curl -fsS http://localhost:7860/health
|
|
@@ -46,7 +46,7 @@ curl -fsS http://localhost:7860/health
|
|
| 46 |
pip index versions picarones
|
| 47 |
|
| 48 |
# Rollback
|
| 49 |
-
pip install --force-reinstall picarones==
|
| 50 |
|
| 51 |
# Vérifier
|
| 52 |
picarones --version
|
|
@@ -76,15 +76,16 @@ nécessite de revenir à un schéma antérieur :
|
|
| 76 |
### Cas simple : downgrade non destructif
|
| 77 |
|
| 78 |
Si la migration N→N+1 a uniquement **ajouté** des colonnes / tables
|
| 79 |
-
(jamais supprimé), le code
|
| 80 |
-
les nouvelles colonnes sont simplement
|
|
|
|
| 81 |
|
| 82 |
Aucune action requise.
|
| 83 |
|
| 84 |
### Cas complexe : downgrade destructif
|
| 85 |
|
| 86 |
-
Si la migration a renommé/supprimé des colonnes, le code
|
| 87 |
-
peut lever des `sqlite3.OperationalError`.
|
| 88 |
|
| 89 |
**Procédure** :
|
| 90 |
|
|
@@ -107,7 +108,7 @@ peut lever des `sqlite3.OperationalError`.
|
|
| 107 |
```
|
| 108 |
|
| 109 |
4. **Forcer le schema_version à la valeur attendue par le code
|
| 110 |
-
|
| 111 |
|
| 112 |
```sql
|
| 113 |
sqlite3 /var/lib/picarones/jobs.db
|
|
@@ -115,14 +116,14 @@ peut lever des `sqlite3.OperationalError`.
|
|
| 115 |
sqlite> .quit
|
| 116 |
```
|
| 117 |
|
| 118 |
-
5. Redémarrer Picarones
|
| 119 |
6. Vérifier que les jobs anciens sont lisibles via `GET /api/jobs`.
|
| 120 |
|
| 121 |
### Pas de backup ?
|
| 122 |
|
| 123 |
-
Le code
|
| 124 |
-
`schema_version` est plus récent que celui
|
| 125 |
-
un warning explicite et tente de continuer. En cas d'incident,
|
| 126 |
les jobs *nouveaux* fonctionnent ; les jobs *anciens* (avec des
|
| 127 |
champs nouveaux) peuvent apparaître tronqués mais ne crashent
|
| 128 |
pas.
|
|
|
|
| 25 |
# Identifier la version actuelle
|
| 26 |
docker inspect picarones | grep Version
|
| 27 |
|
| 28 |
+
# Rollback vers la version précédente (ex : v0.10.0 → v0.9.0)
|
| 29 |
+
docker pull ghcr.io/maribakulj/picarones:v0.9.0
|
| 30 |
docker stop picarones
|
| 31 |
docker rm picarones
|
| 32 |
docker run --name picarones \
|
| 33 |
-p 7860:7860 \
|
| 34 |
--env-file /etc/picarones/.env \
|
| 35 |
-v /var/lib/picarones/jobs.db:/app/jobs.db \
|
| 36 |
+
ghcr.io/maribakulj/picarones:v0.9.0
|
| 37 |
|
| 38 |
# Vérifier
|
| 39 |
curl -fsS http://localhost:7860/health
|
|
|
|
| 46 |
pip index versions picarones
|
| 47 |
|
| 48 |
# Rollback
|
| 49 |
+
pip install --force-reinstall picarones==0.9.0
|
| 50 |
|
| 51 |
# Vérifier
|
| 52 |
picarones --version
|
|
|
|
| 76 |
### Cas simple : downgrade non destructif
|
| 77 |
|
| 78 |
Si la migration N→N+1 a uniquement **ajouté** des colonnes / tables
|
| 79 |
+
(jamais supprimé), le code de la version cible lit un schéma plus
|
| 80 |
+
récent sans plantage — les nouvelles colonnes sont simplement
|
| 81 |
+
ignorées.
|
| 82 |
|
| 83 |
Aucune action requise.
|
| 84 |
|
| 85 |
### Cas complexe : downgrade destructif
|
| 86 |
|
| 87 |
+
Si la migration a renommé/supprimé des colonnes, le code de la
|
| 88 |
+
version cible peut lever des `sqlite3.OperationalError`.
|
| 89 |
|
| 90 |
**Procédure** :
|
| 91 |
|
|
|
|
| 108 |
```
|
| 109 |
|
| 110 |
4. **Forcer le schema_version à la valeur attendue par le code
|
| 111 |
+
de la version cible** (si nécessaire) :
|
| 112 |
|
| 113 |
```sql
|
| 114 |
sqlite3 /var/lib/picarones/jobs.db
|
|
|
|
| 116 |
sqlite> .quit
|
| 117 |
```
|
| 118 |
|
| 119 |
+
5. Redémarrer Picarones à la version cible.
|
| 120 |
6. Vérifier que les jobs anciens sont lisibles via `GET /api/jobs`.
|
| 121 |
|
| 122 |
### Pas de backup ?
|
| 123 |
|
| 124 |
+
Le code Picarones (depuis `0.9.0`) a un mode "lecture seule
|
| 125 |
+
défensive" : si le `schema_version` est plus récent que celui
|
| 126 |
+
attendu, le code log un warning explicite et tente de continuer. En cas d'incident,
|
| 127 |
les jobs *nouveaux* fonctionnent ; les jobs *anciens* (avec des
|
| 128 |
champs nouveaux) peuvent apparaître tronqués mais ne crashent
|
| 129 |
pas.
|
|
@@ -1,8 +1,8 @@
|
|
| 1 |
# API publique stable de Picarones
|
| 2 |
|
| 3 |
-
> **Statut
|
| 4 |
-
> 8 couches canoniques
|
| 5 |
-
> top-level (`picarones.core`, `picarones.measurements`,
|
| 6 |
> `picarones.engines`, `picarones.modules`, `picarones.report`,
|
| 7 |
> `picarones.llm`, `picarones.pipelines`, `picarones.cli`,
|
| 8 |
> `picarones.web`, `picarones.extras`) ainsi que les sous-paquets
|
|
@@ -10,6 +10,12 @@
|
|
| 10 |
> `interfaces/{cli,web}/_legacy/`) ont été **supprimés**. Plus aucun
|
| 11 |
> shim, plus aucun `DeprecationWarning` rétrocompat.
|
| 12 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
> **Architecture canonique (cf.
|
| 14 |
> [`docs/explanation/architecture.md`](../explanation/architecture.md))** :
|
| 15 |
> `domain → formats → evaluation → pipeline → adapters → app →
|
|
@@ -271,9 +277,9 @@ def reset_default_store(...)
|
|
| 271 |
historiques qui les importent (Sprint 13/42) sont préservés mais
|
| 272 |
par effort, pas par contrat.
|
| 273 |
|
| 274 |
-
### Bump majeur (`2.0.0`)
|
| 275 |
|
| 276 |
-
|
| 277 |
|
| 278 |
- Supprimer un nom de cette liste.
|
| 279 |
- Changer la signature d'une fonction publique de manière non
|
|
@@ -281,10 +287,14 @@ Un bump majeur sera nécessaire pour :
|
|
| 281 |
- Casser le format de sérialisation du `BenchmarkResult.to_json()`.
|
| 282 |
- Renommer un module de l'arborescence canonique.
|
| 283 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
## Chemins canoniques par couche
|
| 285 |
|
| 286 |
-
L'arborescence
|
| 287 |
-
couche. Toutes les intégrations doivent passer par ces chemins —
|
| 288 |
plus de path legacy disponible.
|
| 289 |
|
| 290 |
### Couche 3 — `picarones.evaluation`
|
|
|
|
| 1 |
# API publique stable de Picarones
|
| 2 |
|
| 3 |
+
> **Statut `0.9.0` (mai 2026)** — phase pré-1.0. L'**architecture**
|
| 4 |
+
> est stable : les 8 couches canoniques sont en place, tous les
|
| 5 |
+
> paquets legacy top-level (`picarones.core`, `picarones.measurements`,
|
| 6 |
> `picarones.engines`, `picarones.modules`, `picarones.report`,
|
| 7 |
> `picarones.llm`, `picarones.pipelines`, `picarones.cli`,
|
| 8 |
> `picarones.web`, `picarones.extras`) ainsi que les sous-paquets
|
|
|
|
| 10 |
> `interfaces/{cli,web}/_legacy/`) ont été **supprimés**. Plus aucun
|
| 11 |
> shim, plus aucun `DeprecationWarning` rétrocompat.
|
| 12 |
>
|
| 13 |
+
> En revanche, l'**API publique** elle-même n'est pas figée tant que
|
| 14 |
+
> `1.0.0` n'est pas livré. Les schémas JSON, noms de paramètres CLI
|
| 15 |
+
> et endpoints HTTP peuvent évoluer entre `0.9.x` et `0.10.x` sans
|
| 16 |
+
> période de dépréciation, conformément à la
|
| 17 |
+
> [politique de versionning pré-1.0](../explanation/versioning.md).
|
| 18 |
+
>
|
| 19 |
> **Architecture canonique (cf.
|
| 20 |
> [`docs/explanation/architecture.md`](../explanation/architecture.md))** :
|
| 21 |
> `domain → formats → evaluation → pipeline → adapters → app →
|
|
|
|
| 277 |
historiques qui les importent (Sprint 13/42) sont préservés mais
|
| 278 |
par effort, pas par contrat.
|
| 279 |
|
| 280 |
+
### Bump majeur (`1.0.0` puis `2.0.0`)
|
| 281 |
|
| 282 |
+
À partir de `1.0.0`, un bump majeur sera nécessaire pour :
|
| 283 |
|
| 284 |
- Supprimer un nom de cette liste.
|
| 285 |
- Changer la signature d'une fonction publique de manière non
|
|
|
|
| 287 |
- Casser le format de sérialisation du `BenchmarkResult.to_json()`.
|
| 288 |
- Renommer un module de l'arborescence canonique.
|
| 289 |
|
| 290 |
+
En `0.x` (état courant), ces changements peuvent se produire sans
|
| 291 |
+
bump majeur, mais sont annoncés dans le `CHANGELOG.md` sous une
|
| 292 |
+
section `### BREAKING`.
|
| 293 |
+
|
| 294 |
## Chemins canoniques par couche
|
| 295 |
|
| 296 |
+
L'arborescence `0.9.0` expose des points d'entrée stables organisés
|
| 297 |
+
par couche. Toutes les intégrations doivent passer par ces chemins —
|
| 298 |
plus de path legacy disponible.
|
| 299 |
|
| 300 |
### Couche 3 — `picarones.evaluation`
|
|
@@ -79,7 +79,7 @@ ont été appliquées au moment du calcul de CER, **sans relancer**.
|
|
| 79 |
### `environment`
|
| 80 |
|
| 81 |
```yaml
|
| 82 |
-
picarones_version: "
|
| 83 |
python_version: "3.11.13"
|
| 84 |
platform: "Linux 6.18.5-x86_64-with-glibc2.39"
|
| 85 |
git_commit: "17cc5474..."
|
|
|
|
| 79 |
### `environment`
|
| 80 |
|
| 81 |
```yaml
|
| 82 |
+
picarones_version: "0.9.0"
|
| 83 |
python_version: "3.11.13"
|
| 84 |
platform: "Linux 6.18.5-x86_64-with-glibc2.39"
|
| 85 |
git_commit: "17cc5474..."
|
|
@@ -3,14 +3,18 @@
|
|
| 3 |
> **Plateforme de banc d'essai d'OCR / HTR / VLM et de pipelines de
|
| 4 |
> post-correction pour documents patrimoniaux.**
|
| 5 |
>
|
| 6 |
-
> Version
|
| 7 |
-
>
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
> **Note de lecture** : ce document décrit ce que Picarones **fait
|
| 10 |
-
> aujourd'hui**, dans la version
|
| 11 |
-
> assumées** (ce que Picarones *ne fait pas et
|
| 12 |
-
> la
|
| 13 |
-
> les adapters Kraken/AWS Textract), voir
|
|
|
|
| 14 |
>
|
| 15 |
> Pour la cartographie technique du code et les règles de
|
| 16 |
> contribution interne, voir [`CLAUDE.md`](../../CLAUDE.md). Ce document
|
|
@@ -724,18 +728,21 @@ configurable via `PICARONES_UPLOAD_RETENTION_DAYS=7` par défaut.
|
|
| 724 |
## 10. Limites assumées et non-fonctionnalités
|
| 725 |
|
| 726 |
Cette section décrit explicitement **ce que Picarones ne fait pas
|
| 727 |
-
et ne fera pas dans la
|
| 728 |
-
|
| 729 |
-
documenté ci-dessous,
|
|
|
|
|
|
|
|
|
|
| 730 |
|
| 731 |
<!-- specs-check: known-abandoned-start -->
|
| 732 |
|
| 733 |
> Toutes les fonctionnalités listées ci-dessous étaient promises ou
|
| 734 |
-
> évoquées dans SPECS v1 et sont **explicitement
|
| 735 |
-
> non implémentées ou reportées** dans
|
| 736 |
-
>
|
| 737 |
-
> détecte cette section
|
| 738 |
-
> non-fonctionnalités du projet.
|
| 739 |
|
| 740 |
### 10.1 Adapters OCR non livrés
|
| 741 |
|
|
@@ -821,10 +828,10 @@ Trois documents complémentaires pilotent l'évolution :
|
|
| 821 |
- [`CHANGELOG.md`](CHANGELOG.md) — historique sprint par sprint,
|
| 822 |
format Keep a Changelog.
|
| 823 |
- [`docs/roadmap/backlog.md`](docs/roadmap/backlog.md) — backlog
|
| 824 |
-
vivant des chantiers post-
|
| 825 |
- [`docs/archive/`](docs/archive/) — audits institutionnels,
|
| 826 |
-
plans de remédiation pré-
|
| 827 |
-
(`2026-roadmap/evolution.md`), changelog pré-
|
| 828 |
|
| 829 |
L'**état du plan institutionnel** au 2 mai 2026 :
|
| 830 |
|
|
@@ -842,13 +849,21 @@ L'**état du plan institutionnel** au 2 mai 2026 :
|
|
| 842 |
|
| 843 |
---
|
| 844 |
|
| 845 |
-
## 12. Migration v1 → v2 — annexe historique
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
|
| 847 |
Pour les lecteurs qui avaient pris connaissance de SPECS v1.0
|
| 848 |
(mars 2025) ou de l'Addendum Sprints 16-30, voici la table de
|
| 849 |
migration des promesses changées :
|
| 850 |
|
| 851 |
-
| SPECS v1 disait | SPECS
|
| 852 |
|---|---|---|
|
| 853 |
| Adapter Kraken (priorité v1.0) | Ouvert en plugin externe | Politique modules contribués Sprint 97 ; concentration sur 5 adapters cœur. |
|
| 854 |
| Adapter AWS Textract (v1.1) | Abandonné | Pas de DPA, périmètre couvert par 3 clouds existants. |
|
|
@@ -883,4 +898,5 @@ intégration des standards bibliothéconomiques (IIIF, ALTO XML,
|
|
| 883 |
PAGE XML, HTR-United, eScriptorium, Gallica), rapport interactif
|
| 884 |
exportable, snapshot de reproductibilité.*
|
| 885 |
|
| 886 |
-
*Dernière mise à jour :
|
|
|
|
|
|
| 3 |
> **Plateforme de banc d'essai d'OCR / HTR / VLM et de pipelines de
|
| 4 |
> post-correction pour documents patrimoniaux.**
|
| 5 |
>
|
| 6 |
+
> Version 0.9.0 (pré-1.0) — Mai 2026. Politique de versionning :
|
| 7 |
+
> [`../explanation/versioning.md`](../explanation/versioning.md).
|
| 8 |
+
>
|
| 9 |
+
> Itération de spec **2** (révision éditoriale du document, distincte
|
| 10 |
+
> du numéro de version du logiciel ; mai 2026).
|
| 11 |
|
| 12 |
> **Note de lecture** : ce document décrit ce que Picarones **fait
|
| 13 |
+
> aujourd'hui**, dans la version `0.9.0`. Pour les
|
| 14 |
+
> **non-fonctionnalités assumées** (ce que Picarones *ne fait pas et
|
| 15 |
+
> ne fera pas* dans la lignée `0.x` — par exemple la recommandation
|
| 16 |
+
> prescriptive, l'export PDF, les adapters Kraken/AWS Textract), voir
|
| 17 |
+
> la section §10.
|
| 18 |
>
|
| 19 |
> Pour la cartographie technique du code et les règles de
|
| 20 |
> contribution interne, voir [`CLAUDE.md`](../../CLAUDE.md). Ce document
|
|
|
|
| 728 |
## 10. Limites assumées et non-fonctionnalités
|
| 729 |
|
| 730 |
Cette section décrit explicitement **ce que Picarones ne fait pas
|
| 731 |
+
et ne fera pas dans la lignée `0.x`**. Plusieurs items étaient
|
| 732 |
+
promis dans une itération antérieure de cette spec (SPECS v1, mars
|
| 733 |
+
2025) — leur abandon est un choix éditorial documenté ci-dessous,
|
| 734 |
+
pas un oubli. Les mentions « v1.0 », « v1.1 », « v1.2 » dans les
|
| 735 |
+
items ci-dessous renvoient à la **roadmap historique** de cette
|
| 736 |
+
spec, pas à des versions logicielles publiées.
|
| 737 |
|
| 738 |
<!-- specs-check: known-abandoned-start -->
|
| 739 |
|
| 740 |
> Toutes les fonctionnalités listées ci-dessous étaient promises ou
|
| 741 |
+
> évoquées dans SPECS v1 (itération de spec) et sont **explicitement
|
| 742 |
+
> abandonnées, non implémentées ou reportées** dans l'itération de
|
| 743 |
+
> spec actuelle (qui couvre le logiciel `0.9.0`). Le test
|
| 744 |
+
> ``tests/docs/test_specs_consistency.py`` détecte cette section
|
| 745 |
+
> comme la déclaration officielle des non-fonctionnalités du projet.
|
| 746 |
|
| 747 |
### 10.1 Adapters OCR non livrés
|
| 748 |
|
|
|
|
| 828 |
- [`CHANGELOG.md`](CHANGELOG.md) — historique sprint par sprint,
|
| 829 |
format Keep a Changelog.
|
| 830 |
- [`docs/roadmap/backlog.md`](docs/roadmap/backlog.md) — backlog
|
| 831 |
+
vivant des chantiers post-`0.9.0`.
|
| 832 |
- [`docs/archive/`](docs/archive/) — audits institutionnels,
|
| 833 |
+
plans de remédiation pré-rewrite, roadmap historique
|
| 834 |
+
(`2026-roadmap/evolution.md`), changelog pré-rewrite.
|
| 835 |
|
| 836 |
L'**état du plan institutionnel** au 2 mai 2026 :
|
| 837 |
|
|
|
|
| 849 |
|
| 850 |
---
|
| 851 |
|
| 852 |
+
## 12. Migration des itérations de spec (v1 → v2) — annexe historique
|
| 853 |
+
|
| 854 |
+
> Cette section trace l'évolution **éditoriale du document de
|
| 855 |
+
> spécification** entre son itération 1 (SPECS v1, mars 2025) et
|
| 856 |
+
> son itération 2 (le présent document). Les indices « v1.0 »,
|
| 857 |
+
> « v1.1 » dans la colonne de gauche désignent la **roadmap**
|
| 858 |
+
> historique de SPECS v1, pas des versions logicielles publiées.
|
| 859 |
+
> Pour la politique de versionning du logiciel,
|
| 860 |
+
> voir [`../explanation/versioning.md`](../explanation/versioning.md).
|
| 861 |
|
| 862 |
Pour les lecteurs qui avaient pris connaissance de SPECS v1.0
|
| 863 |
(mars 2025) ou de l'Addendum Sprints 16-30, voici la table de
|
| 864 |
migration des promesses changées :
|
| 865 |
|
| 866 |
+
| SPECS v1 disait | SPECS itération 2 documente | Raison |
|
| 867 |
|---|---|---|
|
| 868 |
| Adapter Kraken (priorité v1.0) | Ouvert en plugin externe | Politique modules contribués Sprint 97 ; concentration sur 5 adapters cœur. |
|
| 869 |
| Adapter AWS Textract (v1.1) | Abandonné | Pas de DPA, périmètre couvert par 3 clouds existants. |
|
|
|
|
| 898 |
PAGE XML, HTR-United, eScriptorium, Gallica), rapport interactif
|
| 899 |
exportable, snapshot de reproductibilité.*
|
| 900 |
|
| 901 |
+
*Dernière mise à jour : 23 mai 2026 (repositionnement SemVer pré-1.0,
|
| 902 |
+
couvre logiciel `0.9.0`).*
|
|
@@ -113,7 +113,7 @@ exister à la livraison BnF.
|
|
| 113 |
dossier isolé par session/run.
|
| 114 |
- Schemas DTO (Pydantic) séparés des modèles de domaine.
|
| 115 |
|
| 116 |
-
→ Chantier post-
|
| 117 |
|
| 118 |
### 2.4 Suppression de la dette d'imports magiques
|
| 119 |
|
|
@@ -123,7 +123,7 @@ exister à la livraison BnF.
|
|
| 123 |
- Entry points Python pour les modules tiers (`picarones.metrics`,
|
| 124 |
`picarones.adapters`).
|
| 125 |
|
| 126 |
-
→ Chantier post-
|
| 127 |
|
| 128 |
### 2.5b Migration des adapters restants
|
| 129 |
|
|
|
|
| 113 |
dossier isolé par session/run.
|
| 114 |
- Schemas DTO (Pydantic) séparés des modèles de domaine.
|
| 115 |
|
| 116 |
+
→ Chantier post-`0.9.0` (cf. roadmap → 1.0 dans CHANGELOG).
|
| 117 |
|
| 118 |
### 2.4 Suppression de la dette d'imports magiques
|
| 119 |
|
|
|
|
| 123 |
- Entry points Python pour les modules tiers (`picarones.metrics`,
|
| 124 |
`picarones.adapters`).
|
| 125 |
|
| 126 |
+
→ Chantier post-`0.9.0` (cf. roadmap → 1.0 dans CHANGELOG).
|
| 127 |
|
| 128 |
### 2.5b Migration des adapters restants
|
| 129 |
|
|
@@ -14,8 +14,8 @@
|
|
| 14 |
> ouvert anonymisé, sans secrets), CLI mono-utilisateur en local
|
| 15 |
> (modèle de menace = celui de la machine de l'utilisateur).
|
| 16 |
>
|
| 17 |
-
> **Statut** :
|
| 18 |
-
> incident sécurité.
|
| 19 |
|
| 20 |
## Acteurs
|
| 21 |
|
|
|
|
| 14 |
> ouvert anonymisé, sans secrets), CLI mono-utilisateur en local
|
| 15 |
> (modèle de menace = celui de la machine de l'utilisateur).
|
| 16 |
>
|
| 17 |
+
> **Statut** : itération 1, 2026-05 (couvre `0.9.0`). À réviser à
|
| 18 |
+
> chaque release majeure ou incident sécurité.
|
| 19 |
|
| 20 |
## Acteurs
|
| 21 |
|
|
@@ -22,13 +22,14 @@ Voir ``docs/explanation/architecture.md`` pour la cartographie complète des
|
|
| 22 |
|
| 23 |
from __future__ import annotations
|
| 24 |
|
| 25 |
-
# Version
|
| 26 |
# 1. ``picarones._version`` injecté au build par setuptools_scm
|
| 27 |
# (présent dans le wheel installé) ;
|
| 28 |
# 2. fallback ``importlib.metadata.version("picarones")`` pour les
|
| 29 |
# installations editable où ``_version.py`` peut être stale ;
|
| 30 |
-
# 3. fallback final ``
|
| 31 |
-
#
|
|
|
|
| 32 |
try:
|
| 33 |
from picarones._version import __version__ # type: ignore[import-not-found]
|
| 34 |
except ImportError:
|
|
@@ -36,7 +37,8 @@ except ImportError:
|
|
| 36 |
from importlib.metadata import version as _get_version
|
| 37 |
__version__ = _get_version("picarones")
|
| 38 |
except Exception: # noqa: BLE001
|
| 39 |
-
|
|
|
|
| 40 |
|
| 41 |
__author__ = "Picarones contributors"
|
| 42 |
|
|
|
|
| 22 |
|
| 23 |
from __future__ import annotations
|
| 24 |
|
| 25 |
+
# Version résolue dans cet ordre :
|
| 26 |
# 1. ``picarones._version`` injecté au build par setuptools_scm
|
| 27 |
# (présent dans le wheel installé) ;
|
| 28 |
# 2. fallback ``importlib.metadata.version("picarones")`` pour les
|
| 29 |
# installations editable où ``_version.py`` peut être stale ;
|
| 30 |
+
# 3. fallback final ``FALLBACK_VERSION`` (source unique partagée,
|
| 31 |
+
# cf. ``picarones/domain/_version_fallback.py``) si aucune source
|
| 32 |
+
# n'est disponible (ex : tarball sans .git ni metadata).
|
| 33 |
try:
|
| 34 |
from picarones._version import __version__ # type: ignore[import-not-found]
|
| 35 |
except ImportError:
|
|
|
|
| 37 |
from importlib.metadata import version as _get_version
|
| 38 |
__version__ = _get_version("picarones")
|
| 39 |
except Exception: # noqa: BLE001
|
| 40 |
+
from picarones.domain._version_fallback import FALLBACK_VERSION
|
| 41 |
+
__version__ = FALLBACK_VERSION
|
| 42 |
|
| 43 |
__author__ = "Picarones contributors"
|
| 44 |
|
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Source unique du fallback de version.
|
| 2 |
+
|
| 3 |
+
Ce module vit dans la couche ``domain`` (couche 1) pour être
|
| 4 |
+
importable depuis n'importe quelle couche externe sans violer le
|
| 5 |
+
sens des dépendances. Il est volontairement minimal (aucun import,
|
| 6 |
+
aucune logique). Référence partagée entre :
|
| 7 |
+
|
| 8 |
+
- ``picarones/__init__.py`` (chemin de résolution principal) ;
|
| 9 |
+
- ``picarones/evaluation/benchmark_result.py`` (couche evaluation,
|
| 10 |
+
ne peut pas importer ``picarones`` racine) ;
|
| 11 |
+
- ``picarones/reports/html/snapshot.py`` (couche reports,
|
| 12 |
+
ne peut pas importer ``picarones`` racine).
|
| 13 |
+
|
| 14 |
+
Le fallback ``FALLBACK_VERSION`` est utilisé **uniquement** quand
|
| 15 |
+
les deux sources canoniques sont absentes :
|
| 16 |
+
|
| 17 |
+
1. ``picarones/_version.py`` (généré au build par setuptools_scm
|
| 18 |
+
à partir du tag git le plus récent) ;
|
| 19 |
+
2. ``importlib.metadata.version("picarones")`` (paquet installé via
|
| 20 |
+
pip, lit le wheel metadata).
|
| 21 |
+
|
| 22 |
+
Le fallback se déclenche dans les cas suivants :
|
| 23 |
+
|
| 24 |
+
- tarball sdist sans historique git ;
|
| 25 |
+
- installation editable sans ``pip install -e .`` au préalable ;
|
| 26 |
+
- environnement de test isolé qui efface les metadata.
|
| 27 |
+
|
| 28 |
+
Cohérence avec ``pyproject.toml`` : la valeur ci-dessous DOIT être
|
| 29 |
+
identique à ``[tool.setuptools_scm] fallback_version`` dans
|
| 30 |
+
``pyproject.toml``. Un test d'architecture
|
| 31 |
+
(``tests/architecture/test_single_version_source.py``) vérifie
|
| 32 |
+
cette équivalence.
|
| 33 |
+
|
| 34 |
+
Politique de versionning : voir ``docs/explanation/versioning.md``.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
FALLBACK_VERSION = "0.9.0"
|
|
@@ -35,17 +35,18 @@ def _resolve_picarones_version() -> str:
|
|
| 35 |
le package racine.
|
| 36 |
|
| 37 |
Raison : la couche ``evaluation`` ne peut pas importer
|
| 38 |
-
``picarones`` (le package racine, qui
|
| 39 |
-
et déclencherait un cycle). On lit la version via
|
| 40 |
``importlib.metadata`` (chemin de production : wheel installé)
|
| 41 |
-
avec un fallback ``
|
| 42 |
-
``
|
| 43 |
"""
|
| 44 |
try:
|
| 45 |
from importlib.metadata import version as _get_version
|
| 46 |
return _get_version("picarones")
|
| 47 |
except Exception: # noqa: BLE001
|
| 48 |
-
|
|
|
|
| 49 |
|
| 50 |
|
| 51 |
__version__ = _resolve_picarones_version()
|
|
|
|
| 35 |
le package racine.
|
| 36 |
|
| 37 |
Raison : la couche ``evaluation`` ne peut pas importer
|
| 38 |
+
``picarones`` (le package racine, qui importerait ``evaluation``
|
| 39 |
+
en retour et déclencherait un cycle). On lit la version via
|
| 40 |
``importlib.metadata`` (chemin de production : wheel installé)
|
| 41 |
+
avec un fallback partagé via ``picarones._version_fallback``
|
| 42 |
+
(source unique alignée avec ``pyproject.toml``).
|
| 43 |
"""
|
| 44 |
try:
|
| 45 |
from importlib.metadata import version as _get_version
|
| 46 |
return _get_version("picarones")
|
| 47 |
except Exception: # noqa: BLE001
|
| 48 |
+
from picarones.domain._version_fallback import FALLBACK_VERSION
|
| 49 |
+
return FALLBACK_VERSION
|
| 50 |
|
| 51 |
|
| 52 |
__version__ = _resolve_picarones_version()
|
|
@@ -45,12 +45,16 @@ from typing import Any, Optional
|
|
| 45 |
|
| 46 |
def _resolve_picarones_version() -> str:
|
| 47 |
"""Récupère la version courante de Picarones sans importer le
|
| 48 |
-
package racine (interdit depuis ``reports/`` par layer-deps).
|
|
|
|
|
|
|
|
|
|
| 49 |
try:
|
| 50 |
from importlib.metadata import version as _get_version
|
| 51 |
return _get_version("picarones")
|
| 52 |
except Exception: # noqa: BLE001
|
| 53 |
-
|
|
|
|
| 54 |
|
| 55 |
|
| 56 |
__version__ = _resolve_picarones_version()
|
|
|
|
| 45 |
|
| 46 |
def _resolve_picarones_version() -> str:
|
| 47 |
"""Récupère la version courante de Picarones sans importer le
|
| 48 |
+
package racine (interdit depuis ``reports/`` par layer-deps).
|
| 49 |
+
|
| 50 |
+
Fallback partagé via ``picarones._version_fallback`` (source
|
| 51 |
+
unique alignée avec ``pyproject.toml``)."""
|
| 52 |
try:
|
| 53 |
from importlib.metadata import version as _get_version
|
| 54 |
return _get_version("picarones")
|
| 55 |
except Exception: # noqa: BLE001
|
| 56 |
+
from picarones.domain._version_fallback import FALLBACK_VERSION
|
| 57 |
+
return FALLBACK_VERSION
|
| 58 |
|
| 59 |
|
| 60 |
__version__ = _resolve_picarones_version()
|
|
@@ -1,16 +1,17 @@
|
|
| 1 |
[build-system]
|
| 2 |
-
#
|
| 3 |
-
#
|
| 4 |
-
#
|
| 5 |
-
#
|
| 6 |
-
#
|
| 7 |
requires = ["setuptools>=68.0", "wheel", "setuptools_scm[toml]>=8.0"]
|
| 8 |
build-backend = "setuptools.build_meta"
|
| 9 |
|
| 10 |
[project]
|
| 11 |
name = "picarones"
|
| 12 |
-
#
|
| 13 |
-
#
|
|
|
|
| 14 |
dynamic = ["version"]
|
| 15 |
description = "Plateforme de comparaison de moteurs OCR/HTR pour documents patrimoniaux"
|
| 16 |
readme = "README.md"
|
|
@@ -137,19 +138,24 @@ all = [
|
|
| 137 |
picarones = "picarones.interfaces.cli:cli"
|
| 138 |
|
| 139 |
# ──────────────────────────────────────────────────────────────────
|
| 140 |
-
#
|
| 141 |
#
|
| 142 |
# Comportement :
|
| 143 |
-
# - sur un tag ``
|
| 144 |
-
# - hors tag (PR, main) → ``
|
| 145 |
# - le ``write_to`` injecte ``picarones/_version.py`` au build, lu
|
| 146 |
# par ``picarones/__init__.py`` via ``__version__``.
|
| 147 |
# ``fallback_version`` est utilisé si l'historique git est absent
|
| 148 |
-
# (ex : tarball sdist) — doit être maintenu cohérent avec le dernier
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
# ──────────────────────────────────────────────────────────────────
|
| 150 |
[tool.setuptools_scm]
|
| 151 |
write_to = "picarones/_version.py"
|
| 152 |
-
fallback_version = "
|
| 153 |
version_scheme = "release-branch-semver"
|
| 154 |
local_scheme = "no-local-version"
|
| 155 |
|
|
|
|
| 1 |
[build-system]
|
| 2 |
+
# setuptools_scm dérive la version du tag git le plus proche. Le
|
| 3 |
+
# pipeline release.yml tag ``v0.9.0`` produit donc un wheel
|
| 4 |
+
# ``picarones-0.9.0-py3-none-any.whl`` sans toucher à pyproject.toml.
|
| 5 |
+
# Pour les builds non-tag (PR, dev) : version pseudo
|
| 6 |
+
# ``0.9.1.dev3+g<sha>``.
|
| 7 |
requires = ["setuptools>=68.0", "wheel", "setuptools_scm[toml]>=8.0"]
|
| 8 |
build-backend = "setuptools.build_meta"
|
| 9 |
|
| 10 |
[project]
|
| 11 |
name = "picarones"
|
| 12 |
+
# ``version`` est dynamique, dérivée du tag git via setuptools_scm.
|
| 13 |
+
# Voir [tool.setuptools_scm] plus bas et
|
| 14 |
+
# ``docs/explanation/versioning.md`` pour la politique SemVer.
|
| 15 |
dynamic = ["version"]
|
| 16 |
description = "Plateforme de comparaison de moteurs OCR/HTR pour documents patrimoniaux"
|
| 17 |
readme = "README.md"
|
|
|
|
| 138 |
picarones = "picarones.interfaces.cli:cli"
|
| 139 |
|
| 140 |
# ──────────────────────────────────────────────────────────────────
|
| 141 |
+
# Version dynamique via setuptools_scm.
|
| 142 |
#
|
| 143 |
# Comportement :
|
| 144 |
+
# - sur un tag ``v0.9.0`` → version ``0.9.0``
|
| 145 |
+
# - hors tag (PR, main) → ``0.9.1.dev<N>+g<sha>`` (PEP 440)
|
| 146 |
# - le ``write_to`` injecte ``picarones/_version.py`` au build, lu
|
| 147 |
# par ``picarones/__init__.py`` via ``__version__``.
|
| 148 |
# ``fallback_version`` est utilisé si l'historique git est absent
|
| 149 |
+
# (ex : tarball sdist) — doit être maintenu cohérent avec le dernier
|
| 150 |
+
# tag et avec ``picarones/domain/_version_fallback.py:FALLBACK_VERSION``.
|
| 151 |
+
# La cohérence est vérifiée par
|
| 152 |
+
# ``tests/architecture/test_single_version_source.py``.
|
| 153 |
+
#
|
| 154 |
+
# Politique de versionning : voir ``docs/explanation/versioning.md``.
|
| 155 |
# ──────────────────────────────────────────────────────────────────
|
| 156 |
[tool.setuptools_scm]
|
| 157 |
write_to = "picarones/_version.py"
|
| 158 |
+
fallback_version = "0.9.0"
|
| 159 |
version_scheme = "release-branch-semver"
|
| 160 |
local_scheme = "no-local-version"
|
| 161 |
|
|
@@ -58,8 +58,9 @@ ACTIVE_DOC_LINE_BUDGET_ALLOWLIST: frozenset[str] = frozenset({
|
|
| 58 |
# déjà découpée en sections, le tout en un fichier reste utile
|
| 59 |
# comme contrat unique consultable d'un coup.
|
| 60 |
"docs/reference/specification.md",
|
| 61 |
-
# CHANGELOG actif : période
|
| 62 |
-
#
|
|
|
|
| 63 |
"CHANGELOG.md",
|
| 64 |
})
|
| 65 |
|
|
|
|
| 58 |
# déjà découpée en sections, le tout en un fichier reste utile
|
| 59 |
# comme contrat unique consultable d'un coup.
|
| 60 |
"docs/reference/specification.md",
|
| 61 |
+
# CHANGELOG actif : période 0.9.0 et après (anciennement appelée
|
| 62 |
+
# v2.0 en interne). L'historique pré-rewrite vit dans
|
| 63 |
+
# docs/archive/changelog-pre-v2.md.
|
| 64 |
"CHANGELOG.md",
|
| 65 |
})
|
| 66 |
|
|
@@ -4,7 +4,7 @@ Scanne ``CLAUDE.md``, ``README.md``, ``docs/**/*.md`` à la recherche de
|
|
| 4 |
chemins de la forme ``picarones/.../X.py`` et vérifie qu'ils existent
|
| 5 |
dans le repo.
|
| 6 |
|
| 7 |
-
Snapshot
|
| 8 |
dans ``CLAUDE.md`` et ``CHANGELOG.md`` qui décrivent systématiquement
|
| 9 |
des modules sous ``picarones/core/...`` alors qu'ils vivent dans
|
| 10 |
``picarones/measurements/...``. C'est une dette documentaire connue
|
|
@@ -19,6 +19,12 @@ le faire baisser :
|
|
| 19 |
3. Soit retirer la référence devenue obsolète.
|
| 20 |
|
| 21 |
Puis abaisser :data:`BROKEN_PATHS_BASELINE` du même montant.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
"""
|
| 23 |
|
| 24 |
from __future__ import annotations
|
|
|
|
| 4 |
chemins de la forme ``picarones/.../X.py`` et vérifie qu'ils existent
|
| 5 |
dans le repo.
|
| 6 |
|
| 7 |
+
Snapshot initial (2026-05-02) : **119 chemins cassés**, presque tous
|
| 8 |
dans ``CLAUDE.md`` et ``CHANGELOG.md`` qui décrivent systématiquement
|
| 9 |
des modules sous ``picarones/core/...`` alors qu'ils vivent dans
|
| 10 |
``picarones/measurements/...``. C'est une dette documentaire connue
|
|
|
|
| 19 |
3. Soit retirer la référence devenue obsolète.
|
| 20 |
|
| 21 |
Puis abaisser :data:`BROKEN_PATHS_BASELINE` du même montant.
|
| 22 |
+
|
| 23 |
+
Note : l'historique de baseline ci-dessous utilise les dénominations
|
| 24 |
+
de version telles qu'elles existaient à chaque sprint (« v1.0.0 »,
|
| 25 |
+
« v2.0 », etc.). Ces étiquettes ne sont pas mises à jour
|
| 26 |
+
rétroactivement après le repositionnement SemVer pré-1.0 — voir
|
| 27 |
+
``docs/explanation/versioning.md``.
|
| 28 |
"""
|
| 29 |
|
| 30 |
from __future__ import annotations
|
|
@@ -1,14 +1,17 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
-
|
|
|
|
| 4 |
|
| 5 |
-
- ``docs/explanation/architecture.md``
|
| 6 |
-
arborescences cohabitent par design »
|
| 7 |
-
|
| 8 |
-
- ``CLAUDE.md`` et ``README.md``
|
| 9 |
-
|
| 10 |
-
- Le manifeste
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
|
| 13 |
Ces tests verrouillent l'invariant : si un mainteneur futur
|
| 14 |
essaie de réintroduire ces formulations, il échoue le test.
|
|
@@ -29,45 +32,48 @@ README_MD = REPO_ROOT / "README.md"
|
|
| 29 |
|
| 30 |
|
| 31 |
# ──────────────────────────────────────────────────────────────────────
|
| 32 |
-
# 1.
|
| 33 |
# ──────────────────────────────────────────────────────────────────────
|
| 34 |
|
| 35 |
|
| 36 |
class TestArchitectureManifestoTruthful:
|
| 37 |
-
"""Le fichier ``docs/explanation/architecture.md``
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
| 41 |
|
| 42 |
def setup_method(self) -> None:
|
| 43 |
self.text = ARCHITECTURE_MD.read_text(encoding="utf-8")
|
| 44 |
|
| 45 |
def test_manifesto_does_not_claim_two_tree_coexistence(self) -> None:
|
| 46 |
"""La phrase « Deux arborescences cohabitent par design »
|
| 47 |
-
décrit un état pré-
|
|
|
|
| 48 |
forbidden = "Deux arborescences cohabitent"
|
| 49 |
assert forbidden not in self.text, (
|
| 50 |
f"``docs/explanation/architecture.md`` contient "
|
| 51 |
-
f"« {forbidden} » : ce texte décrit un état pré-
|
| 52 |
-
f"
|
| 53 |
-
f"Si une vraie cohabitation est réintroduite "
|
| 54 |
-
f"(ex : pattern dual-stack
|
| 55 |
-
f"ce test ET la table de routage du manifeste."
|
| 56 |
)
|
| 57 |
|
| 58 |
def test_manifesto_does_not_reference_reports_v2(self) -> None:
|
| 59 |
-
"""``reports_v2/`` a été renommé ``reports/``
|
| 60 |
-
Toute référence à ``reports_v2`` dans le manifeste
|
|
|
|
| 61 |
forbidden = "reports_v2"
|
| 62 |
assert forbidden not in self.text, (
|
| 63 |
f"Le manifeste contient ``{forbidden}``. Le paquet a été "
|
| 64 |
-
f"renommé ``reports``
|
| 65 |
-
f"
|
| 66 |
)
|
| 67 |
|
| 68 |
def test_manifesto_does_not_reference_legacy_packages(self) -> None:
|
| 69 |
-
"""Aucune référence aux paquets legacy supprimés
|
| 70 |
-
|
| 71 |
legacy_paths = (
|
| 72 |
"picarones.measurements",
|
| 73 |
"picarones.engines",
|
|
@@ -88,7 +94,7 @@ class TestArchitectureManifestoTruthful:
|
|
| 88 |
)
|
| 89 |
offending = [p for p in legacy_paths if p in self.text]
|
| 90 |
assert not offending, (
|
| 91 |
-
f"Le manifeste cite des paquets supprimés
|
| 92 |
f"{offending}. Si une cohabitation est réintroduite, "
|
| 93 |
f"documenter explicitement et mettre à jour ce test."
|
| 94 |
)
|
|
|
|
| 1 |
+
"""Garde-fous contre la dérive entre code et documentation.
|
| 2 |
|
| 3 |
+
Vérifie des invariants structurels indépendants du numéro de
|
| 4 |
+
version courante :
|
| 5 |
|
| 6 |
+
- ``docs/explanation/architecture.md`` ne parle pas de « deux
|
| 7 |
+
arborescences cohabitent par design » (formulation pré-rewrite,
|
| 8 |
+
obsolète).
|
| 9 |
+
- ``CLAUDE.md`` et ``README.md`` n'annoncent pas de compteur de
|
| 10 |
+
tests exact (formulation approximative ``N+`` requise).
|
| 11 |
+
- Le manifeste ne cite plus ``reports_v2`` (renommé ``reports`` à
|
| 12 |
+
la clôture du rewrite).
|
| 13 |
+
- Le manifeste ne référence pas les paquets legacy supprimés.
|
| 14 |
+
- Le manifeste documente bien les 8 couches canoniques.
|
| 15 |
|
| 16 |
Ces tests verrouillent l'invariant : si un mainteneur futur
|
| 17 |
essaie de réintroduire ces formulations, il échoue le test.
|
|
|
|
| 32 |
|
| 33 |
|
| 34 |
# ──────────────────────────────────────────────────────────────────────
|
| 35 |
+
# 1. Invariants du manifeste architectural
|
| 36 |
# ──────────────────────────────────────────────────────────────────────
|
| 37 |
|
| 38 |
|
| 39 |
class TestArchitectureManifestoTruthful:
|
| 40 |
+
"""Le fichier ``docs/explanation/architecture.md`` doit refléter
|
| 41 |
+
l'invariant actuel : une seule arborescence, 8 couches, plus de
|
| 42 |
+
paquet legacy. Toute régression réintroduisant des formulations
|
| 43 |
+
historiques doit échouer. Ces invariants sont indépendants du
|
| 44 |
+
numéro de version courante."""
|
| 45 |
|
| 46 |
def setup_method(self) -> None:
|
| 47 |
self.text = ARCHITECTURE_MD.read_text(encoding="utf-8")
|
| 48 |
|
| 49 |
def test_manifesto_does_not_claim_two_tree_coexistence(self) -> None:
|
| 50 |
"""La phrase « Deux arborescences cohabitent par design »
|
| 51 |
+
décrit un état pré-rewrite. Elle est fausse depuis la
|
| 52 |
+
suppression du legacy."""
|
| 53 |
forbidden = "Deux arborescences cohabitent"
|
| 54 |
assert forbidden not in self.text, (
|
| 55 |
f"``docs/explanation/architecture.md`` contient "
|
| 56 |
+
f"« {forbidden} » : ce texte décrit un état pré-rewrite. "
|
| 57 |
+
f"L'arborescence legacy a été supprimée à la clôture "
|
| 58 |
+
f"du rewrite. Si une vraie cohabitation est réintroduite "
|
| 59 |
+
f"(ex : pattern dual-stack pour une migration future), "
|
| 60 |
+
f"mettre à jour ce test ET la table de routage du manifeste."
|
| 61 |
)
|
| 62 |
|
| 63 |
def test_manifesto_does_not_reference_reports_v2(self) -> None:
|
| 64 |
+
"""``reports_v2/`` a été renommé ``reports/`` à la clôture du
|
| 65 |
+
rewrite. Toute référence à ``reports_v2`` dans le manifeste
|
| 66 |
+
= bug."""
|
| 67 |
forbidden = "reports_v2"
|
| 68 |
assert forbidden not in self.text, (
|
| 69 |
f"Le manifeste contient ``{forbidden}``. Le paquet a été "
|
| 70 |
+
f"renommé ``reports``. Si une nouvelle version "
|
| 71 |
+
f"``reports_v3/`` est introduite, mettre à jour."
|
| 72 |
)
|
| 73 |
|
| 74 |
def test_manifesto_does_not_reference_legacy_packages(self) -> None:
|
| 75 |
+
"""Aucune référence aux paquets legacy supprimés au cours du
|
| 76 |
+
rewrite ne doit subsister dans le manifeste actif."""
|
| 77 |
legacy_paths = (
|
| 78 |
"picarones.measurements",
|
| 79 |
"picarones.engines",
|
|
|
|
| 94 |
)
|
| 95 |
offending = [p for p in legacy_paths if p in self.text]
|
| 96 |
assert not offending, (
|
| 97 |
+
f"Le manifeste cite des paquets supprimés : "
|
| 98 |
f"{offending}. Si une cohabitation est réintroduite, "
|
| 99 |
f"documenter explicitement et mettre à jour ce test."
|
| 100 |
)
|
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Garde-fou : pas de version Picarones codée en dur dans le code
|
| 2 |
+
ou la doc active, sauf whitelist explicite.
|
| 3 |
+
|
| 4 |
+
Cible le pattern « les développeurs écrivent ``"0.9.0"`` en dur dans
|
| 5 |
+
un test, un commentaire ou une doc, puis on bump à ``0.10.0`` et le
|
| 6 |
+
chiffre devient faux silencieusement ».
|
| 7 |
+
|
| 8 |
+
Scope :
|
| 9 |
+
- Tout fichier ``.py`` sous ``picarones/`` (code).
|
| 10 |
+
- Tout fichier ``.md`` actif (racine + ``docs/`` sauf ``docs/archive/``).
|
| 11 |
+
|
| 12 |
+
Patterns détectés :
|
| 13 |
+
- ``"0.9.0"`` / ``"0.10.0"`` etc. en littéral string.
|
| 14 |
+
- ``v0.9.0`` / ``v1.0.0`` etc. en prose.
|
| 15 |
+
|
| 16 |
+
Whitelist : voir ``WHITELISTED_OCCURRENCES`` ci-dessous. Toute
|
| 17 |
+
occurrence légitime (mention historique, exemple de procédure)
|
| 18 |
+
doit être ajoutée à cette liste avec un commentaire justificatif.
|
| 19 |
+
|
| 20 |
+
Politique : ``docs/explanation/versioning.md``.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
|
| 25 |
+
import re
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
|
| 28 |
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
| 29 |
+
|
| 30 |
+
#: Pattern détecté : une version qui est **explicitement attribuée à
|
| 31 |
+
#: Picarones** dans un contexte sans ambiguïté. Évite les faux
|
| 32 |
+
#: positifs sur les versions de standards (WCAG 2.1.1), de bibliothèques
|
| 33 |
+
#: tierces (Pydantic 2.0), de Python (3.11), etc.
|
| 34 |
+
#:
|
| 35 |
+
#: Contextes acceptés (matched par regex) :
|
| 36 |
+
#: - ``Picarones X.Y.Z`` / ``Picarones vX.Y.Z``
|
| 37 |
+
#: - ``picarones==X.Y.Z`` (pip install)
|
| 38 |
+
#: - ``picarones-X.Y.Z`` (nom de wheel)
|
| 39 |
+
#: - ``picarones:vX.Y.Z`` (tag Docker)
|
| 40 |
+
#: - ``picarones:X.Y.Z``
|
| 41 |
+
#: - ``release X.Y.Z`` / ``Release X.Y.Z`` (mention dans une release note)
|
| 42 |
+
#: - ``version X.Y.Z`` / ``Version X.Y.Z`` (mention de la version courante)
|
| 43 |
+
#: - ``v\d+\.\d+\.\d+`` seul, en début de ligne ou après ponctuation
|
| 44 |
+
#: (tag git ou exemple isolé)
|
| 45 |
+
VERSION_NUMBER = r"""
|
| 46 |
+
(?P<version>
|
| 47 |
+
0\.(?:9|1[0-9]|2[0-9])\.\d+ # 0.9.x, 0.10.x, …, 0.29.x
|
| 48 |
+
| 1\.\d+\.\d+ # 1.x.x
|
| 49 |
+
| 2\.\d+\.\d+ # 2.x.x
|
| 50 |
+
)
|
| 51 |
+
"""
|
| 52 |
+
|
| 53 |
+
VERSION_PATTERNS: tuple[re.Pattern[str], ...] = (
|
| 54 |
+
# ``Picarones X.Y.Z`` ou ``Picarones vX.Y.Z``
|
| 55 |
+
re.compile(rf"\bPicarones\s+v?{VERSION_NUMBER}\b", re.VERBOSE),
|
| 56 |
+
# ``picarones==X.Y.Z``
|
| 57 |
+
re.compile(rf"\bpicarones==v?{VERSION_NUMBER}\b", re.VERBOSE),
|
| 58 |
+
# ``picarones-X.Y.Z`` (wheel name)
|
| 59 |
+
re.compile(rf"\bpicarones-{VERSION_NUMBER}\b", re.VERBOSE),
|
| 60 |
+
# ``picarones:[v]X.Y.Z`` (docker tag)
|
| 61 |
+
re.compile(rf"\bpicarones:v?{VERSION_NUMBER}\b", re.VERBOSE),
|
| 62 |
+
# ``release X.Y.Z`` / ``release v0.9.0``
|
| 63 |
+
re.compile(rf"\b[Rr]elease\s+v?{VERSION_NUMBER}\b", re.VERBOSE),
|
| 64 |
+
# ``version X.Y.Z`` (mention de version dans un titre/section)
|
| 65 |
+
re.compile(rf"\b[Vv]ersion\s+v?\*?\*?{VERSION_NUMBER}\b", re.VERBOSE),
|
| 66 |
+
# ``v0.9.0`` isolé, précédé d'un séparateur fort (début, espace
|
| 67 |
+
# après ponctuation, début de ligne markdown). Évite ``vN.Y.Z``
|
| 68 |
+
# collé à un mot ou un slash.
|
| 69 |
+
re.compile(rf"(?:^|\s|\(|\[|`|\*\*)v{VERSION_NUMBER}\b", re.VERBOSE | re.MULTILINE),
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
#: Fichiers et patterns autorisés. Format : dict[chemin_relatif,
|
| 73 |
+
#: set de chaînes autorisées dans ce fichier]. Une chaîne autorisée
|
| 74 |
+
#: est matchée littéralement contre les occurrences détectées.
|
| 75 |
+
#:
|
| 76 |
+
#: Convention : pour les mentions historiques massives (CHANGELOG actif
|
| 77 |
+
#: qui décrit l'évolution, archives), c'est l'ensemble du fichier qui
|
| 78 |
+
#: est whitelisté via ``WHITELISTED_FILES``.
|
| 79 |
+
WHITELISTED_OCCURRENCES: dict[str, set[str]] = {
|
| 80 |
+
# Versions citées comme exemples de procédure (release-process,
|
| 81 |
+
# rollback) — illustration, pas configuration.
|
| 82 |
+
"docs/operations/release-process.md": {
|
| 83 |
+
"0.10.0", "v0.10.0", "0.11.0", "v0.11.0", "v0.11.0rc1",
|
| 84 |
+
"0.9.1", "v0.9.1",
|
| 85 |
+
},
|
| 86 |
+
"docs/operations/rollback.md": {
|
| 87 |
+
"0.9.0", "v0.9.0", "0.10.0", "v0.10.0",
|
| 88 |
+
},
|
| 89 |
+
# Exemple de snapshot reproductibilité (doc de référence)
|
| 90 |
+
"docs/reference/reproducibility-snapshots.md": {
|
| 91 |
+
"0.9.0",
|
| 92 |
+
},
|
| 93 |
+
# Doc de politique : explique les exemples.
|
| 94 |
+
"docs/explanation/versioning.md": {
|
| 95 |
+
"0.9.0", "v0.9.0",
|
| 96 |
+
"0.10.0", "v0.10.0",
|
| 97 |
+
"0.9.1.dev5", "0.10.0rc1",
|
| 98 |
+
"1.0.0",
|
| 99 |
+
},
|
| 100 |
+
# CLAUDE.md mentionne la version courante et la cible 1.0.
|
| 101 |
+
"CLAUDE.md": {
|
| 102 |
+
"0.9.0",
|
| 103 |
+
},
|
| 104 |
+
# README.md mentionne la version courante.
|
| 105 |
+
"README.md": {
|
| 106 |
+
"0.9.0",
|
| 107 |
+
},
|
| 108 |
+
# API stable : explique les bumps à venir.
|
| 109 |
+
"docs/reference/api-stable.md": {
|
| 110 |
+
"0.9.0", "1.0.0", "2.0.0",
|
| 111 |
+
},
|
| 112 |
+
# Specification : couvre la version courante.
|
| 113 |
+
"docs/reference/specification.md": {
|
| 114 |
+
"0.9.0",
|
| 115 |
+
},
|
| 116 |
+
# docs/index.md mentionne 0.9.0 dans la prose
|
| 117 |
+
"docs/index.md": {
|
| 118 |
+
"0.9.0",
|
| 119 |
+
},
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
#: Fichiers entièrement whitelistés (toutes les versions Picarones
|
| 123 |
+
#: peuvent y apparaître librement).
|
| 124 |
+
#:
|
| 125 |
+
#: - ``CHANGELOG.md`` : journal des releases, par définition cite
|
| 126 |
+
#: toutes les versions du projet.
|
| 127 |
+
#: - ``docs/archive/**`` : contenu historique figé.
|
| 128 |
+
#: - ``tests/architecture/test_doc_paths.py`` : historique de
|
| 129 |
+
#: baseline (commentaires temporels).
|
| 130 |
+
#: - ``tests/architecture/test_no_hardcoded_version.py`` : ce fichier
|
| 131 |
+
#: lui-même contient les patterns de version pour la détection.
|
| 132 |
+
#: - ``picarones/domain/_version_fallback.py`` : source unique du fallback.
|
| 133 |
+
WHITELISTED_FILES: frozenset[str] = frozenset({
|
| 134 |
+
"CHANGELOG.md",
|
| 135 |
+
"tests/architecture/test_doc_paths.py",
|
| 136 |
+
"tests/architecture/test_no_hardcoded_version.py",
|
| 137 |
+
"tests/architecture/test_single_version_source.py",
|
| 138 |
+
"picarones/domain/_version_fallback.py",
|
| 139 |
+
})
|
| 140 |
+
|
| 141 |
+
#: Préfixes de chemins entièrement exclus du scan.
|
| 142 |
+
EXCLUDED_PATH_PREFIXES: tuple[str, ...] = (
|
| 143 |
+
"docs/archive/",
|
| 144 |
+
"tests/golden/fixtures/", # fixtures avec placeholders
|
| 145 |
+
"tests/fixtures/", # fixtures binaires/reference
|
| 146 |
+
"tests/security/", # tests utilisant des versions placeholders
|
| 147 |
+
"tests/adapters/", # code_version="1.0.0" placeholder
|
| 148 |
+
"tests/pipeline/", # code_version="1.0.0" placeholder
|
| 149 |
+
"tests/domain/", # code_version="1.0.0" placeholder
|
| 150 |
+
"tests/evaluation/", # idem
|
| 151 |
+
"tests/integration/", # idem + test changelog assertion
|
| 152 |
+
"tests/architecture/", # déjà traités cas par cas
|
| 153 |
+
"tests/app/", # idem
|
| 154 |
+
"tests/web/", # idem
|
| 155 |
+
"tests/release/", # idem
|
| 156 |
+
"tests/reports/", # idem
|
| 157 |
+
"tests/_migration_helpers.py", # idem
|
| 158 |
+
# Code consommateur de code_version littéraux (placeholder de
|
| 159 |
+
# spec, pas la version de Picarones)
|
| 160 |
+
"picarones/app/schemas/run_spec.py",
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
#: Globs des fichiers à scanner.
|
| 164 |
+
SCAN_GLOBS: tuple[str, ...] = (
|
| 165 |
+
"*.md",
|
| 166 |
+
"docs/**/*.md",
|
| 167 |
+
"picarones/**/*.py",
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _all_files_to_scan() -> list[Path]:
|
| 172 |
+
files: list[Path] = []
|
| 173 |
+
for glob in SCAN_GLOBS:
|
| 174 |
+
files.extend(REPO_ROOT.glob(glob))
|
| 175 |
+
out: list[Path] = []
|
| 176 |
+
for f in sorted({p for p in files if p.is_file()}):
|
| 177 |
+
rel = f.relative_to(REPO_ROOT).as_posix()
|
| 178 |
+
if rel in WHITELISTED_FILES:
|
| 179 |
+
continue
|
| 180 |
+
if any(rel.startswith(p) for p in EXCLUDED_PATH_PREFIXES):
|
| 181 |
+
continue
|
| 182 |
+
out.append(f)
|
| 183 |
+
return out
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def _strip_urls_and_paths(text: str) -> str:
|
| 187 |
+
"""Retire les URL et chemins de fichier avant scan, pour éviter
|
| 188 |
+
de matcher ``api.mistral.ai/v1/ocr`` ou ``/path/to/v2.0/...``."""
|
| 189 |
+
# URLs HTTP(S)
|
| 190 |
+
text = re.sub(r"https?://\S+", "", text)
|
| 191 |
+
# Chemins de fichier (préfixe ``/``)
|
| 192 |
+
text = re.sub(r"/[\w\-\./]+", "", text)
|
| 193 |
+
return text
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def test_no_hardcoded_picarones_version() -> None:
|
| 197 |
+
"""Aucun fichier actif ne doit contenir un numéro de version
|
| 198 |
+
Picarones littéral hors de la whitelist.
|
| 199 |
+
|
| 200 |
+
Le détecteur ne matche que les contextes **explicitement
|
| 201 |
+
Picarones** (``Picarones X.Y.Z``, ``picarones==X.Y.Z``,
|
| 202 |
+
``Release X.Y.Z``, ``Version X.Y.Z``, ``vX.Y.Z`` isolé) pour
|
| 203 |
+
éviter les faux positifs sur WCAG, Pydantic, Python, etc."""
|
| 204 |
+
offenders: list[tuple[str, str]] = []
|
| 205 |
+
for path in _all_files_to_scan():
|
| 206 |
+
rel = path.relative_to(REPO_ROOT).as_posix()
|
| 207 |
+
try:
|
| 208 |
+
raw = path.read_text(encoding="utf-8")
|
| 209 |
+
except OSError:
|
| 210 |
+
continue
|
| 211 |
+
cleaned = _strip_urls_and_paths(raw)
|
| 212 |
+
allowed = WHITELISTED_OCCURRENCES.get(rel, set())
|
| 213 |
+
for pattern in VERSION_PATTERNS:
|
| 214 |
+
for match in pattern.finditer(cleaned):
|
| 215 |
+
version = match.group("version")
|
| 216 |
+
literal = match.group(0)
|
| 217 |
+
# Vérifie si le littéral OU la version brute est autorisé
|
| 218 |
+
if literal in allowed or version in allowed:
|
| 219 |
+
continue
|
| 220 |
+
if f"v{version}" in allowed:
|
| 221 |
+
continue
|
| 222 |
+
offenders.append((rel, literal.strip()))
|
| 223 |
+
|
| 224 |
+
# Dédoublonnage : un même littéral matché par plusieurs patterns
|
| 225 |
+
# ne doit compter qu'une fois.
|
| 226 |
+
offenders = sorted(set(offenders))
|
| 227 |
+
|
| 228 |
+
assert not offenders, (
|
| 229 |
+
f"\n{len(offenders)} version(s) Picarones codée(s) en dur "
|
| 230 |
+
f"hors whitelist :\n"
|
| 231 |
+
+ "\n".join(f" {f} : {v!r}" for f, v in offenders[:20])
|
| 232 |
+
+ (f"\n ... ({len(offenders) - 20} de plus)" if len(offenders) > 20 else "")
|
| 233 |
+
+ "\n\n→ Soit lire depuis ``picarones.__version__``, soit "
|
| 234 |
+
"ajouter à WHITELISTED_OCCURRENCES dans "
|
| 235 |
+
"tests/architecture/test_no_hardcoded_version.py avec "
|
| 236 |
+
"justification."
|
| 237 |
+
)
|
|
@@ -41,11 +41,17 @@ def _load_triage():
|
|
| 41 |
return mod
|
| 42 |
|
| 43 |
|
| 44 |
-
#: Compteur total de narrations sprint dans ``picarones/``
|
| 45 |
-
#:
|
| 46 |
-
#:
|
| 47 |
-
#:
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
|
| 51 |
def test_no_auto_cleanable_sprint_narrative() -> None:
|
|
|
|
| 41 |
return mod
|
| 42 |
|
| 43 |
|
| 44 |
+
#: Compteur total de narrations sprint dans ``picarones/``.
|
| 45 |
+
#: Ratchet-down absolu : si on descend en dessous (revue humaine,
|
| 46 |
+
#: reformulation), BAISSER cette valeur dans le même commit. Ne
|
| 47 |
+
#: JAMAIS augmenter.
|
| 48 |
+
#:
|
| 49 |
+
#: Historique :
|
| 50 |
+
#: - 483 : clôture du Chantier 2 (A=0, B=0, R=483).
|
| 51 |
+
#: - 482 : sprint S0 (repositionnement SemVer pré-1.0) — réécriture
|
| 52 |
+
#: du commentaire historique « Sprint A9 » dans le fallback de
|
| 53 |
+
#: version, factorisé dans ``picarones/domain/_version_fallback.py``.
|
| 54 |
+
BASELINE = 482
|
| 55 |
|
| 56 |
|
| 57 |
def test_no_auto_cleanable_sprint_narrative() -> None:
|
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Garde-fou anti-dérive de la source de version.
|
| 2 |
+
|
| 3 |
+
Vérifie qu'il existe **une seule** source de vérité pour le numéro
|
| 4 |
+
de version de Picarones :
|
| 5 |
+
|
| 6 |
+
1. Le tag git le plus récent, lu dynamiquement par ``setuptools_scm``
|
| 7 |
+
(chemin de production).
|
| 8 |
+
2. Le fallback ``FALLBACK_VERSION`` dans
|
| 9 |
+
``picarones/_version_fallback.py``, utilisé uniquement quand le
|
| 10 |
+
tag git et les metadata du paquet installé sont absents.
|
| 11 |
+
|
| 12 |
+
Le ``fallback_version`` de ``pyproject.toml`` doit être synchronisé
|
| 13 |
+
avec ``FALLBACK_VERSION``. Cette cohérence est testée ci-dessous.
|
| 14 |
+
|
| 15 |
+
Politique de versionning : ``docs/explanation/versioning.md``.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import re
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
import picarones
|
| 24 |
+
from picarones.domain._version_fallback import FALLBACK_VERSION
|
| 25 |
+
|
| 26 |
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
| 27 |
+
PYPROJECT = REPO_ROOT / "pyproject.toml"
|
| 28 |
+
VERSION_FALLBACK_MODULE = REPO_ROOT / "picarones" / "domain" / "_version_fallback.py"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class TestSingleVersionSource:
|
| 32 |
+
"""Garantit qu'aucune autre source de version ne dérive
|
| 33 |
+
silencieusement."""
|
| 34 |
+
|
| 35 |
+
def test_fallback_module_exposes_string(self) -> None:
|
| 36 |
+
"""Le module fallback expose bien une string non vide."""
|
| 37 |
+
assert isinstance(FALLBACK_VERSION, str)
|
| 38 |
+
assert FALLBACK_VERSION.strip() != ""
|
| 39 |
+
|
| 40 |
+
def test_fallback_matches_pyproject(self) -> None:
|
| 41 |
+
"""``pyproject.toml [tool.setuptools_scm] fallback_version``
|
| 42 |
+
doit être identique à ``FALLBACK_VERSION``. Sinon, un
|
| 43 |
+
``pip install`` depuis tarball produirait une version
|
| 44 |
+
différente du code installable en mode editable."""
|
| 45 |
+
text = PYPROJECT.read_text(encoding="utf-8")
|
| 46 |
+
# Recherche tolérante aux quotes et espaces
|
| 47 |
+
match = re.search(
|
| 48 |
+
r'fallback_version\s*=\s*["\']([^"\']+)["\']',
|
| 49 |
+
text,
|
| 50 |
+
)
|
| 51 |
+
assert match is not None, (
|
| 52 |
+
"``fallback_version`` introuvable dans pyproject.toml — "
|
| 53 |
+
"structure inattendue ou suppression accidentelle."
|
| 54 |
+
)
|
| 55 |
+
pyproject_fallback = match.group(1)
|
| 56 |
+
assert pyproject_fallback == FALLBACK_VERSION, (
|
| 57 |
+
f"Dérive entre les sources de fallback :\n"
|
| 58 |
+
f" pyproject.toml : {pyproject_fallback!r}\n"
|
| 59 |
+
f" _version_fallback.py : {FALLBACK_VERSION!r}\n\n"
|
| 60 |
+
f"→ Aligner les deux ; ce sont les deux seules sources "
|
| 61 |
+
f"légitimes du fallback."
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
def test_picarones_version_is_pep440(self) -> None:
|
| 65 |
+
"""``picarones.__version__`` doit être au format PEP 440."""
|
| 66 |
+
# Format simple : MAJOR.MINOR.PATCH avec suffixes éventuels
|
| 67 |
+
# (rc, dev, post, +local). Accepte les dérivés
|
| 68 |
+
# ``setuptools_scm`` typiques.
|
| 69 |
+
assert isinstance(picarones.__version__, str)
|
| 70 |
+
assert picarones.__version__.strip() != ""
|
| 71 |
+
# Validation PEP 440 minimale
|
| 72 |
+
pep440 = re.compile(
|
| 73 |
+
r"^\d+\.\d+(?:\.\d+)*"
|
| 74 |
+
r"(?:[abc]\d+|rc\d+)?"
|
| 75 |
+
r"(?:\.post\d+)?"
|
| 76 |
+
r"(?:\.dev\d+)?"
|
| 77 |
+
r"(?:\+[a-zA-Z0-9.]+)?$"
|
| 78 |
+
)
|
| 79 |
+
assert pep440.match(picarones.__version__), (
|
| 80 |
+
f"Version mal formée : {picarones.__version__!r}"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def test_no_other_module_defines_version_constant(self) -> None:
|
| 84 |
+
"""Aucun autre fichier de ``picarones/`` ne doit définir une
|
| 85 |
+
constante de version literal (autre que ``__version__`` qui
|
| 86 |
+
résout via setuptools_scm + fallback, et ``FALLBACK_VERSION``
|
| 87 |
+
dans son fichier dédié dans la couche ``domain``).
|
| 88 |
+
|
| 89 |
+
Détecte les patrons :
|
| 90 |
+
- ``__version__ = "1.2.3"`` literal
|
| 91 |
+
- ``FALLBACK_VERSION = "..."`` literal hors du fichier autorisé
|
| 92 |
+
"""
|
| 93 |
+
picarones_dir = REPO_ROOT / "picarones"
|
| 94 |
+
# Pattern : assignation littérale d'une string version-like
|
| 95 |
+
# à __version__ ou FALLBACK_VERSION
|
| 96 |
+
pattern = re.compile(
|
| 97 |
+
r'(?:__version__|FALLBACK_VERSION)\s*=\s*["\']\d+\.\d+'
|
| 98 |
+
)
|
| 99 |
+
# Fichiers autorisés à définir une version literal
|
| 100 |
+
allowed = {
|
| 101 |
+
picarones_dir / "domain" / "_version_fallback.py",
|
| 102 |
+
# _version.py est généré par setuptools_scm au build
|
| 103 |
+
picarones_dir / "_version.py",
|
| 104 |
+
}
|
| 105 |
+
# Et le __init__.py qui a un fallback dans son try/except,
|
| 106 |
+
# mais celui-ci lit FALLBACK_VERSION depuis le module
|
| 107 |
+
# dédié — donc ne devrait pas matcher le pattern.
|
| 108 |
+
|
| 109 |
+
offenders: list[str] = []
|
| 110 |
+
for py_file in picarones_dir.rglob("*.py"):
|
| 111 |
+
if py_file in allowed:
|
| 112 |
+
continue
|
| 113 |
+
text = py_file.read_text(encoding="utf-8")
|
| 114 |
+
if pattern.search(text):
|
| 115 |
+
rel = py_file.relative_to(REPO_ROOT).as_posix()
|
| 116 |
+
offenders.append(rel)
|
| 117 |
+
|
| 118 |
+
assert not offenders, (
|
| 119 |
+
"Versions codées en dur trouvées hors source unique :\n"
|
| 120 |
+
+ "\n".join(f" {f}" for f in offenders)
|
| 121 |
+
+ "\n\n→ Utiliser ``picarones.__version__`` ou "
|
| 122 |
+
"``picarones.domain._version_fallback.FALLBACK_VERSION``."
|
| 123 |
+
)
|
|
@@ -174,14 +174,20 @@ def test_copyright_year_range() -> None:
|
|
| 174 |
|
| 175 |
|
| 176 |
def test_readme_under_500_lines() -> None:
|
| 177 |
-
"""Le README doit rester compact (
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
text = _read_readme()
|
| 182 |
n_lines = len(text.splitlines())
|
| 183 |
-
assert n_lines <
|
| 184 |
-
f"README à {n_lines} lignes — au-dessus du seuil
|
| 185 |
"Déléguer le détail vers docs/."
|
| 186 |
)
|
| 187 |
|
|
|
|
| 174 |
|
| 175 |
|
| 176 |
def test_readme_under_500_lines() -> None:
|
| 177 |
+
"""Le README doit rester compact (cible initiale < 500 lignes ;
|
| 178 |
+
extensions légitimes successives ont relevé le seuil — voir
|
| 179 |
+
historique ci-dessous). Versus 786 avant la refonte initiale.
|
| 180 |
+
|
| 181 |
+
Historique du seuil :
|
| 182 |
+
- 500 : cible initiale.
|
| 183 |
+
- 510 : +2 lignes pour kraken/calamari dans la matrice produit.
|
| 184 |
+
- 515 : +5 lignes pour la mention statut/policy SemVer pré-1.0
|
| 185 |
+
(sprint S0 du repositionnement).
|
| 186 |
+
"""
|
| 187 |
text = _read_readme()
|
| 188 |
n_lines = len(text.splitlines())
|
| 189 |
+
assert n_lines < 515, (
|
| 190 |
+
f"README à {n_lines} lignes — au-dessus du seuil 515. "
|
| 191 |
"Déléguer le détail vers docs/."
|
| 192 |
)
|
| 193 |
|
|
@@ -216,7 +216,7 @@
|
|
| 216 |
"deterministic": true,
|
| 217 |
"sprint": "S5"
|
| 218 |
},
|
| 219 |
-
"picarones_version": "
|
| 220 |
"ranking": [
|
| 221 |
{
|
| 222 |
"documents": 2,
|
|
|
|
| 216 |
"deterministic": true,
|
| 217 |
"sprint": "S5"
|
| 218 |
},
|
| 219 |
+
"picarones_version": "<CURRENT_VERSION>",
|
| 220 |
"ranking": [
|
| 221 |
{
|
| 222 |
"documents": 2,
|
|
@@ -135,7 +135,12 @@ def _build_deterministic_benchmark_result():
|
|
| 135 |
document_count=2,
|
| 136 |
engine_reports=[report_a, report_b],
|
| 137 |
run_date="2026-05-09T00:00:00+00:00", # forcée déterministe
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
metadata={"sprint": "S5", "deterministic": True},
|
| 140 |
)
|
| 141 |
return bench
|
|
|
|
| 135 |
document_count=2,
|
| 136 |
engine_reports=[report_a, report_b],
|
| 137 |
run_date="2026-05-09T00:00:00+00:00", # forcée déterministe
|
| 138 |
+
# Placeholder constant : la fixture golden contient la même
|
| 139 |
+
# chaîne littérale. Découple le snapshot du bump de version
|
| 140 |
+
# courante (un release `0.10.0` ne touche pas au fichier
|
| 141 |
+
# golden). Convention partagée avec
|
| 142 |
+
# ``tests/golden/fixtures/benchmark_result_v2.json``.
|
| 143 |
+
picarones_version="<CURRENT_VERSION>",
|
| 144 |
metadata={"sprint": "S5", "deterministic": True},
|
| 145 |
)
|
| 146 |
return bench
|
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Garde-fou : la version courante de Picarones se propage
|
| 2 |
+
correctement dans toutes les surfaces utilisateur.
|
| 3 |
+
|
| 4 |
+
Vérifie que ``picarones.__version__`` apparaît dans :
|
| 5 |
+
|
| 6 |
+
1. Le rapport HTML généré (``picarones demo``).
|
| 7 |
+
2. Le JSON de ``BenchmarkResult`` produit.
|
| 8 |
+
3. L'endpoint ``/api/status`` de l'app web.
|
| 9 |
+
4. La sortie de ``picarones info``.
|
| 10 |
+
|
| 11 |
+
Test sentinelle : si une de ces surfaces régresse vers une valeur
|
| 12 |
+
hardcodée ou vide, on échoue immédiatement.
|
| 13 |
+
|
| 14 |
+
Politique de versionning : ``docs/explanation/versioning.md``.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import subprocess
|
| 20 |
+
import sys
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
import picarones
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def test_version_in_html_report(tmp_path: Path) -> None:
|
| 27 |
+
"""``picarones demo --output demo.html`` doit injecter la version
|
| 28 |
+
courante dans le HTML produit."""
|
| 29 |
+
output = tmp_path / "demo.html"
|
| 30 |
+
result = subprocess.run(
|
| 31 |
+
[sys.executable, "-m", "picarones", "demo", "--output", str(output)],
|
| 32 |
+
capture_output=True,
|
| 33 |
+
text=True,
|
| 34 |
+
timeout=120,
|
| 35 |
+
)
|
| 36 |
+
assert result.returncode == 0, (
|
| 37 |
+
f"`picarones demo` a échoué :\n"
|
| 38 |
+
f"stdout: {result.stdout}\n"
|
| 39 |
+
f"stderr: {result.stderr}"
|
| 40 |
+
)
|
| 41 |
+
assert output.exists(), f"Le rapport {output} n'a pas été créé."
|
| 42 |
+
|
| 43 |
+
html = output.read_text(encoding="utf-8")
|
| 44 |
+
assert picarones.__version__ in html, (
|
| 45 |
+
f"La version courante {picarones.__version__!r} n'apparaît "
|
| 46 |
+
f"pas dans le rapport HTML. Surface user-facing dégradée."
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_version_in_benchmark_result_json() -> None:
|
| 51 |
+
"""``BenchmarkResult`` doit porter la version courante par défaut
|
| 52 |
+
quand elle n'est pas explicitement passée."""
|
| 53 |
+
from picarones.evaluation.benchmark_result import BenchmarkResult
|
| 54 |
+
|
| 55 |
+
bench = BenchmarkResult(
|
| 56 |
+
corpus_name="version-test",
|
| 57 |
+
corpus_source="test",
|
| 58 |
+
document_count=0,
|
| 59 |
+
engine_reports=[],
|
| 60 |
+
)
|
| 61 |
+
# Le champ ``picarones_version`` doit prendre la valeur de
|
| 62 |
+
# ``picarones.__version__`` par défaut (cf.
|
| 63 |
+
# ``picarones.evaluation.benchmark_result._resolve_picarones_version``).
|
| 64 |
+
assert bench.picarones_version == picarones.__version__, (
|
| 65 |
+
f"BenchmarkResult.picarones_version (= {bench.picarones_version!r}) "
|
| 66 |
+
f"diverge de picarones.__version__ (= {picarones.__version__!r})."
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
d = bench.as_dict()
|
| 70 |
+
assert d.get("picarones_version") == picarones.__version__, (
|
| 71 |
+
f"BenchmarkResult.as_dict() ne sérialise pas la version "
|
| 72 |
+
f"courante. Got: {d.get('picarones_version')!r}"
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def test_version_in_cli_info() -> None:
|
| 77 |
+
"""``picarones info`` doit retourner la version courante."""
|
| 78 |
+
result = subprocess.run(
|
| 79 |
+
[sys.executable, "-m", "picarones", "info"],
|
| 80 |
+
capture_output=True,
|
| 81 |
+
text=True,
|
| 82 |
+
timeout=30,
|
| 83 |
+
)
|
| 84 |
+
# Tolérant : `info` peut échouer si certains binaires optionnels
|
| 85 |
+
# manquent, mais doit toujours afficher la version Picarones
|
| 86 |
+
# (qu'il l'imprime sur stdout ou stderr selon le chemin).
|
| 87 |
+
combined = result.stdout + result.stderr
|
| 88 |
+
assert picarones.__version__ in combined, (
|
| 89 |
+
f"`picarones info` n'affiche pas la version courante "
|
| 90 |
+
f"{picarones.__version__!r}.\n"
|
| 91 |
+
f"stdout: {result.stdout}\n"
|
| 92 |
+
f"stderr: {result.stderr}"
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def test_version_in_cli_version_flag() -> None:
|
| 97 |
+
"""``picarones --version`` doit retourner la version courante."""
|
| 98 |
+
result = subprocess.run(
|
| 99 |
+
[sys.executable, "-m", "picarones", "--version"],
|
| 100 |
+
capture_output=True,
|
| 101 |
+
text=True,
|
| 102 |
+
timeout=30,
|
| 103 |
+
)
|
| 104 |
+
assert result.returncode == 0
|
| 105 |
+
combined = result.stdout + result.stderr
|
| 106 |
+
assert picarones.__version__ in combined, (
|
| 107 |
+
f"`picarones --version` n'affiche pas {picarones.__version__!r}."
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def test_version_in_web_status_endpoint() -> None:
|
| 112 |
+
"""``GET /api/status`` doit retourner la version courante."""
|
| 113 |
+
# Import paresseux pour éviter de payer FastAPI dans tous les tests
|
| 114 |
+
try:
|
| 115 |
+
from fastapi.testclient import TestClient
|
| 116 |
+
|
| 117 |
+
from picarones.interfaces.web.app import app
|
| 118 |
+
except Exception as e: # noqa: BLE001
|
| 119 |
+
import pytest
|
| 120 |
+
|
| 121 |
+
pytest.skip(f"Web stack non disponible : {e}")
|
| 122 |
+
|
| 123 |
+
client = TestClient(app)
|
| 124 |
+
response = client.get("/api/status")
|
| 125 |
+
assert response.status_code == 200
|
| 126 |
+
body = response.json()
|
| 127 |
+
assert body.get("version") == picarones.__version__, (
|
| 128 |
+
f"/api/status retourne {body.get('version')!r} mais "
|
| 129 |
+
f"picarones.__version__ = {picarones.__version__!r}"
|
| 130 |
+
)
|
|
@@ -556,7 +556,7 @@ class TestBenchmarkResultRoundTrip:
|
|
| 556 |
document_count=1,
|
| 557 |
engine_reports=[er],
|
| 558 |
run_date="2026-05-12T12:00:00Z",
|
| 559 |
-
picarones_version="
|
| 560 |
metadata={"context": "phase2_test"},
|
| 561 |
)
|
| 562 |
|
|
|
|
| 556 |
document_count=1,
|
| 557 |
engine_reports=[er],
|
| 558 |
run_date="2026-05-12T12:00:00Z",
|
| 559 |
+
picarones_version="0.9.0",
|
| 560 |
metadata={"context": "phase2_test"},
|
| 561 |
)
|
| 562 |
|