Spaces:
Sleeping
Sleeping
File size: 83,816 Bytes
792973a df7146b 792973a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 | # Plan de remédiation — Picarones vers le niveau BnF / British Library
> Réponse opérationnelle à
> [`institutional-readiness-2026-05.md`](institutional-readiness-2026-05.md)
> (13 BLOCKERS, 28 MAJORS, 18 MINORS, 1 faux positif).
> Cible : 0 BLOCKER, 0 MAJOR ouvert ; CITATION valide ; audit RGAA AA passé ;
> `pip install picarones` fonctionnel ; lock file utilisé en prod.
>
> **15 sprints, 8 phases, ~58 PJ, ~12 semaines en 1 ETP.**
> Avec 2 ETP et la parallélisation décrite §2 : ~7–8 semaines.
---
## 1. Principes directeurs
Six principes structurent le séquençage. Chacun est tenu sur **toute** la
durée du plan, pas seulement à un sprint isolé.
1. **Scaffolding avant contenu.** Les *garde-fous CI* (Phase 0)
sont posés en premier pour que les sprints suivants ne puissent pas
régresser sur ce qu'on vient de corriger. Sans cela on déboguerait des
régressions au lieu d'avancer.
2. **Tests avant fixes.** Chaque correctif d'audit s'accompagne d'un test
de non-régression dans le même PR. Un fix sans test est une dette qui
se rouvrira au sprint suivant.
3. **DRY entre code et documentation.** Une assertion vérifiable (compteur
de tests, liste des moteurs, liste des commandes CLI) doit être
*générée* depuis le code, pas dupliquée à la main. Le sprint A2 pose
ce principe pour le README et l'audit.
4. **Source unique de vérité.** À chaque divergence entre deux documents
(CLAUDE.md vs README vs SPECS), on désigne le canon et on déprécie
le reste. Pas de patch parallèle.
5. **Refonte documentation produit en dernier.** Le README et SPECS
reflètent un état du code. On ne refait pas ces documents tant que
les phases 1–6 ne sont pas stabilisées, sinon on les refait deux fois.
6. **Parallélisation contrôlée.** Les phases 3 (accessibilité) et 4
(reproductibilité opérationnelle) touchent des fichiers disjoints :
templates HTML / CSS / JS d'un côté, Dockerfile / pyproject /
workflows GitHub de l'autre. Elles peuvent tourner en parallèle si
l'équipe a deux ETP. Avec un seul ETP, séquentiel.
---
## 2. Vue d'ensemble — 15 sprints en 8 phases
### Tableau récapitulatif
| Phase | Sprint | Thème | PJ | Sem. (1 ETP) | Items audit |
|---|---|---|---|---|---|
| 0 | A1 | Hardening CI | 4 | 1 | B-7, B-8, M-4, M-15, m-7, m-8, m-9 |
| 0 | A2 | Tests de cohérence documentation | 3 | 2 | (préparation A13, m-12) |
| 1 | A3 | Refactor cercles + importers | 3 | 3 | B-1, B-2, B-3, m-17 |
| 2 | A4 | Sécurité web (CSRF, /health) | 3 | 4 | B-11, M-3 |
| 2 | A5 | Concurrence et performance | 5 | 4–5 | M-13, M-14, M-16, m-10 |
| 3 | A6 | WCAG niveau A (bloquant) | 3 | 5–6 | B-9, B-10, m-3, m-4 |
| 3 | A7 | WCAG AA + i18n résiduel + a11y statement | 3 | 6 | m-1, m-2, m-5, m-6, M-9 |
| 4 | A8 | Reproductibilité opérationnelle | 3 | 5–6 *(parallèle phase 3)* | M-1, M-2, M-12, M-18, m-11, m-13, m-14 |
| 4 | A9 | Distribution PyPI + ghcr.io + releases | 3 | 7 | M-5, M-6, m-15, m-16 |
| 5 | A10 | Politiques de gouvernance | 2 | 8 | M-10, M-11 |
| 5 | A11 | Documentation institutionnelle | 5 | 8–9 | M-7, M-8, M-17 |
| 6 | A12 | Publication scientifique | 5 *(+ peer review externe)* | 9–10 | B-4, B-5, B-6 |
| 7 | A13 | Refonte README | 4 | 10–11 | B-13, M-19 à M-28, m-18, §9.3 |
| 7 | A14 | Refonte SPECS.md | 3 | 11 | B-12 |
| 8 | A15 | Audits externes (RGAA + sécurité) | 1 *(+ cycle externe)* | 11–12 | validation finale |
| 4* | A16 | Build Docker reproductible (digest + lock file) | 1 | *(post-A14)* | M-2 (clôture) |
| **TOTAL** | | | **~59 PJ** | **~12 sem. (1 ETP)** | **60 items** |
> Note : Sprint A16 a été ajouté après l'exécution d'A14 pour clôturer
> M-2 bout-en-bout (digest sha256 sur les deux ARG + lock file
> ``requirements-docker.lock`` consommé par le Dockerfile). La dette
> résiduelle ``apt-get`` non figé est tracée comme nouvel item M-29
> dans `institutional-readiness-2026-05.md` (différé post-v1.2).
### Diagramme de Gantt synthétique
```
Sem. 1 2 3 4 5 6 7 8 9 10 11 12
Phase 0 ████░░ ← CI hardening + doc consistency tests (gates)
Phase 1 ███ ← refactor cercles + importers
Phase 2 █████ ← web sécurité + concurrence/perf
Phase 3 ██████ ← a11y niveau A puis AA
Phase 4 █████ ← reproductibilité ops + distribution (parallèle)
Phase 5 ████ ← gouvernance + doc institutionnelle
Phase 6 █████ ← CITATION + JOSS draft
════════════════════ ← peer review externe (calendrier propre)
Phase 7 █████ ← refonte README + SPECS
Phase 8 ███ ← audits externes
════ ← audit RGAA externe (calendrier propre)
```
Légende : `█` = sprint actif (1 ETP) ; `═` = calendrier externe en parallèle.
### Dépendances dures
- **A1 doit précéder tout** : sans seuil de couverture, scanners de sécurité
et timeout pytest, les sprints suivants ne peuvent pas être validés en CI.
- **A2 doit précéder A13/A14** : les tests de cohérence documentation
posés en A2 deviennent les *gates* qui valident la refonte README/SPECS.
- **A3 doit précéder A12** : les violations Cercle 2→3 doivent être
réparées avant que SPECS et le papier JOSS décrivent l'architecture.
- **A8 doit précéder A12** : le snapshot de reproductibilité doit être
documenté avant qu'un papier puisse promettre la reproductibilité.
- **A9 doit précéder A12** : un papier qui pointe vers `pip install
picarones` exige que ça fonctionne.
- **Phases 1–6 doivent précéder Phase 7** : on ne refait pas le README
tant que le code n'est pas stable.
### Dépendances souples (parallélisables avec 2 ETP)
- A6+A7 (a11y) ⫽ A8+A9 (ops/distribution) — fichiers disjoints.
- A11 (doc institutionnelle) peut commencer dès la fin de A8.
- A12 démarre dès que A3+A8+A9 sont clos ; sa rédaction peut chevaucher A13/A14.
---
## 3. Phase 0 — Garde-fous (sem. 1–2)
> **Objectif** : aucun sprint ultérieur ne peut faire régresser ce qu'on
> a corrigé, parce que la CI le détecte au PR. Sans cette phase,
> chaque correctif est susceptible de se rouvrir trois sprints plus tard.
### Sprint A1 — Hardening CI (4 PJ)
**Pourquoi à cette position dans la séquence**
Les phases suivantes corrigent des dizaines de fichiers. Sans seuil de
couverture, sans scanners de sécurité, sans type-check, sans timeout
pytest et sans validation pre-commit en CI, chaque correctif est une
prise de risque silencieuse : on apprend la régression à la PR suivante,
voire en production. C'est le sprint qui rend tous les autres exécutables
de manière crédible.
**Items de l'audit résolus**
**B-7** (scanners sécurité CI), **B-8** (cov-fail-under), **M-4**
(mypy en CI), **M-15** (pytest-timeout), **m-7** (pre-commit non rejoué
en CI), **m-8** (Python 3.13 manquant de la matrice), **m-9** (API
stability defaults).
**Livrables concrets**
- `.github/workflows/ci.yml` : ajout d'un job `security` (bandit + pip-audit
+ trivy sur l'image Docker), ajout `--cov-fail-under=85` au job `tests`,
ajout `pytest-timeout=300` global, ajout d'un job `typecheck` (mypy
strict sur `picarones/core/`, lax ailleurs), ajout Python 3.13 à la
matrice (mode warning, pas bloquant 6 mois).
- `.github/workflows/precommit.yml` (nouveau) : rejoue tous les hooks
pre-commit en CI pour empêcher le bypass `--no-verify`.
- `pyproject.toml` : `[tool.mypy]` avec `strict = true` sur
`picarones.core.*`, `[tool.pytest.ini_options]` `timeout = 300` et
`timeout_method = "thread"`. Ajout `pytest-timeout` à `[dev]`.
- `picarones/py.typed` (marqueur PEP 561 pour signaler le typage aux
consommateurs externes).
- `tests/core/test_public_api_signatures.py` : pour chaque fonction
exposée par `picarones/__init__.py`, vérifier les valeurs par défaut
des paramètres via `inspect.signature` (couvre **m-9**).
**Critères d'acceptation**
- [ ] `ruff check picarones/ tests/` passe (régression nulle).
- [ ] `pytest tests/` passe avec `--cov-fail-under=85`.
- [ ] `bandit -r picarones/ -ll` passe en CI.
- [ ] `pip-audit --strict` passe en CI.
- [ ] `mypy picarones/core/ --strict` passe.
- [ ] Un PR qui supprime un default value d'une fonction publique fait
échouer la CI sur `test_public_api_signatures`.
- [ ] Un PR qui dépasse 5 minutes sur un test individuel fait échouer
la CI avec un message explicite, pas un hang.
**Risques et mitigation**
- *Risque* : seuil de couverture initialement fixé trop haut, premier PR
bloqué. *Mitigation* : mesurer le baseline avant de fixer le seuil
(`pytest --cov` sur `main`), poser le plancher 2 points en dessous.
- *Risque* : `mypy --strict` sur `core/` révèle 50 erreurs cachées.
*Mitigation* : démarrer par `core/` qui est le plus stable et le mieux
typé ; les autres cercles passent en mode `--no-strict-optional`
pendant 1 sprint, durci en A11.
---
### Sprint A2 — Tests de cohérence documentation (3 PJ)
**Pourquoi à cette position dans la séquence**
L'audit §9 montre que README liste un moteur Kraken qui n'existe pas,
documente des variables AWS sans adapter, annonce 1 242 tests au lieu
de 3 356, et liste 9 commandes CLI au lieu de 15. Ces erreurs ne
viennent pas d'incompétence — elles viennent du fait que le README
n'a aucun *gardien* automatique. Si on refait le README en A13 sans
poser ce garde-fou, il dérivera à nouveau dès le sprint suivant.
A2 pose donc des **tests de cohérence** entre la documentation
publiée et le code. Ces tests deviennent ensuite les *gates* qui
valident A13 et A14.
**Items de l'audit résolus**
Préparation des **gates** pour M-19 (compteur tests), M-23
(moteurs annoncés), M-24 (variables env sans adapter), M-25 (CLI),
M-26 (API web), M-27 (métriques), M-28 (sections rapport). **m-12**
(numérotation sprint des tests).
**Livrables concrets**
- `tests/docs/test_readme_consistency.py` (nouveau) :
- parse les tableaux Markdown du README ;
- pour chaque moteur listé, vérifie qu'un fichier
`picarones/engines/{nom_normalisé}.py` existe ;
- pour chaque commande CLI listée, vérifie qu'elle apparaît dans
`picarones --help` ;
- pour chaque endpoint web listé, vérifie qu'il apparaît dans
`app.openapi()["paths"]` ;
- pour la phrase « pytest tests/ → N passed », vérifie que N
correspond au baseline collecté par `pytest --collect-only`.
- `tests/docs/test_specs_consistency.py` (nouveau) : même approche pour
SPECS.md, avec acceptation explicite des sections marquées « Reporté »
ou « Abandonné au profit de … » (lecture des balises).
- `tests/docs/test_changelog_links.py` (nouveau) : vérifie que toute
référence `Sprint N` dans CHANGELOG correspond à une entrée existante.
- `Makefile` : cible `make doc-check` qui lance ces tests seuls (utile
pour l'auteur du README).
- `docs/developer/doc-consistency.md` (nouveau, ~80 L) : explique le
contrat (« si vous ajoutez un moteur, ajoutez la ligne dans le tableau
README ; le test la valide »).
- `tests/__init__.py` ou `conftest.py` : helper qui audite la
numérotation `test_sprintNN` et signale les trous suspects (couvre
**m-12**, sortie informative pas bloquante).
**Critères d'acceptation**
- [ ] `pytest tests/docs/` passe sur l'état actuel des docs **après
avoir corrigé le README et SPECS minimalement** (suppression
simple des fausses promesses pour ne pas bloquer le présent
sprint ; refonte complète en A13/A14).
- [ ] Un PR qui ajoute un nouvel adapter OCR sans mettre à jour le
tableau README échoue à `pytest tests/docs/test_readme_consistency.py`.
- [ ] Un PR qui supprime une commande CLI sans mettre à jour le README
échoue de la même manière.
- [ ] `make doc-check` produit un rapport lisible en < 5 s.
**Risques et mitigation**
- *Risque* : les tests sont trop stricts et bloquent un PR légitime
(ex : moteur en cours d'ajout, doc à jour ensuite). *Mitigation* :
tolérer une « exception déclarée » via une balise HTML invisible
`<!-- doc-check: skip-engine -->` dans le tableau, à utiliser avec
modération et auditée à la PR.
- *Risque* : la mise à jour minimale du README/SPECS pour faire passer
le test interfère avec la refonte A13/A14. *Mitigation* : se limiter
à *supprimer* les promesses fausses (Kraken ligne, AWS env vars), pas
à *ajouter* du contenu nouveau — ça reste pour A13.
---
## 4. Phase 1 — Hygiène architecturale (sem. 3)
> **Objectif** : ramener l'architecture à 100 % de conformité au modèle
> 3 cercles avant que la documentation produit (Phase 7) ou un papier
> JOSS (Phase 6) ne décrivent l'architecture.
### Sprint A3 — Refactor cercles + importers (3 PJ)
**Pourquoi à cette position dans la séquence**
L'audit §2 identifie deux violations Cercle 2 → Cercle 3
(`measurements/statistics.py:861` importe `report.diff_utils` ;
`measurements/difficulty.py:195` importe `report.colors`) et trois
`except Exception: pass` qui violent la règle propre du projet.
Ces dettes architecturales sont locales mais doivent être payées
*avant* la Phase 6 : un papier JOSS qui décrit une architecture en
3 cercles ne peut pas se faire mentir par le code. De plus, c'est
**le sprint le moins risqué** des phases suivantes — il rode l'équipe
sur le pattern *fix + test de non-régression* posé par A1.
**Items de l'audit résolus**
**B-1** (statistics.py:861 → core/), **B-2** (difficulty.py:195 →
report/), **B-3** (3 importers `except Exception: pass`), **m-17**
(`tests/measurements/test_sprint11_i18n_english.py` importe Cercle 3 →
déplacement en `tests/integration/`).
**Livrables concrets**
- Création de `picarones/core/diff_utils.py` (déplacement depuis
`picarones/report/diff_utils.py`). `picarones/report/diff_utils.py`
devient un ré-export trivial pour rétrocompat. `statistics.py:861`
importe désormais depuis `core`.
- Création de `picarones/report/difficulty_render.py` qui contient
`difficulty_color()`. `picarones/measurements/difficulty.py` ne
contient plus que la logique numérique.
- `picarones/extras/importers/huggingface.py:266, 416` : remplacer les
deux `except Exception: pass` par
`logger.warning("[importers/hf] <opération> a échoué (mode dégradé) : %s", e)`.
- `picarones/extras/importers/htr_united.py:448` : idem.
- Émettre un `Fact` `IMPORTER_FALLBACK_TRIGGERED` (priorité MEDIUM,
template factuel sans chiffres en dur) pour que la synthèse du
rapport mentionne l'incident à l'utilisateur final.
- Déplacement de `tests/measurements/test_sprint11_i18n_english.py`
vers `tests/integration/test_sprint11_i18n_english.py` (couvre **m-17**).
- Déplacement de `tests/measurements/test_sprint94_error_absorption.py`
vers `tests/integration/` (audit §2 MINOR 4).
- Tests de non-régression :
- `tests/core/test_diff_utils.py` : reproduire les tests de
`tests/report/test_diff_utils.py` au nouveau chemin (le doublon
sur `report/` reste pour la rétrocompat).
- `tests/measurements/test_difficulty_pure.py` : vérifier que
`picarones.measurements.difficulty` n'importe plus rien depuis
Cercle 3 (`importlib.util.find_spec` + analyse AST).
- `tests/extras/test_importer_warnings.py` : vérifier que chaque
chemin d'erreur des 3 importers loggue un warning explicite
(capturer via `caplog`).
- Garde-fou architectural : `tests/core/test_circle_dependencies.py`
(nouveau) qui parse les imports de tous les fichiers
`picarones/measurements/`, `engines/`, `llm/`, `pipelines/`,
`modules/` et **échoue** si l'un d'eux importe `picarones.report.*`,
`picarones.cli.*`, `picarones.web.*`, `picarones.extras.*`.
**Critères d'acceptation**
- [ ] `pytest tests/` passe (régression nulle ; rappel : 3 356 baseline).
- [ ] `tests/core/test_circle_dependencies.py` rapporte 0 violation.
- [ ] `grep -rn "except Exception:$" picarones/extras/importers/` renvoie
0 ligne (les 3 violations B-3 sont remplacées).
- [ ] Le rapport HTML d'un benchmark où un fallback importer a été
déclenché contient le `Fact` `IMPORTER_FALLBACK_TRIGGERED` dans la
synthèse narrative.
**Risques et mitigation**
- *Risque* : la fonction `compute_word_diff` déplacée a des callers
externes non documentés. *Mitigation* : laisser un ré-export dans
`picarones/report/diff_utils.py` avec un `DeprecationWarning` au
premier appel (suppression planifiée 2 versions plus tard).
- *Risque* : le test `test_circle_dependencies` refuse un import
légitime (par exemple un test qui mocke). *Mitigation* : limiter le
test à `picarones/`, pas à `tests/` ; ne scanner que les imports
top-level, pas les imports paresseux dans des fonctions (qui sont
acceptables pour break dependency cycles).
---
## 5. Phase 2 — Robustesse runtime (sem. 4–5)
> **Objectif** : durcir l'application web et l'orchestrateur de benchmark
> avant de pouvoir promettre une adoption institutionnelle. Une bibliothèque
> nationale ne déploie pas un service qui n'a ni protection CSRF, ni
> endpoint `/health`, ni test de concurrence.
### Sprint A4 — Sécurité web (3 PJ)
**Pourquoi à cette position dans la séquence**
A1 a posé les scanners (bandit + trivy) qui détecteront toute régression
sécurité. A3 a stabilisé l'architecture. Il est maintenant temps de
fermer les deux trous fonctionnels : **B-11** (pas de CSRF) et **M-3**
(le `HEALTHCHECK` Docker pointe vers un `/health` qui n'existe pas).
Ces deux items sont indépendants des autres phases — on les fait avant
A5 (concurrence) parce qu'A5 va ajouter des tests d'intégration qui ont
besoin du middleware CSRF stabilisé.
**Items de l'audit résolus**
**B-11** (CSRF), **M-3** (`/health` absent).
**Livrables concrets**
- `picarones/web/security.py` : ajout du middleware `csrf_middleware`
basé sur `starlette-csrf` (ou implémentation maison ~80 L : token
signé HMAC-SHA256 dans cookie `picarones_csrf` + en-tête
`X-CSRF-Token` exigé sur POST/PUT/DELETE). Activé par variable
`PICARONES_CSRF_REQUIRED=1`. Désactivé par défaut sur Space public
(pas de session authentifiée à protéger), activé d'office en mode
institutionnel.
- `picarones/web/routers/system.py` : ajouter `GET /health` qui retourne
`{"status": "ok", "version": __version__}` en < 50 ms, sans toucher à
la BD ni aux engines (vrai healthcheck Kubernetes-ready). Conserver
`/api/status` qui reste plus riche (pour le frontend).
- `Dockerfile:96` : pointer le `HEALTHCHECK` vers `/health` au lieu de
`/api/status`.
- `picarones/web/templates/_app.js` : pour chaque appel `fetch` POST,
injecter automatiquement l'en-tête `X-CSRF-Token` lu depuis le cookie.
- `tests/web/test_csrf.py` (nouveau, ~12 cas) : POST sans token →
403 ; POST avec token invalide → 403 ; POST avec token valide →
200 ; en mode public (sans `PICARONES_CSRF_REQUIRED`) → POST passe
sans token (rétrocompat HF Space).
- `tests/web/test_health.py` (nouveau, ~4 cas) : `GET /health` →
200 + JSON valide ; latence < 100 ms ; ne déclenche pas de log SQL ;
fonctionne même si la BD jobs est down.
- Documentation : `SECURITY.md` mise à jour avec un encart « Mode
institutionnel : exporter `PICARONES_CSRF_REQUIRED=1` derrière votre
reverse-proxy ».
**Critères d'acceptation**
- [ ] `pytest tests/web/` passe.
- [ ] `curl -X POST -H 'X-CSRF-Token: invalid' http://localhost:7860/api/config/save`
retourne 403 quand `PICARONES_CSRF_REQUIRED=1`.
- [ ] `docker run … && sleep 35 && docker inspect <id> | grep -c '"Status": "healthy"'`
retourne 1 (le HEALTHCHECK passe).
- [ ] `bandit -r picarones/web/` ne signale aucune nouvelle issue.
**Risques et mitigation**
- *Risque* : une intégration tierce (jq, script CI maison) qui appelle
`/api/benchmark/start` sans token casse en mode institutionnel.
*Mitigation* : documenter explicitement dans `SECURITY.md` la
procédure « générer un token via `GET /api/csrf/token` puis le passer
dans toutes les requêtes » + exemple curl.
- *Risque* : l'ajout du middleware ralentit les requêtes. *Mitigation* :
benchmark p99 sur `/api/status` avant/après ; ajouter au job `tests`
CI un check `< 200 ms p99 sur 100 requêtes`.
---
### Sprint A5 — Concurrence et performance (5 PJ)
**Pourquoi à cette position dans la séquence**
L'orchestrateur (`measurements/runner.py`, 1 019 lignes) gère
ProcessPool + ThreadPool, et la couche web utilise SSE +
SQLite WAL. L'audit §3 (M-13, M-14, M-16) signale qu'aucun test ne
couvre les cas concurrence sous charge, qu'il n'existe pas de
garde-fou anti-régression de performance, et que les rapports HTML
peuvent dépasser 200 MB sur de gros corpus. Tant que ces trois trous
ne sont pas fermés, la promesse « plateforme robuste » est
invérifiable. A5 vient après A4 parce que les tests de concurrence
appellent l'API web et ont besoin du middleware CSRF stable.
**Items de l'audit résolus**
**M-13** (tests concurrence runner + SSE), **M-14** (anti-régression
CER), **M-16** (lazy loading rapports), **m-10** (tests cloud OCR
sur erreurs HTTP 429/401/503).
**Livrables concrets**
- `tests/integration/test_runner_concurrency.py` (nouveau, ~50 cas) :
- 32 jobs concurrents avec `PICARONES_MAX_CONCURRENT_JOBS=32` ;
- épuisement du ProcessPool puis recovery ;
- mort d'un worker au milieu d'un benchmark ;
- timeout d'un doc dans un workers (le runner doit isoler) ;
- écritures SSE concurrentes sur la même file ;
- réception `Last-Event-ID` après reconnexion → replay correct.
- `tests/web/test_sqlite_concurrent_writes.py` (nouveau, ~10 cas) :
10 threads écrivent simultanément dans `JobStore` → 0 corruption,
pas de `SQLITE_BUSY` qui remonte au client.
- `tests/web/test_public_mode_hot_swap.py` (nouveau) : passage à chaud
`PICARONES_PUBLIC_MODE=0 → 1` au milieu d'un benchmark ne casse pas
les jobs en cours.
- `tests/engines/test_cloud_http_errors.py` (nouveau, ~12 cas par
cloud) : Mistral OCR / Google Vision / Azure DI mockés pour
retourner 429 (rate limit), 401 (clé invalide), 503 (indisponible),
réponse vide. Vérifier le retry exponentiel + le warning explicite.
- `tests/fixtures/reference_corpus/` (nouveau) : 10 documents libres
de droits couvrant 3 strates (médiéval, imprimé ancien, moderne) avec
GT manuelle. Source : Gallica + Wikisource (à vérifier les licences
doc par doc).
- `.github/workflows/perf_regression.yml` (nouveau) : workflow
hebdomadaire (cron) qui lance `picarones run` sur le corpus de
référence avec Tesseract + Pero, échoue si CER > 15 %. **Pas** à
chaque PR (coût) mais audit hebdo + rapport en GitHub Issue auto.
- `picarones/report/generator.py` : nouveau paramètre
`lazy_images: bool = False` (défaut conservateur). Si activé,
externalise les images dans `report-assets/{doc_id}.png` à côté du
HTML, avec `<img loading="lazy" src="report-assets/…" />`. Le HTML
reste auto-portant si on copie aussi le dossier.
- `picarones/cli/__init__.py` : option `--lazy-images` sur `picarones
report` qui propage le paramètre.
- Documentation dans `docs/user/reading-a-report.md` : encart « Pour
les corpus > 50 documents, activer `--lazy-images` ».
**Critères d'acceptation**
- [ ] `pytest tests/integration/test_runner_concurrency.py` passe en
< 3 min sur le runner CI.
- [ ] `pytest tests/web/test_sqlite_concurrent_writes.py` passe sans
flakiness sur 10 runs successifs.
- [ ] Le job `perf_regression` hebdomadaire publie un commentaire
automatique sur une GitHub Issue dédiée (`#perf-baseline`) avec
le CER mesuré.
- [ ] Un rapport généré avec `--lazy-images` sur le corpus de référence
pèse < 5 MB (vs ~50 MB sans).
- [ ] Aucun test n'a un timeout > 60 s individuel (limite douce
pour parallélisation pytest-xdist plus tard).
**Risques et mitigation**
- *Risque* : le corpus de référence introduit un coût CI permanent.
*Mitigation* : ne le lancer qu'en hebdo (cron), pas en PR. Le job
est skippable manuellement via `[skip perf]` dans le commit message
pour les release urgentes.
- *Risque* : `--lazy-images` casse l'auto-portance promise du rapport.
*Mitigation* : documenter explicitement (encart en tête du rapport
généré avec ce flag) et ajouter un sous-flag `--bundle-zip` qui
produit un `.zip` contenant HTML + dossier d'images.
---
## 6. Phase 3 — Accessibilité (sem. 5–6, parallélisable avec Phase 4)
> **Objectif** : atteindre WCAG 2.1 niveau A bloquant (A6) puis niveau
> AA cible BnF (A7). Cette phase ne touche que les templates HTML/CSS/JS
> et les fichiers i18n — fichiers disjoints de Phase 4. Avec 2 ETP,
> A6+A7 et A8+A9 tournent en parallèle. Avec 1 ETP, A6 → A7 → A8 → A9.
### Sprint A6 — WCAG niveau A bloquant (3 PJ)
**Pourquoi à cette position dans la séquence**
A1 a posé les outils CI mais aucun n'audite l'accessibilité. A4 a
durci les endpoints. Il est maintenant temps de fermer les **deux
violations de niveau A** identifiées par l'audit §3 : graphiques
Chart.js Canvas inaccessibles aux lecteurs d'écran (B-9) et absence de
lien « Aller au contenu » (B-10). Sans ces deux corrections, **aucune
déclaration de conformité RGAA n'est légalement possible**. Le sprint
ouvre aussi la voie pour A11 (declaration d'accessibilité) qui ne peut
pas être rédigée sans audit niveau A passé.
**Items de l'audit résolus**
**B-9** (Canvas charts inaccessibles, ~12 graphiques Chart.js),
**B-10** (skip-to-content), **m-3** (bouton « Réinitialiser » sans clé
i18n), **m-4** (tableaux HTML sans `scope="col"` sur `<th>`).
**Livrables concrets**
- `picarones/report/templates/_app.js` : pour chaque instanciation
Chart.js (`new Chart(canvas, …)` lignes 1062, 1102, et autres) :
- ajouter `aria-label` descriptif sur le `<canvas>` ;
- générer en parallèle un `<table>` masqué visuellement
(`.visually-hidden`) avec les mêmes données, lié au canvas par
`aria-describedby` ;
- ajouter un bouton « Voir les données » qui révèle la table à tous
(utile aussi pour la copie). Bouton masqué par défaut.
- `picarones/report/templates/_header.html` : en premier enfant du
`<body>`, ajouter
`<a href="#main" class="skip-link">{{ i18n.skip_to_content }}</a>`.
Ajouter `id="main"` sur le `<main>` du `base.html.j2`.
- `picarones/report/templates/_styles.css` : classe `.skip-link` cachée
hors `:focus` (`position:absolute; left:-9999px;` → revient à
`top:0; left:0;` au focus, contraste AA).
- Tableaux dans `view_*.html` : ajouter `scope="col"` sur tous les
`<th>` du tableau classement, du tableau NER, du tableau philologie,
du tableau levers, etc. (~12 tables totales).
- Bouton « Réinitialiser » (`_header.html:25`) : remplacer le label
hardcodé par `{{ i18n.reset_all }}`. Ajouter la clé dans
`picarones/report/i18n/{fr,en}.json`.
- `picarones/report/i18n/fr.json` et `en.json` : nouvelle clé
`skip_to_content` (« Aller au contenu » / « Skip to content ») et
`reset_all` (« Réinitialiser » / « Reset all »).
- `tests/report/test_a11y_level_a.py` (nouveau, ~15 cas) :
- chaque rapport HTML généré (demo + corpus de référence) contient
`<a class="skip-link">` en premier enfant du `<body>` ;
- chaque `<canvas>` a un `aria-label` non vide ;
- chaque `<table>` a au moins un `<th scope="col">` ;
- chaque `<canvas>` a un `<table>` jumelé via `aria-describedby` ;
- aucune chaîne hardcodée FR/EN ne traîne dans `_header.html`.
- Optionnel mais recommandé : ajouter `axe-core` (CLI) en CI sur le
rapport demo (`pytest tests/report/test_a11y_level_a.py` produit le
HTML, `axe http://file://…` audite). Si `axe` complexe à déployer
en CI, lancer manuellement à chaque release.
**Critères d'acceptation**
- [ ] `pytest tests/report/test_a11y_level_a.py` passe (15/15).
- [ ] Lecture du rapport demo par NVDA (test manuel, capté en
vidéo dans `docs/audits/a11y-tests/A6.mp4`) : chaque graphique
annonce son contenu via `aria-label` + table jumelle accessible.
- [ ] Tab depuis l'URL bar atteint le `<main>` en 1 tabulation
(skip-link visible et fonctionnel).
- [ ] `axe-core` audit sur le rapport demo signale 0 violation niveau A.
**Risques et mitigation**
- *Risque* : la table jumelle pour chaque chart double le poids HTML.
*Mitigation* : table générée à la demande via JS (`onclick="showTable(canvas_id)"`),
pas dans le DOM initial. Pour les utilisateurs AT, table rendue via
`aria-describedby` qui pointe vers une `<template>` JS-rendered.
- *Risque* : `axe-core` en CI introduit dépendance Node/Chromium.
*Mitigation* : le test pytest natif est suffisant pour bloquer les
régressions ; `axe-core` reste audit *manuel* à chaque release.
---
### Sprint A7 — WCAG AA + i18n résiduel + déclaration (3 PJ)
**Pourquoi à cette position dans la séquence**
A6 a fermé les violations de niveau A (donc *éligibilité* à une
conformité). A7 monte au niveau **AA** qui est le standard
institutionnel BnF / Service-Public.fr / European Accessibility Act,
et publie la déclaration d'accessibilité formelle. Sans A7,
l'institution ne peut pas afficher l'engagement légal de conformité
sur la home web. A7 vient juste après A6 parce que la déclaration
agrège les résultats des deux sprints.
**Items de l'audit résolus**
**m-1** (`_app.js:1087` chaîne FR hardcodée), **m-2** (`_app.js:1049`
chaîne FR hardcodée), **m-5** (palette heatmap non-daltonien-friendly),
**m-6** (nombres non localisés dans les tableaux), **M-9** (déclaration
d'accessibilité absente).
**Livrables concrets**
- `picarones/report/templates/_app.js:1087` : remplacer
`'Données d'ancrage non disponibles.'` par `I18N.no_anchor_data`.
Ligne 1049 : remplacer le fallback FR par `I18N.no_gini`.
- `picarones/report/i18n/{fr,en}.json` : ajout des clés
`no_anchor_data`, `no_gini` (la deuxième existe peut-être déjà — à
vérifier).
- `picarones/report/colors.py` : nouvelle palette daltonien-friendly
(Okabe-Ito : `#0072B2` bleu / `#E69F00` orange / `#009E73` vert /
`#CC79A7` rose / `#56B4E9` cyan). Conserver l'ancienne palette
comme `colors_classic` pour rétrocompat. Variable `report_palette`
dans `_styles.css` qui pointe vers la nouvelle par défaut, avec
override possible via `?palette=classic` dans l'URL.
- Toggle dans le panneau « Avancé » du rapport :
`<label><input type="checkbox" data-key="palette"> Mode daltonien-friendly</label>`
qui bascule la palette via classe CSS sur `<body>`.
- `picarones/report/templates/_app.js` : utiliser
`Number(value).toLocaleString(I18N.locale)` partout où un nombre
s'affiche dans un tableau (chercher `${cer}`, `${pct}`, etc.).
- `picarones/report/i18n/{fr,en}.json` : ajouter le champ
`locale: "fr-FR"` / `"en-GB"` (utilisé par `toLocaleString`).
- `ACCESSIBILITY.md` (nouveau, ~150 L à la racine) :
- engagement de conformité WCAG 2.1 AA / RGAA 4.1 ;
- méthode d'audit (interne A6+A7 + externe en A15) ;
- dérogations connues (ex : matrice de confusion Unicode reste
visuellement dense, contournement table jumelle décrit) ;
- contact référent accessibilité ;
- calendrier de réaudit (annuel).
- Lien `ACCESSIBILITY.md` ajouté en footer du rapport HTML et de la
home web.
- `tests/report/test_a11y_level_aa.py` (nouveau, ~12 cas) :
- palette daltonien-friendly active par défaut ;
- contraste cellules tableau classement ≥ 4,5:1
(pour normal text) sur les 4 tiers (excellent/acceptable/médiocre/
critique) ;
- nombres dans les tableaux respectent
`toLocaleString(report_lang)` (test : `1234567` rendu en FR
contient un espace fine ` ` ou un espace insécable ` `) ;
- aucune chaîne FR n'apparaît dans le HTML rendu en mode EN.
- `tests/web/test_accessibility_link.py` : la home web et le footer
du rapport HTML linkent vers `/accessibility` ou
`ACCESSIBILITY.md`.
**Critères d'acceptation**
- [ ] `pytest tests/report/test_a11y_level_aa.py` passe.
- [ ] Le toggle « Mode daltonien-friendly » dans le panneau Avancé
bascule la palette en < 100 ms et persiste en URL (`?palette=…`).
- [ ] Sur le rapport demo, `axe-core` signale 0 violation niveau AA
(audit manuel).
- [ ] `ACCESSIBILITY.md` publié et linké depuis le footer.
**Risques et mitigation**
- *Risque* : la nouvelle palette Okabe-Ito n'est pas appréciée
esthétiquement par l'équipe. *Mitigation* : laisser le toggle URL
`?palette=classic` qui revient à l'ancienne palette ; l'équipe
trouvera son équilibre par usage.
- *Risque* : la déclaration d'accessibilité engage légalement
(loi 2005-102 art. 47 en France). *Mitigation* : audit externe en
A15 *avant* publication officielle de la déclaration. La version
rédigée en A7 reste en mode draft (`ACCESSIBILITY.md` avec encart
« Audit externe en cours, version provisoire ») jusqu'à validation.
---
## 7. Phase 4 — Reproductibilité opérationnelle (sem. 5–7, parallélisable avec Phase 3)
> **Objectif** : passer d'une plateforme « ça compile chez moi » à une
> plateforme dont *tout* artefact est reproductible bit-à-bit ou à
> minima reconstructible à un commit/digest donné. Préalable absolu à
> la Phase 6 (publication scientifique) qui ne peut pas garantir la
> reproductibilité tant que les builds eux-mêmes ne le sont pas.
### Sprint A8 — Lock file + Docker digest + ops files (3 PJ)
**Pourquoi à cette position dans la séquence**
A1 a hardci la CI mais elle reste vulnérable à une dérive de
dépendances : aucun lock file, image Docker `python:3.11-slim` sans
digest, `requirements.txt` à la racine divergent. Toute promesse
de reproductibilité (Phase 6) tombe si on ne peut pas reconstruire
exactement l'environnement d'un benchmark. A8 vient *avant* A9
(distribution PyPI) parce qu'on ne publie pas un wheel sans avoir
verrouillé ses transitives.
**Items de l'audit résolus**
**M-1** (lock file), **M-2** (Docker digest), **M-12** (snapshots
reproductibilité sous-documentés), **M-18** (.dockerignore +
.env.example), **m-11** (versionnement testdata), **m-13**
(`requirements.txt` divergent), **m-14** (staleness pricing.yaml).
**Livrables concrets**
- Adoption de `uv` (plus rapide que pip-tools, projet Astral) pour la
génération du lock :
- `uv pip compile pyproject.toml --extra dev --extra web --extra stats
--extra llm --extra ner --output-file requirements.lock`
- `uv pip compile pyproject.toml --output-file requirements-runtime.lock`
(cœur seulement, utilisé par Docker production).
- `Dockerfile` : remplacer `pip install .` par
`uv pip sync --system requirements-runtime.lock && pip install .
--no-deps`. Épingler `FROM python:3.11.10-slim@sha256:…` (digest
obtenu via `docker pull python:3.11.10-slim && docker inspect`).
- `.dockerignore` (nouveau, ~30 L) : exclure `.git`, `tests/`, `docs/`,
`*.pyc`, `__pycache__/`, `.venv/`, `.github/`, `examples/`,
`*.spec`, `node_modules/`, `*.md` sauf `README.md` (utile pour le
message de welcome).
- `.env.example` (nouveau, ~30 L) : toutes les variables d'env
utilisées par `docker-compose.yml` et `picarones/web/security.py`
(PICARONES_PUBLIC_MODE, PICARONES_RATE_LIMIT_PER_HOUR,
PICARONES_MAX_UPLOAD_MB, PICARONES_BROWSE_ROOTS,
PICARONES_MAX_CONCURRENT_JOBS, PICARONES_CSRF_REQUIRED,
MISTRAL_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY,
GOOGLE_APPLICATION_CREDENTIALS, AZURE_DOC_INTEL_*) avec une ligne
de commentaire chacune.
- `requirements.txt` (existant) : devient un alias `-r requirements.lock`
ou supprimé avec un message dans `INSTALL.md` redirigeant vers
`pip install -e ".[dev,web]"`.
- `tests/.testdata/VERSION.yaml` (nouveau) : pour chaque corpus de test
versionnable, une entrée `{name, sha256, source_url, commit_picarones,
date_added}`. Permet de détecter une dérive accidentelle des fixtures.
- `picarones/data/pricing.yaml` : ajouter en tête `last_updated:` et
`valid_until:` (par défaut +6 mois). Le générateur de rapport
émet un `Fact` `PRICING_STALENESS_WARNING` (importance MEDIUM) si
`today > valid_until`.
- `docs/reproducibility-snapshots.md` (nouveau, ~250 L) :
- ce qu'un snapshot contient (déjà documenté en partie dans
`picarones/report/snapshot.py` mais éparpillé) ;
- comment recharger un snapshot pour rejouer un benchmark ;
- comment versionner un snapshot dans un papier
(commit picarones + digest Docker + lock file hash) ;
- exemple complet bout-en-bout avec un mini corpus.
- `.github/workflows/lock_refresh.yml` (nouveau) : workflow mensuel
(cron 1er du mois) qui régénère les locks et ouvre un PR
automatique pour validation humaine.
**Critères d'acceptation**
- [ ] `pytest tests/` passe identiquement avec `pip install
-r requirements.lock` que avec `pip install -e ".[dev,web]"`.
- [ ] `docker build .` fait sans network après le premier pull du
base image (toutes les deps sont dans le cache).
- [ ] `docker images picarones --format "{{.Size}}"` < 1.5 GB
(réduction grâce à `.dockerignore`).
- [ ] Un benchmark reproduit à 6 mois d'intervalle avec les mêmes
lock file + digest Docker + commit picarones produit un rapport
bit-à-bit identique (test bonus, lourd, fait manuellement avant
release v1.1.0).
- [ ] Le rapport demo généré 2 jours après `valid_until` du pricing
contient le `Fact` `PRICING_STALENESS_WARNING`.
**Risques et mitigation**
- *Risque* : `uv pip sync` introduit un comportement subtilement
différent de `pip` sur des deps avec extras conditionnels.
*Mitigation* : test parité installation (`pytest -k "test_install_parity"`)
qui compare `pip freeze` avant/après.
- *Risque* : le PR mensuel auto de refresh des locks crée du bruit.
*Mitigation* : labels GitHub `auto-refresh` + auto-merge si tous
les checks passent (zone configurable selon politique de l'institution).
---
### Sprint A9 — Distribution PyPI + ghcr.io + releases (3 PJ)
**Pourquoi à cette position dans la séquence**
A8 a posé la base reproductible. A9 transforme cette base en
**artefacts publiables** : wheel sur PyPI, image conteneur sur ghcr.io,
release GitHub avec changelog auto. Sans A9, le projet reste invitable
en `pip install picarones` ; un papier JOSS qui pointe vers le
projet (Phase 6) doit pouvoir citer une version installable.
**Items de l'audit résolus**
**M-5** (PyPI release pipeline), **M-6** (image conteneur immutable
publiée), **m-15** (`picarones.spec` PyInstaller hidden_imports
manuels), **m-16** (extras placeholder `[historical]` `[importers]`).
**Livrables concrets**
- `pyproject.toml` : adopter `setuptools_scm` pour la version (via tag
Git). Suppression de la version hardcodée `1.0.0`.
- `.github/workflows/release.yml` (nouveau) : déclenché sur tag `v*` :
- build sdist + wheel via `python -m build`
- test parité (`twine check`)
- publication TestPyPI
- smoke test (`pip install --index-url testpypi picarones==<version> &&
picarones --version`)
- publication PyPI via `pypa/gh-action-pypi-publish` avec OIDC
trust (pas de token long-lived)
- build image multi-arch (amd64 + arm64 pour Mac M-series) via
`docker buildx`
- push `ghcr.io/maribakulj/picarones:<version>` + `:latest`
- création GitHub Release avec corps généré depuis le CHANGELOG
(parser Keep-a-Changelog)
- `picarones.spec` (PyInstaller) : remplacer la liste
`hiddenimports` manuelle par
`from PyInstaller.utils.hooks import collect_all` et un
parcours auto. Tester via un nouveau job CI `build-exe` (non bloquant
jusqu'à v1.1.0, bloquant ensuite).
- `pyproject.toml` extras : retirer les placeholders
`historical = []` et `importers = []` (les modules sont *dans* le
package principal — l'extra n'apporte rien). Documenter dans
`CHANGELOG.md` que la séparation en packages PyPI séparés
(`picarones-historical`, `picarones-importers`) est une décision
architecturale future, pas un placeholder vide aujourd'hui.
- `tests/release/test_pypi_install.py` (nouveau) : sur un job CI
`release-smoke`, lance dans un container vide
`pip install picarones && picarones demo --output /tmp/demo.html`
et vérifie que le HTML est produit.
- `docs/operations/release-process.md` (nouveau, ~80 L) : procédure
release manuelle (tag → workflow → vérifs → annonce).
**Critères d'acceptation**
- [ ] `pip install picarones==1.1.0-rc1` depuis TestPyPI fonctionne sur
Linux + macOS + Windows.
- [ ] `docker pull ghcr.io/maribakulj/picarones:1.1.0-rc1` retourne une
image fonctionnelle qui démarre en < 30 s.
- [ ] La release GitHub `v1.1.0-rc1` est créée automatiquement, son
corps reflète la section correspondante du CHANGELOG.
- [ ] `picarones.spec` build sans erreur et l'exécutable produit lance
`picarones demo --output /tmp/demo.html` correctement.
**Risques et mitigation**
- *Risque* : conflit de nom sur PyPI (un autre projet `picarones`
existerait). *Mitigation* : vérifier en début de sprint via
`pip search` (déprécié) → `https://pypi.org/project/picarones/`.
Si conflit, négocier avec le maintainer ou renommer en
`picarones-bench`.
- *Risque* : OIDC trust setup demande des permissions GitHub Actions
spécifiques. *Mitigation* : documenter dans `release-process.md` la
procédure de setup PyPI Trusted Publisher + capture d'écran.
---
## 8. Phase 5 — Gouvernance institutionnelle (sem. 7–8)
> **Objectif** : passer du « projet maintenu par une personne » à un
> **artefact institutionnel** avec politiques publiques (gouvernance,
> conflit d'intérêt), documentation opérationnelle pour DSI BnF
> (déploiement intranet, RGPD, traduction des guides clés), et
> CODEOWNERS qui désigne les responsabilités de revue.
### Sprint A10 — Politiques de gouvernance (2 PJ)
**Pourquoi à cette position dans la séquence**
A8 et A9 ont rendu le projet *distribuable*. Avant qu'une institution
ne l'évalue pour adoption, elle doit pouvoir lire la politique de
maintenance, la divulgation de conflit d'intérêt (le projet benchmarke
des APIs cloud payantes — biais éditorial possible) et l'identification
des mainteneurs. C'est un sprint court mais **bloquant** pour toute
relation avec un service achat / juridique d'institution publique.
**Items de l'audit résolus**
**M-10** (divulgation de conflits d'intérêt), **M-11** (CODEOWNERS,
politique de maintenance, GOVERNANCE).
**Livrables concrets**
- `.github/CODEOWNERS` (nouveau) : pour chaque sous-package, désigner
le mainteneur de revue. Exemple :
```
/picarones/core/ @maribakulj
/picarones/measurements/ @maribakulj
/picarones/engines/ @maribakulj
/picarones/web/ @maribakulj
/picarones/report/ @maribakulj
/docs/ @maribakulj
/docs/case-studies/ @maribakulj # rotater quand contributeurs domain experts
```
À l'arrivée de contributeurs domain experts (paléographe, archiviste),
les mettre dans le CODEOWNERS pour les fichiers qui les concernent
(cas d'études, prompts, glossaire).
- `GOVERNANCE.md` (nouveau, ~120 L à la racine) :
- rôles : maintenance team, contributeurs occasionnels, reviewers ;
- cadence release : versions mineures mensuelles ; versions majeures
trimestrielles ; patches sécurité 72 h ;
- SLO réponse aux issues : 5 jours ouvrés pour triage initial ;
- politique de breaking changes : interdits sans tag `v2.0.0` ;
- procédure de transfert de mainteneur (BDFL → equipe → fondation
en cas de croissance).
- `CODE_OF_CONDUCT.md` (nouveau si absent ; ~40 L) : adopter
Contributor Covenant 2.1.
- `README.md` ou `GOVERNANCE.md` : section « Conflicts of interest » :
- les mainteneurs déclarent leurs affiliations académiques /
industrielles ;
- les fournisseurs cloud benchmarkés (OpenAI, Anthropic, Mistral,
Google, Azure) n'ont aucun lien capitalistique avec le projet
(à vérifier puis affirmer) ;
- le `pricing.yaml` reflète les tarifs publics observés à la date
`last_updated`, sans accord commercial avec les providers.
- `paper.md` (draft JOSS, créé en A12) : reproduira la section COI.
- `tests/docs/test_governance_files_present.py` (nouveau) : vérifie
que `CODEOWNERS`, `GOVERNANCE.md`, `CODE_OF_CONDUCT.md`,
`SECURITY.md`, `LICENSE` existent et ne sont pas vides. Garde-fou
contre suppression accidentelle.
**Critères d'acceptation**
- [ ] `pytest tests/docs/test_governance_files_present.py` passe.
- [ ] `gh repo view --json codeOfConduct,licenseInfo` retourne les
bons noms.
- [ ] La page « About » du repo GitHub est complète (description,
website, topics).
- [ ] Au moins un PR test passe par le mécanisme de review CODEOWNERS
(assigned reviewers correspondant au path).
**Risques et mitigation**
- *Risque* : le mainteneur unique ne veut pas s'engager sur des SLO
publics. *Mitigation* : formuler les SLO en mode « best effort
current » avec date de revue annuelle, pas en engagement contractuel.
---
### Sprint A11 — Documentation institutionnelle (5 PJ)
**Pourquoi à cette position dans la séquence**
A10 a publié les politiques *publiques*. A11 produit la documentation
*opérationnelle* qu'un DSI BnF lit en deuxième : guide de déploiement
intranet (au-delà du seul Space HuggingFace), politique RGPD/rétention,
déclaration d'accessibilité finalisée (issue de A6+A7), traduction
anglaise des guides utilisateur et développeur prioritaires. Sans
A11, l'institution ne peut pas mettre Picarones en production sur
ses propres infrastructures.
**Items de l'audit résolus**
**M-7** (guide déploiement institutionnel), **M-8** (politique RGPD /
rétention des données), **M-17** (traduction EN documentation
prioritaire). Validation finale de **M-9** initiée en A7
(`ACCESSIBILITY.md` passe en mode « audit interne validé, audit externe
en cours »).
**Livrables concrets**
- `docs/operations/deployment-institutional.md` (nouveau, ~250 L) :
- pré-requis (Python 3.11+, Tesseract, optionnel : SSO Shibboleth /
CAS / OIDC, BD partagée optionnelle Postgres en remplacement de
SQLite, reverse-proxy Nginx/Apache) ;
- architecture cible (mono-instance simple ; multi-instance derrière
load balancer + BD centralisée) ;
- intégration SSO via en-tête trusté `X-Remote-User` (déjà géré par
la plupart des proxies institutionnels) ;
- configuration des `PICARONES_*` variables d'env pour mode
institutionnel (CSRF activé, browse roots restreints, rate limit
aligné sur la politique interne) ;
- intégration observabilité : format de log JSON pour ELK/Loki,
métriques Prometheus exposées sur `/metrics` (à implémenter — voir
note Risque) ;
- sauvegarde / restauration de l'historique SQLite et des rapports.
- `docs/operations/data-retention-rgpd.md` (nouveau, ~150 L) :
- quelles données Picarones collecte (uploads, IPs dans le
rate-limiter, historique benchmarks) ;
- durées de rétention par défaut et configurables :
- uploads : 7 jours après dernier accès, purge auto via cron ;
- logs IP : 24 h ;
- historique benchmarks : indéfini par défaut, purge sur demande ;
- procédure d'export / suppression des données d'un usager (droit
à l'oubli) ;
- mention RGPD dans le footer web (lien vers cette page).
- `picarones/web/maintenance.py` (nouveau, ~80 L) : tâche de purge
schedulée (`asyncio.create_task` au démarrage de l'app) qui scanne
`uploads/` et supprime ce qui dépasse `PICARONES_UPLOAD_RETENTION_DAYS`
(défaut 7).
- `tests/web/test_upload_retention.py` (nouveau, ~5 cas) : un upload
daté de 8 jours est supprimé ; un upload daté de 6 jours est
conservé ; la purge n'efface pas les rapports générés.
- `ACCESSIBILITY.md` : passer en mode « audit interne validé », mettre
à jour la date de réaudit pour A15.
- Traduction prioritaire en anglais :
- `docs/user/reading-a-report.md` → `docs/user/reading-a-report.en.md`
- `docs/developer/index.md` → `docs/developer/index.en.md`
- `docs/developer/narrative-engine.md` → `.en.md`
- `docs/developer/extending-glossary.md` → `.en.md`
- `docs/developer/extending-i18n.md` → `.en.md`
- `CONTRIBUTING.md` → `CONTRIBUTING.en.md`
- chaque fichier en français reçoit un en-tête
`> 🇬🇧 [English version](file.en.md)` et réciproquement.
- `tests/docs/test_translation_parity.py` (nouveau) : vérifie que
chaque `*.md` a son `.en.md` équivalent et que les sections de
premier niveau (`##`) correspondent (titres traduits ou
identiques).
- CHANGELOG.md (cible non-rétroactive) : adopter à partir de v1.2.0
un format bilingue (sections « Ajouté / Added », « Modifié /
Changed », « Corrigé / Fixed »).
**Critères d'acceptation**
- [ ] `pytest tests/docs/test_translation_parity.py` passe (5 fichiers
traduits, 5 paires).
- [ ] Un test manuel de la purge auto sur un upload daté de J-8
confirme la suppression effective et un log
`[maintenance] purged upload <id>`.
- [ ] La home web affiche en footer les liens vers `RGPD`,
`ACCESSIBILITY`, `GOVERNANCE`.
- [ ] `docs/operations/deployment-institutional.md` est revu par au
moins un DSI partenaire (BnF, BL, KBR ou autre — sollicitation
à externaliser).
**Risques et mitigation**
- *Risque* : l'intégration Prometheus / observabilité dépasse le scope
prévu. *Mitigation* : au pire, A11 ne livre que la *documentation*
de l'intégration (`docs/operations/observability.md`) et l'instrumentation
effective passe en backlog sprint A16+.
- *Risque* : la traduction EN demande une compétence linguistique pas
toujours interne. *Mitigation* : version EN passée par DeepL
+ relecture humaine, marquée `<!-- translation: machine + human review -->`
jusqu'à validation par un anglophone natif.
---
## 9. Phase 6 — Publication scientifique (sem. 8–10)
> **Objectif** : rendre le projet **citable**. Sans CITATION.cff, sans
> DOI Zenodo, sans papier JOSS, et sans citation primaire des méthodes
> statistiques dans le code, Picarones reste un dépôt GitHub mutable
> qu'aucune thèse, aucun papier, aucune institution sérieuse ne peut
> citer comme référence stable.
### Sprint A12 — CITATION + traçabilité méthodes + draft JOSS (5 PJ + cycle externe)
**Pourquoi à cette position dans la séquence**
C'est le sprint qui exige le plus de stabilisation préalable :
- A3 (architecture propre) avant que SPECS et le papier ne décrivent
les 3 cercles ;
- A8 (lock file) avant que le papier ne promette la reproductibilité ;
- A9 (PyPI release) avant que le papier ne pointe vers `pip install
picarones` ;
- A10 (COI) avant que le papier ne déclare l'absence de conflit ;
- A11 (a11y validée, RGPD documenté) avant que la soumission ne
passe les comités d'éthique le cas échéant.
A12 démarre dès que A9 ferme. Le sprint produit la doc et le draft ;
le **cycle de revue par les pairs JOSS (8–12 semaines)** tourne en
parallèle de la suite (Phase 7).
**Items de l'audit résolus**
**B-4** (CITATION.cff + Zenodo + draft JOSS), **B-5** (références
primaires des méthodes statistiques dans le code), **B-6** (traçabilité
des profils de normalisation aux standards éditoriaux MUFI / TEI / DEAF).
**Livrables concrets**
- `CITATION.cff` (nouveau, racine) :
- format Citation File Format 1.2.0 (parsé automatiquement par
GitHub) ;
- champs : `authors` (avec ORCID), `title`, `version`, `date-released`,
`doi: 10.5281/zenodo.<id>` (assigné par Zenodo après release), `url`,
`keywords`, `license: Apache-2.0`, `repository-code`,
`preferred-citation` pointant vers le papier JOSS quand publié.
- Configuration Zenodo : activer l'intégration GitHub-Zenodo dans les
settings du repo. Une fois la release v1.1.0 publiée (sortie d'A9),
Zenodo crée un DOI immutable. Mettre à jour `CITATION.cff`.
- `paper.md` (nouveau, racine, format JOSS, ~6–8 pages soit ~600 L) :
- **Summary** (200 mots) : qu'est-ce que Picarones, à qui ça
s'adresse, en quoi c'est différent d'ocrevalUAtion / dinglehopper ;
- **Statement of need** (300 mots) : pourquoi un *banc d'essai* (pas
un atelier de production) ; pourquoi les métriques philologiques
et la neutralité éditoriale comptent pour les institutions
patrimoniales ;
- **Functionality** (1 page) : architecture en 3 cercles, registre
typé de métriques (Sprint 34), interface `BaseModule` (Sprint 33),
moteur narratif factuel anti-hallucination (Sprint 19), pipelines
composables (Sprints 63–66) ;
- **Quality control** (½ page) : 3 356 tests, ruff, scanners CI,
snapshots reproductibles, conformité WCAG AA ;
- **References** (BibTeX) : Demšar 2006, Wilcoxon 1945, Efron 1979,
MUFI v4.0, TEI P5, HTR-United, etc.
- `paper.bib` (nouveau, racine) : entries BibTeX correspondantes.
- Soumission JOSS : fork JOSS reviews repo, pre-review issue,
attente de l'attribution d'un éditeur. Cycle externe non bloquant
pour les sprints suivants.
- `picarones/measurements/statistics.py` : ajouter en-tête de module
avec les références primaires (BibTeX en commentaire) :
```python
"""Tests statistiques pour la comparaison de moteurs OCR.
Méthodes implémentées et leurs références primaires :
- Test de Wilcoxon signé : Wilcoxon, F. (1945). Individual comparisons
by ranking methods. Biometrics Bulletin, 1(6), 80–83.
- Test de Friedman + post-hoc Nemenyi : Demšar, J. (2006). Statistical
comparisons of classifiers over multiple data sets. JMLR, 7, 1–30.
- Bootstrap intervalles de confiance : Efron, B. (1979). Bootstrap
methods. Annals of Statistics, 7(1), 1–26.
- Critical Difference Diagram : Demšar 2006 (cf. ci-dessus).
"""
```
+ dans la docstring de chaque fonction publique (`compute_friedman`,
`compute_nemenyi_posthoc`, `bootstrap_ci`, etc.), ajouter une ligne
`:references: Demšar 2006 §3.2`.
- `picarones/measurements/normalization.py` : pour chaque profil
(`DIPLOMATIC_FR`, `MUFI`, `EARLY_MODERN_*`, `MEDIEVAL_*`), ajouter
en commentaire la spec source (URL stable, version, date d'extraction) :
```python
MEDIEVAL_FRENCH = {
# Source: TEI P5 §3.4 Unicode (https://tei-c.org/release/doc/...)
# MUFI v4.0 (https://mufi.info/m.php?p=mufi/specifications)
# DEAF normalization conventions (Möhren 2017)
# Date d'extraction: 2026-05-02
"ſ": "s", # long s
...
}
```
- `docs/normalization-specs.md` (nouveau, ~200 L) : tableau exhaustif
profil × spec source × date d'extraction × DOI/URL. Liens vers les
documents source. Politique de mise à jour (à chaque révision MUFI
ou TEI, ouvrir un PR avec la diff).
- `tests/measurements/test_normalization_traceability.py` (nouveau) :
pour chaque profil, vérifier qu'il existe une entrée dans
`docs/normalization-specs.md` (parsing simple).
- `picarones/report/glossary/{fr,en}.yaml` : pour les 25 entrées,
vérifier que le champ `reference` est rempli avec une citation
primaire (audit ligne par ligne ; corriger les manquants).
**Critères d'acceptation**
- [ ] `CITATION.cff` est valide selon `cffconvert --validate`.
- [ ] Le bouton « Cite this repository » apparaît sur la page GitHub
du repo et produit BibTeX + APA correct.
- [ ] DOI Zenodo `10.5281/zenodo.<id>` est attribué et le badge
apparaît dans le README (mise à jour finale en A13).
- [ ] `paper.md` est validé localement par `whedon` (validateur JOSS,
`whedon prepare picarones-paper`).
- [ ] `tests/measurements/test_normalization_traceability.py` passe.
- [ ] Une recherche `grep -rn "Demšar 2006" picarones/measurements/` retourne
au moins 3 occurrences.
**Risques et mitigation**
- *Risque* : revue JOSS demande des changements méthodologiques de fond
(par ex. ajouter une métrique ou changer une interprétation).
*Mitigation* : prévoir un sprint A12-bis dédié à ces retours, en
Phase 8 ; ne pas bloquer A13/A14 sur ce cycle externe.
- *Risque* : Zenodo intégration ne fonctionne qu'avec une release
GitHub *publique*. *Mitigation* : confirmer que le repo est public
ou rendre public en début de sprint (cohérent avec Apache-2.0
affiché).
- *Risque* : MUFI v4.1 sort pendant la rédaction → références
obsolètes en publication. *Mitigation* : `docs/normalization-specs.md`
date l'extraction explicitement ; la version implémentée reste
v4.0 avec tracking ouvert pour v4.1 en backlog.
---
## 10. Phase 7 — Refonte documentation produit (sem. 10–11)
> **Objectif** : maintenant que le code, la CI, l'a11y, l'ops, la
> gouvernance et la communication scientifique sont stabilisées, on
> refait README et SPECS *en dernier* — précisément parce qu'ils
> doivent refléter un état figé. Refaire ces documents avant aurait
> impliqué de les refaire deux fois.
### Sprint A13 — Refonte README (4 PJ)
**Pourquoi à cette position dans la séquence**
Le README est la première impression. L'audit §9.2 a identifié 13
items : markdown cassé, compteurs faux, roadmap arrêtée 75 sprints en
arrière, project structure pré-Sprint 32-34, moteurs annoncés sans
adapter, CLI/API/métriques sous-documentées de moitié à deux tiers.
A13 vient en avant-dernier parce que le contenu à publier dépend de :
- A1+A2 (gates anti-régression activés),
- A3 (architecture stabilisée),
- A6+A7 (a11y validée → on peut afficher le badge AA),
- A8+A9 (`pip install picarones` fonctionne, image ghcr.io disponible),
- A12 (CITATION.cff présent → bouton « Cite this repository » à
promouvoir dans le README).
**Items de l'audit résolus**
**B-13** (markdown des taglines cassé), **M-19** (compteur de tests),
**M-20** (Roadmap Sprint 22 → 97), **M-21** (Known Issues obsolète),
**M-22** (Project Structure pré-refactor), **M-23** (Kraken /
custom YAML annoncés sans implémentation), **M-24** (variables
`AWS_*` sans adapter), **M-25** (CLI 6/15 documentée), **M-26** (API
web 10/27), **M-27** (métriques 8/28), **M-28** (sections rapport
15/25), **m-18** (copyright, lien SPECS, prompts latin),
**§9.3** (alignement compteurs entre 3 docs).
**Livrables concrets**
- `README.md` réécrit intégralement à partir du squelette de l'existant,
en respectant les sections suivantes (et seulement celles-ci, pour
ne pas reproduire l'enflure originelle) :
1. **Header** (titre, taglines bilingues fermées correctement, badges
CI / Python / License / DOI Zenodo / PyPI / HF Space).
2. **What is Picarones** (paragraphe de 100 mots, FR puis EN — pas
de duplication de section).
3. **Use case** (paragraphe d'archive/library, 80 mots).
4. **Quick start** (3 commandes : `pip install picarones`,
`picarones demo`, `picarones serve`).
5. **Installation** (renvoie à `INSTALL.md` pour le détail).
6. **Documentation** (table des renvois vers `docs/user/`,
`docs/developer/`, `docs/operations/`).
7. **Citation** (BibTeX généré depuis CITATION.cff + DOI Zenodo).
8. **License** (Apache 2.0).
- **Tableau « Supported engines »** auto-généré par un script
`scripts/gen_readme_tables.py` (nouveau) qui lit
`picarones/engines/__init__.py` et produit la liste à partir du
registre. Idem pour la liste des commandes CLI (lit
`picarones --help`) et la liste des endpoints (lit
`app.openapi()`). Ces tableaux sont insérés dans le README via des
balises HTML invisibles `<!-- generated:engines -->` /
`<!-- /generated:engines -->`. Régénération en CI à chaque PR via
un job qui échoue si le contenu généré diffère du contenu commité.
- **Section « Heritage-specific metrics »** : 3 sous-sections
(« Métriques classiques OCR/HTR », « Métriques philologiques »,
« Métriques de comparaison et décision »), chacune avec 3 à 5
bullets et un lien vers `docs/views.md` pour le détail. Plus de
liste linéaire de 28 items.
- **Section « Interactive HTML Report »** : screenshot du rapport
demo (image dans `docs/assets/report-screenshot.png`, taille < 200 KB)
+ liste structurée des 25 sections du rapport regroupées en 5
familles (synthèse, classement, vues thématiques, analyses
statistiques, panneaux interactifs).
- **Roadmap** : tableau condensé des sprints **par phase**, pas
individuellement. Trois colonnes : phase / focus / état. Détail
technique renvoyé vers `CHANGELOG.md` et `docs/roadmap/`.
- **Known Issues** : suppression intégrale. Cette section devient
caduque (le présent plan de remédiation et l'audit la remplacent).
Note de redirection : « Voir
[`docs/audits/`](docs/audits/) pour les audits en cours ».
- **Project Structure** : régénéré à partir du repo réel via un
script `scripts/gen_project_structure.py` (nouveau). Insertion via
`<!-- generated:structure -->`.
- Footer : `Copyright 2024–2026 Picarones contributors`. Lien
RGPD / Accessibility / Governance / Contributing.
- `tests/docs/test_readme_consistency.py` (déjà créé en A2) doit
passer **strictement** sur le nouveau README — tous les moteurs,
toutes les commandes, tous les endpoints, tous les compteurs sont
vérifiés.
- `tests/docs/test_readme_dual_lang.py` (nouveau) : la version FR et
la version EN ont des sections de premier niveau qui se
correspondent.
**Critères d'acceptation**
- [ ] `pytest tests/docs/test_readme_consistency.py` passe (0
divergence entre tableau README et code).
- [ ] Les badges CI / Codecov / PyPI / DOI / HF Space sont tous
verts au moment de la PR de refonte.
- [ ] Le nouveau README compte < 400 lignes (vs 786 actuelles, en
grande partie déléguées à `docs/`).
- [ ] Le markdown des taglines est correct, validé par
`markdown-link-check` ou équivalent.
- [ ] Un test manuel de rendu sur GitHub ET sur HuggingFace Space
affiche un README propre, sans `> **` jamais fermé.
**Risques et mitigation**
- *Risque* : la génération auto des tableaux casse à un PR futur si
un moteur n'a pas la bonne shape. *Mitigation* : le script de
génération échoue lui-même en CI avant l'étape de comparaison,
avec un message explicite (« Engine `xyz` manque le champ
`display_name` »).
- *Risque* : le screenshot du rapport demo prend de la place dans le
repo. *Mitigation* : 200 KB plafonné par pre-commit (déjà actif
via `check-added-large-files --maxkb=500`). Image optimisée
via `pngquant`.
---
### Sprint A14 — Refonte SPECS.md (3 PJ)
**Pourquoi à cette position dans la séquence**
SPECS.md (Mars 2025, addendum Sprints 16-30) est désynchronisé d'~75
sprints. L'audit §9.1 identifie 9 promesses non tenues sans deprecation
et ~25 modules majeurs ajoutés invisibles dans SPECS. A14 vient *après*
A13 parce que :
- le README est la première lecture et doit être impeccable avant
qu'on touche au document plus technique ;
- A13 a posé le pattern de génération auto des tableaux que SPECS
va réutiliser pour ses listes de moteurs / métriques ;
- A12 a publié les références primaires des méthodes statistiques —
SPECS peut maintenant les citer correctement.
**Items de l'audit résolus**
**B-12** (SPECS à refondre intégralement, 9 promesses non tenues +
25 modules ajoutés non documentés).
**Livrables concrets**
- `SPECS.md` réécrit intégralement, version 2.0 datée mai 2026,
reflétant strictement le code réel, structuré comme suit :
1. **Vision et positionnement** : philosophie banc d'essai
(pas atelier), neutralité éditoriale, contribution scientifique
du projet (registres typés, narrative anti-hallucination,
interface BaseModule).
2. **Architecture** : diagramme des 3 cercles à jour
(Cercle 1 = 7 modules, Cercle 2 = ~70, Cercle 3 = ~50), règle
de dépendance, registre typé Sprint 34, interface BaseModule
Sprint 33, GT multi-niveaux Sprint 32.
3. **Modules fonctionnels** : 6 sous-sections (Corpus, Adaptateurs
OCR, Pipelines composables, Métriques, Rapport, Interface).
Chacune décrit ce qui existe *aujourd'hui*, sans projection.
4. **Métriques** : tableau exhaustif des 28+ métriques avec leur
statut (« stable »/« expérimental »), spécification exacte,
citation primaire, jonction de type (TEXT, ALTO, etc.), limites
connues.
5. **Modes pipeline** : `zero_shot`, `post_correction_texte`,
`post_correction_image_texte`, `pipeline_composable_yaml`
(Sprint 70).
6. **Sécurité institutionnelle** : récap des garde-fous
(PICARONES_PUBLIC_MODE, CSRF, zip-slip, defusedxml, rate limit,
validation Pillow).
7. **Reproductibilité** : snapshots, lock file, digest Docker.
8. **Limites assumées et non-fonctionnalités** : liste explicite
de ce que Picarones **ne fait pas et ne fera pas** dans la
v1.x — par exemple :
- pas de recommandation prescriptive (pivot philosophique vs
SPECS v1) ;
- pas d'export PDF (CSV + JSON suffisent ; export PDF reporté
car coût de maintenance disproportionné) ;
- pas d'adapter Kraken / AWS Textract / Calamari / OCRopus4
intégré (raisons : maintenance par adapter ~50 PJ ; ouverture
en plugins externes prévue Sprint 97+) ;
- pas de moteur custom YAML (refondu en pipelines composables
Sprint 63) ;
- pas de k-means clustering automatique des erreurs (taxonomie
discrète + co-occurrence Jaccard couvrent l'usage) ;
- pas de dataset curé livré avec le projet (philosophie « banc
d'essai sur votre golden dataset »).
9. **Roadmap d'évolution 2026** : pointe vers
`docs/roadmap/evolution-2026.md`.
- Tableau de migration v1 → v2 SPECS (annexe) : pour chaque promesse
v1 non tenue, ligne « v1 disait X / v2 documente Y / raison Z ».
Permet à un lecteur qui avait lu v1 de comprendre ce qui a changé.
- `tests/docs/test_specs_consistency.py` (déjà créé en A2) doit
passer.
- Lien dans le README (A13) : « Pour la spécification fonctionnelle
et technique complète, voir [SPECS.md](SPECS.md) ».
**Critères d'acceptation**
- [ ] `pytest tests/docs/test_specs_consistency.py` passe.
- [ ] Aucune section de SPECS ne décrit une fonctionnalité absente
du code.
- [ ] Toute fonctionnalité majeure citée dans le CHANGELOG depuis
Sprint 30 est mentionnée dans SPECS v2.
- [ ] La section « Limites assumées » est non vide et liste les 9+
promesses v1 explicitement abandonnées avec leur raison.
**Risques et mitigation**
- *Risque* : la section « Limites assumées » est lue comme une
régression par un primo-lecteur. *Mitigation* : reformuler
positivement (« choix éditoriaux du projet ») et expliquer le
pivot vers la philosophie banc d'essai en intro.
- *Risque* : SPECS.md devient un duplicata de CLAUDE.md. *Mitigation* :
garder SPECS *fonctionnel et tourné public* (vocabulaire
bibliothécaire, exemples patrimoniaux), CLAUDE.md *technique et
tourné contributeur*. Les deux docs ne se chevauchent pas.
---
## 11. Phase 8 — Validation externe (sem. 11–12 + calendrier externe)
> **Objectif** : valider l'ensemble par des tiers indépendants. C'est
> ce qui transforme l'auto-déclaration en certification.
### Sprint A15 — Audits externes (1 PJ interne + cycle externe)
**Pourquoi à cette position dans la séquence**
Le travail interne est terminé. A15 est volontairement court côté
équipe (1 PJ) parce que la valeur vient des **prestataires externes** :
- audit RGAA externe (cabinet d'a11y, ~3 semaines après contractualisation) ;
- audit sécurité externe (pentest léger, ~2 semaines) ;
- finalisation de la revue JOSS (commencée en A12, ~12 semaines
cumulées au total).
A15 est purement coordination + intégration des retours.
**Items de l'audit résolus**
Validation finale de **B-9, B-10, M-9** (audit RGAA externe),
**B-7, B-11** (audit sécurité externe), **B-4, B-5** (acceptation JOSS).
**Livrables concrets**
- Sélection et contractualisation d'un cabinet RGAA (par ex.
Access42, Atalan, Tanaguru en France ; Tetralogical au UK ; ou
service interne BnF si disponible). Cahier des charges :
audit WCAG 2.1 AA sur le rapport HTML demo + l'interface web.
Livrable attendu : rapport d'audit + déclaration de conformité
signée.
- Sélection et contractualisation d'un audit sécurité (par ex. Synacktiv,
Ambionics, ou équivalent BL-side). Cahier des charges :
pentest application web (focus authentification/CSRF, file upload,
injection, RCE), revue de la chaîne de build (CI scanners + image
Docker).
- `ACCESSIBILITY.md` : intégrer les retours RGAA, supprimer la
mention « audit externe en cours ».
- `SECURITY.md` : intégrer les retours pentest, ajouter la date de
prochain réaudit (annuel).
- Réponse aux reviewers JOSS : un PR par retour majeur, intégré au
paper. Tour de rév normalement court car le draft a été préparé
rigoureusement en A12.
- `docs/audits/external-audits-2026/` (nouveau dossier) :
- `rgaa-audit-2026-MM.pdf` (rapport scanné/PDF du cabinet) ;
- `pentest-2026-MM.md` (résumé public, le rapport complet reste
confidentiel) ;
- `joss-review-correspondence.md` (résumé public des échanges).
- Annonce publique sur la home web et le README v2.1 : badges
« WCAG 2.1 AA conformité totale » et « JOSS published » avec
liens.
**Critères d'acceptation**
- [ ] Audit RGAA externe rapporte ≥ 95 % de critères AA conformes
(le 5 % résiduel listé en dérogations dans `ACCESSIBILITY.md`).
- [ ] Audit sécurité externe ne signale aucune vulnérabilité de
sévérité HIGH / CRITICAL.
- [ ] Le papier JOSS est accepté (`accepted by JOSS` issue closed).
- [ ] Le DOI JOSS est ajouté au CITATION.cff comme
`preferred-citation`.
**Risques et mitigation**
- *Risque* : audit RGAA trouve un bloqueur niveau A imprévu (par ex.
une matrice de confusion Unicode jugée non navigable au clavier).
*Mitigation* : sprint A15-bis dédié à la remédiation, planifié en
buffer fin de Phase 8.
- *Risque* : reviewers JOSS demandent une fonctionnalité scientifique
manquante (par ex. métrique nouvelle). *Mitigation* : sprint
A12-bis (cf. A12 risques), buffer Phase 8.
- *Risque* : prestataire externe indisponible. *Mitigation* :
démarcher 2 candidats par audit et choisir le premier qui répond
dans le délai.
---
## 12. Matrice de couverture
Chacun des **59 items** identifiés par l'audit est mappé à un sprint.
Cette matrice est la garantie d'exhaustivité : si un item n'apparaît
pas ici, le plan a un trou.
### Bloqueurs (13/13 couverts)
| ID | Item | Sprint | Livrable spécifique |
|---|---|---|---|
| B-1 | Violation Cercle 2→3 (`statistics.py:861`) | A3 | `core/diff_utils.py` créé, ré-export rétrocompat |
| B-2 | Violation Cercle 2→3 (`difficulty.py:195`) | A3 | `report/difficulty_render.py` créé |
| B-3 | 3 `except Exception: pass` importers | A3 | `logger.warning` + Fact `IMPORTER_FALLBACK_TRIGGERED` |
| B-4 | Pas de CITATION.cff / JOSS | A12 | `CITATION.cff` + Zenodo DOI + `paper.md` |
| B-5 | Méthodes statistiques non citées | A12 | En-tête module `statistics.py` + `:references:` par fonction |
| B-6 | Profils normalisation non tracés | A12 | `docs/normalization-specs.md` + commentaires inline |
| B-7 | Aucun scanner sécurité CI | A1 | Job `security` (bandit + pip-audit + trivy) |
| B-8 | Pas de `--cov-fail-under` | A1 | `--cov-fail-under=85` dans `ci.yml` |
| B-9 | Canvas Chart.js inaccessibles | A6 | `aria-label` + `<table>` jumelle + bouton |
| B-10 | Pas de skip-to-content | A6 | `<a class="skip-link">` dans `_header.html` |
| B-11 | Pas de CSRF | A4 | Middleware `csrf_middleware` + tests |
| B-12 | SPECS désynchronisé | A14 | Refonte intégrale v2.0 |
| B-13 | Markdown taglines README cassé | A13 | Refonte README intégrale |
### Majors (28/28 couverts)
| ID | Item | Sprint | Livrable spécifique |
|---|---|---|---|
| M-1 | Lock file absent | A8 | `requirements.lock` + `requirements-runtime.lock` via `uv` |
| M-2 | Image Docker non épinglée | A8 | `FROM python:3.11.10-slim@sha256:…` |
| M-3 | `/health` absent | A4 | `GET /health` dans `system.py` |
| M-4 | Pas de mypy en CI | A1 | Job `typecheck` (`core/` strict, ailleurs lax) |
| M-5 | Pas de release PyPI | A9 | `release.yml` + setuptools_scm + OIDC |
| M-6 | Image conteneur non publiée | A9 | Push ghcr.io multi-arch dans `release.yml` |
| M-7 | Pas de guide déploiement institutionnel | A11 | `docs/operations/deployment-institutional.md` |
| M-8 | Pas de politique RGPD | A11 | `docs/operations/data-retention-rgpd.md` + purge auto |
| M-9 | Pas de déclaration a11y | A7 + A11 + A15 | `ACCESSIBILITY.md` (draft A7, draft validé A11, finalisé A15) |
| M-10 | Pas de COI | A10 | Section dans `GOVERNANCE.md` + `paper.md` |
| M-11 | Pas de CODEOWNERS / governance | A10 | `.github/CODEOWNERS` + `GOVERNANCE.md` + `CODE_OF_CONDUCT.md` |
| M-12 | Snapshots reproductibilité sous-doc | A8 | `docs/reproducibility-snapshots.md` |
| M-13 | Tests concurrence runner+SSE | A5 | `test_runner_concurrency.py` + `test_sqlite_concurrent_writes.py` |
| M-14 | Pas d'anti-régression CER | A5 | `perf_regression.yml` cron hebdo + corpus de référence |
| M-15 | Pas de timeout pytest | A1 | `[tool.pytest.ini_options] timeout = 300` |
| M-16 | Pas de lazy loading rapports | A5 | Option `--lazy-images` dans `picarones report` |
| M-17 | Doc déséquilibrée FR/EN | A11 | 5 fichiers traduits + `test_translation_parity.py` |
| M-18 | `.dockerignore` + `.env.example` | A8 | Deux fichiers créés |
| M-19 | Compteur tests faux × 3 | A13 | Génération auto via `gen_readme_tables.py` |
| M-20 | Roadmap arrêtée Sprint 22 | A13 | Roadmap condensée par phase + lien CHANGELOG |
| M-21 | Known Issues obsolète | A13 | Section supprimée + redirection `docs/audits/` |
| M-22 | Project Structure trompeuse | A13 | `gen_project_structure.py` + insertion balisée |
| M-23 | Kraken / customYAML annoncés | A13 | Suppression du tableau (statut documenté en SPECS A14) |
| M-24 | Variables `AWS_*` sans adapter | A13 | Suppression des 3 lignes |
| M-25 | CLI sous-documentée | A13 | Génération auto via `picarones --help` |
| M-26 | API web sous-documentée | A13 | Génération auto via `app.openapi()` |
| M-27 | Métriques sous-vendues | A13 | 3 sous-sections + lien `docs/views.md` |
| M-28 | Sections rapport sous-vendues | A13 | Liste structurée 5 familles + screenshot |
### Mineurs (18/18 couverts)
| ID | Item | Sprint | Livrable spécifique |
|---|---|---|---|
| m-1 | `_app.js:1087` chaîne FR hardcodée | A7 | `I18N.no_anchor_data` |
| m-2 | `_app.js:1049` chaîne FR hardcodée | A7 | `I18N.no_gini` |
| m-3 | Bouton « Réinitialiser » sans i18n | A6 | `i18n.reset_all` |
| m-4 | Tableaux sans `scope="col"` | A6 | Audit + ajout sur ~12 tables |
| m-5 | Palette non daltonien-friendly | A7 | Palette Okabe-Ito + toggle |
| m-6 | Nombres non localisés | A7 | `toLocaleString(I18N.locale)` |
| m-7 | Pre-commit non rejoué en CI | A1 | `.github/workflows/precommit.yml` |
| m-8 | Pas de Python 3.13 | A1 | Matrice `["3.11", "3.12", "3.13"]` |
| m-9 | API stability defaults | A1 | `test_public_api_signatures.py` |
| m-10 | Tests cloud OCR sans HTTP errors | A5 | `test_cloud_http_errors.py` (12×3 cas) |
| m-11 | Versionnement testdata | A8 | `tests/.testdata/VERSION.yaml` |
| m-12 | Numérotation sprint des tests | A2 | Helper `conftest.py` informatif |
| m-13 | `requirements.txt` divergent | A8 | Alias `-r requirements.lock` ou suppression |
| m-14 | `pricing.yaml` staleness | A8 | `valid_until:` + Fact `PRICING_STALENESS_WARNING` |
| m-15 | PyInstaller `hiddenimports` manuels | A9 | `collect_all` auto + job CI `build-exe` |
| m-16 | Extras placeholder vides | A9 | Suppression `historical = []` / `importers = []` |
| m-17 | Test mesures importe Cercle 3 | A3 | Déplacement vers `tests/integration/` |
| m-18 | Petits items README (copyright, etc.) | A13 | Refonte intégrale couvre |
| §9.3 | Compteurs tests divergents 3 docs | A13 | Source unique CLAUDE.md, vérifié par A2 |
**Total : 13 + 28 + 18 + 1 = 60 entrées (les 59 items + §9.3
bookkeeping). Aucune entrée orpheline.**
---
## 13. Risques transverses et stratégies de contingence
### Risque R-1 — JOSS reviewer demande des changements méthodologiques de fond
*Probabilité* : moyenne (JOSS est généralement constructif mais
exigeant sur la rigueur statistique).
*Impact* : peut décaler la publication de 4 à 8 semaines
supplémentaires.
*Mitigation* :
- A12 prépare un draft *défendable* (refs primaires citées, tests
méthodologiques rigoureux comme `test_friedman_canonical`, etc.) ;
- Sprint A12-bis dédié et planifié en buffer Phase 8 (sem. 12+) ;
- Si refus JOSS, fallback arXiv preprint (sans peer-review formel
mais citable et DOI-stable).
### Risque R-2 — Audit RGAA externe trouve un bloqueur niveau A imprévu
*Probabilité* : faible si A6 est rigoureux (pytest + axe-core auto).
*Impact* : sprint A15-bis nécessaire, retard ~2 semaines.
*Mitigation* :
- A6 inclut un audit `axe-core` avant la fin du sprint, pas
seulement à la livraison ;
- A15 prévoit explicitement un buffer A15-bis ;
- Si critique : démarche de **conformité partielle** documentée
avec dérogations argumentées (acceptable RGAA).
### Risque R-3 — Refactor architecture casse un test caché ou un
consommateur externe
*Probabilité* : faible (lint passe, 3 356 tests, ré-exports
rétrocompat). *Impact* : régression silencieuse découverte tard.
*Mitigation* :
- A3 maintient des ré-exports rétrocompat avec `DeprecationWarning` ;
- `test_circle_dependencies.py` détecte toute nouvelle violation
immédiatement ;
- Suppression effective des ré-exports planifiée 2 versions plus
tard (v1.3.0 minimum), pas dans la même release.
### Risque R-4 — Indisponibilité d'une dépendance cloud (HF Datasets,
Gallica, MUFI registry)
*Probabilité* : faible. *Impact* : tests d'importation cassent en
CI.
*Mitigation* :
- Tous les imports de corpus en CI sont mockés (vérifier en A1) ;
- Le test de cohérence `docs/normalization-specs.md` archive les
versions des spécifications sources dans le repo, pas via lien
externe live.
### Risque R-5 — Le mainteneur unique manque de bande passante
*Probabilité* : forte sur projet académique mono-personne. *Impact* :
glissement calendrier.
*Mitigation* :
- Le plan est **séquencé pour qu'un sprint soit fermable en isolation**
— abandon partiel possible sur les phases tardives sans casser
les phases livrées (par ex. A15 peut s'étaler tant que les
audits externes tournent) ;
- Documentation A11 + GOVERNANCE A10 préparent l'arrivée de
contributeurs domain-experts (paléographe, archiviste, dev EN
natif) — recrutement ouvert dès Phase 5 ;
- En cas de blocage long, annoncer un *« release v1.1 minimal
institutional »* après A11 (Phase 5 close) — ce serait déjà un
saut qualitatif majeur sur l'état actuel.
### Risque R-6 — Découverte d'un bug de fond lors d'un audit externe
*Probabilité* : faible (la base de tests est solide). *Impact* :
hotfix sprint nécessaire.
*Mitigation* :
- A1 (scanners + cov-fail-under) attrape la majorité des cas
silencieux ;
- A8 (snapshots reproductibles) permet de rejouer un benchmark
pré-bug pour confirmer la régression ;
- Politique de hotfix sécurité 72 h documentée en A10.
---
## 14. Définition de « niveau BnF / British Library atteint »
Le projet est considéré au niveau institutionnel cible quand
**toutes** les conditions suivantes sont satisfaites simultanément.
Cette liste est la *Definition of Done* du présent plan.
### Côté code
- [ ] Audit interne `institutional-readiness-2026-05.md` : 0 BLOCKER
ouvert, 0 MAJOR ouvert.
- [ ] `pytest tests/` : 100 % vert sur Linux + macOS + Windows
× Python 3.11 + 3.12 + 3.13.
- [ ] `ruff check` 0 erreur.
- [ ] `mypy picarones/core/ --strict` 0 erreur.
- [ ] `bandit -r picarones/ -ll` 0 issue HIGH/CRITICAL.
- [ ] `pip-audit --strict` 0 vulnérabilité ouverte.
- [ ] Couverture ≥ 85 % (`--cov-fail-under=85` actif et tenu).
- [ ] `tests/core/test_circle_dependencies.py` 0 violation.
- [ ] `tests/docs/test_readme_consistency.py` 0 divergence.
- [ ] `tests/docs/test_specs_consistency.py` 0 divergence.
### Côté distribution
- [ ] `pip install picarones` fonctionne depuis PyPI sur 3 OS.
- [ ] `docker pull ghcr.io/maribakulj/picarones:<version>` retourne
une image immutable épinglée.
- [ ] `requirements.lock` versionné et utilisé en prod (Dockerfile).
- [ ] Release GitHub `v1.x.y` automatique avec corps depuis CHANGELOG.
### Côté reproductibilité
- [ ] Un benchmark rejoué à 6 mois d'intervalle avec mêmes lock file +
digest Docker + commit picarones produit un rapport bit-à-bit
identique (ou avec diff explicable).
- [ ] `docs/reproducibility-snapshots.md` documente la procédure
end-to-end.
### Côté communication scientifique
- [ ] `CITATION.cff` valide (`cffconvert --validate`), bouton
« Cite this repository » fonctionnel sur GitHub.
- [ ] DOI Zenodo attribué et présent dans le README.
- [ ] Papier JOSS : statut `accepted` + DOI JOSS dans CITATION.cff
comme `preferred-citation`. *(ou, en fallback, arXiv preprint
avec DOI alternatif).*
- [ ] `docs/normalization-specs.md` : chaque profil a sa source citée
avec date d'extraction.
### Côté gouvernance et conformité
- [ ] `CODEOWNERS`, `GOVERNANCE.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`,
`LICENSE`, `ACCESSIBILITY.md`, `CITATION.cff` tous présents et
non-vides (test CI `test_governance_files_present`).
- [ ] Audit RGAA externe : conformité WCAG 2.1 niveau AA ≥ 95 %, le
résiduel listé en dérogations argumentées.
- [ ] Audit sécurité externe : 0 finding HIGH/CRITICAL.
- [ ] Politique RGPD documentée + purge auto fonctionnelle.
- [ ] Guide de déploiement institutionnel revu par au moins un DSI
partenaire.
### Côté documentation produit
- [ ] README < 400 lignes, markdown valide, badges verts, tableaux
auto-générés depuis le code.
- [ ] SPECS.md v2 reflète strictement le code, contient une section
« Limites assumées » non vide.
- [ ] Documentation utilisateur et 4 guides développeur traduits en
anglais avec parité de structure validée par test.
- [ ] CHANGELOG.md à jour, format Keep-a-Changelog respecté.
### Critère méta — sécabilité
- [ ] Le repo peut être récupéré et son rapport demo généré sur une
machine vierge en moins de 5 minutes
(`git clone && pip install -e . && picarones demo`), preuve
par job CI dédié `test_quickstart`.
Quand ces 30 cases sont cochées, l'institution peut adopter Picarones
comme outil de référence interne et les chercheurs peuvent le citer
dans des publications scientifiques avec garantie de stabilité,
reproductibilité, et conformité.
---
*Plan rédigé en réponse à l'audit
[`institutional-readiness-2026-05.md`](institutional-readiness-2026-05.md)
sur la branche `claude/audit-institutional-readiness-8Cw4w`.*
*Période de mise en œuvre suggérée : mai–juillet 2026.*
|