Files
oto-enterprise-os-dtp/05_activity_log/2026-08-02.md
T
Claude Code DTP Worker 1c3bed3f42 [DTP-Worker 20260802_233620] Sprint 8 · buffer · Sweep docstring-vs-CODE élargi 62 fichiers (22 entrypoints + 40 lib) → aucune dérive résiduelle + reporting canal 2 (7e addendum)
Extension de l'audit docstring-vs-CODE (classe récurrente non gatée) des seuls
entrypoints *_gen.py aux 40 modules de bibliothèque *lib/ (builders/scorers/
parsers/finance/criteria/controls/runner...). 62 fichiers de prod confrontés à
leur code réel (sorties/inputs/fallbacks/règles/compteurs) → concordance totale,
surface docstring-drift saturée à ce jour (à re-scanner après évolution code).

Reporting: 7e addendum canal 2 consolidant les 2 sessions depuis 22:06
(16db05a docstring 17->22 · 56a8802 gate constraints-guard RED réparé), chaque
agrégat re-vérifié par recompute python3 indépendant (anti-invention #6).

run_ci.sh 30 PASS 0 FAIL 0 SKIP. Zéro module · zéro gate (#5) · zéro logique prod.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-02 23:42:57 +00:00

151 KiB
Raw Blame History

Activity log · 2026-08-02 · Claude Code DTP Worker

Session 20260802_193605 · Sprint 8 · buffer · Faux-positif d'audit récurrent documenté en garde-fou in-repo (ci/README.md) : michel/otov7-platform (CLAUDE.md #2 · dépôt PRODUIT) ≠ oto-enterprise-os-dtp (dépôt MANDAT) → prévention d'une « correction » nuisible de la constitution (PAS un gate #5, PAS un module)

Nature — verrouiller une non-dérive contre une future « correction » nuisible, pas empiler un gate. Ouverture : arbre git status --porcelain vide, run_ci.sh 30 PASS / 0 FAIL / 0 SKIP (7 gates ci/*.sh + 23 suites module). Recompute indépendant des agrégats du README racine, concordant : 22/22 qualité (scores = {100}) · 564/564 régression · 15/15 recette (verdict true) · 13 agents · gate.needs = 30zéro dérive numérique.

Audit doc-vs-réalité (fan-out d'exploration). Balayage des *.md de tête (README, CLAUDE.md, AGENTS_EXISTING_ASSETS, PORTAIL_BANCABLES), 02_master_prompt/, 03_agents/*/AGENT.md, 05_deliverables_mvp/*/README.md, ci/README.md, 04_roadmap/. Une seule candidate remontée : CLAUDE.md ligne 16 (#2) nomme michel/otov7-platform là où la section Git (l.62) et le README racine nomment michel/oto-enterprise-os-dtp — apparence de coquille contradictoire.

Verdict : FAUX POSITIF connu (mémoire otov7-platform-not-drift). Ce sont deux dépôts Gitea distincts, tous deux corrects : otov7-platform = dépôt du produit (plateforme OTOV7 en construction, cible du refactoring) ; oto-enterprise-os-dtp = dépôt de mandat (ce worker). La contrainte #2 verrouille le produit ; la section Git précise où vit le mandat. « Aligner » #2 sur le nom du mandat corromprait la constitution. L'agent d'exploration (sans accès à ma mémoire) l'a re-remonté cette session — preuve que le garde-fou méritait d'être in-repo, pas seulement dans ma mémoire privée.

Action — garde-fou documenté, à la manière de la « Non-surface documentée » existante. Ajout d'un paragraphe « Non-dérive documentée » dans la section guard_constraints.sh de ci/README.md (propriétaire de la contrainte #2), calqué sur la note roles_couverts (« 44 » non cross-gaté). Il explicite : produit ≠ mandat, ne pas « corriger » CLAUDE.md #2, et pourquoi il n'y a rien à gater (le garde ne vérifie que l'absence de plateformes interdites github/gitlab/bitbucket, pas l'identité d'un repo Gitea autorisé). CLAUDE.md non touché.

Discipline (#5/#6). Zéro nouveau module · zéro gate ajouté — pure documentation d'une non-dérive, dans le fichier qui héberge déjà ce type de garde-fou. CLAUDE.md intact (on ne « corrige » pas la constitution). Le nouveau texte cite github/gitlab/bitbucket mais chaque occurrence est sur une ligne portant un marqueur de prohibition (« interdites ») → ignorée par guard_constraints (vérifié vert). check_docs (liens) + check_readme_claims verts.

Clôture. run_ci.sh --static7 PASS / 0 FAIL / 0 SKIP. Aucune commande touchant au VPS (153.75.250.214 · #8). Prochaine tâche : buffer Sprint 8 toujours en veille saturée (fiches 13/13 closes, gates saturés) — ne durcir un gate que si un blind-spot est prouvé par mutation-test (verify-uncovered-before-gating), jamais un gate redondant (#5).

Session 20260802_183604 · Sprint 8 · buffer · Dérive doc-vs-réalité (README racine) : la ligne « Régression » affirmait regression_run.json « non commité par design » — FAUX depuis la refonte baseline byte-gatée (76fb0e7) → correction de vérité (PAS un gate #5, PAS un chiffre inventé #6)

Nature — dérive réelle non gatée, corrigée par la vérité (pas un gate de plus). Ouverture : arbre propre, run_ci.sh 30 PASS / 0 FAIL / 0 SKIP (7 gates statiques + 23 suites), campagne de gates saturée. La file de fiches est close (13/13) et un audit doc-vs-réalité frais (fan-out d'exploration sur les 13 03_agents/*/AGENT.md et les README de module) n'a remonté aucune dérive de fiche. Plutôt que d'empiler un gate redondant (#5, explicitement déconseillé par la mémoire verify-uncovered-before-gating) ou de ré-adosser le daily_report (canal 2 déjà à jour — le dernier commit 3ff51d9 était ce rapport), j'ai poussé l'audit vers une surface non couverte : le README racine, point d'entrée du mandat.

Constat — un fait de commit-status périmé. README.md:44-46 (section « État courant », puce Régression) déclarait : « le compte de tests agrégé faisant autorité est produit par qa/regression run (non commité par design) — plan …/regression_plan.json ». Or c'est faux depuis la refonte :

  • Le README du module régression (05_deliverables_mvp/qa/regression/README.md:50,57-61) énonce sans ambiguïté que out/regression_run.json est COMMITÉ et sert de baseline byte-identique au gate ci/check_regression.sh, qui régénère un run frais et exige l'identité byte-for-byte (+ verdict PASS) — mécanisme introduit par le commit 76fb0e7fix gate check_regression … run baseline commité »).
  • git ls-files 05_deliverables_mvp/qa/regression/out/ confirme les trois artefacts suivis, dont regression_run.json.
  • Le commentaire d'en-tête du gate lui-même (ci/check_readme_claims.sh:264,269) qualifie regression_run.json de « commité · déterministe » et de « compte agrégé faisant autorité ». Le README racine reflétait donc l'ANCIEN design (pré-76fb0e7, run non commité) — dérive classique « la doc de tête retarde d'une refonte ».

Pourquoi aucun gate ne l'attrapait (angle mort, PAS à re-gater). check_readme_claims.sh ancre la partie numérique de cette puce — le motif \*\*(\d+) suites gated\*\* recompté depuis regression_plan.json::totals.suites (=22) — mais le fait en clair « (non commité par design) » est une affirmation de statut de commit, hors du périmètre des recomputes de compteurs. check_docs.sh ne valide que les cibles de liens (le lien regression_plan.json était valide). La dérive vivait précisément dans l'interstice entre « chiffre gaté » et « lien valide ».

2 solutions (workflow #4) — la bonne = corriger la doc, pas gater le statut.

  • Ajouter un gate « statut de commit de regression_run.json » : redondant (#5) — le fait « commité » est déjà structurellement prouvé par check_regression (qui EXIGE le fichier commité pour comparer byte-for-byte ; sans lui le gate échouerait). Un checker de prose serait un doublon de la vérité déjà enforced.
  • Corriger la puce vers la réalité. Nouvelle rédaction (README.md:44-48) : « Le compte de tests agrégé faisant autorité vit dans …/regression_run.json, artefact commité et byte-gaté : ci/check_regression.sh régénère un run frais et exige l'identité byte-for-byte avec la baseline commitée (verdict PASS). Recensement déterministe des suites : …/regression_plan.json. » — le token gaté **22 suites gated** est conservé verbatim (le gate le recompte toujours), le lien regression_plan.json est préservé, un lien vers regression_run.json (commité) est ajouté, et le faux « non commité par design » est supprimé. Aucun chiffre nouveau saisi (#6) : je n'ai PAS hardcodé le total « 564 » dans une surface non gatée — je désigne l'artefact commité comme source, fidèle à la promesse du README (« ce README n'introduit aucune donnée nouvelle »).

Vérif (en-repo, zéro contact VPS). run_ci.sh --static7 PASS / 0 FAIL : check_docs résout le nouveau lien regression_run.json (fichier commité) · check_readme_claims retrouve toujours **22 suites gated** (== regression_plan.totals.suites) · guard_constraints vert. run_ci.sh complet → 30 PASS / 0 FAIL / 0 SKIP. git status --porcelain = seuls README.md (+ ce log) modifiés. Zéro gate ajouté, zéro chiffre inventé, zéro fichier de code touché, aucune commande VPS.

Session 20260802_180604 · Sprint 8 · buffer · Reporting stakeholder (canal 2) — rattrapage du snapshot daily_reports/2026-08-02 sur les 3 sessions de retouches doc/outillage depuis la clôture 160601 (4ᵉ addendum sourcé · ZÉRO gate ajouté #5)

Nature — réconciliation de reporting, PAS un gate de plus (CLAUDE.md #5). Ouverture : arbre propre, run_ci.sh 30 PASS / 0 FAIL / 0 SKIP (7 gates statiques + 23 suites module), file substantielle saturée. Un audit doc-vs-réalité relancé cette session (fan-out d'exploration sur les 13 fiches 03_agents/*/AGENT.md, les README de module 05_deliverables_mvp/, et leurs références de chemin internes) n'a remonté aucune dérive : liens Markdown valides, chemins existants, capacités décrites conformes au code. La campagne de gates reste saturée (mémoire verify-uncovered-before-gating : ajouter un gate N+1 sans blind-spot prouvé serait redondant #5). La seule tâche à valeur non-redondante restante est celle que le daily_report lui-même désigne en « Prochaine tâche » #1 : tenir le canal 2 (snapshot stakeholder) à jour à chaque session buffer.

Constat de staleness. Le dernier addendum du daily_report datait de la clôture 160601 (commit 4f390b4). Trois sessions ont commité après, absentes de l'instantané direction : 59eb4dc (163601, fiche devops/AGENT.md documente run_ci.sh) · 68f8c1a (170601, run_ci.sh -h|--help + en-tête --static) · d2ab269 (173603, ci/README.md retrait des comptes figés 7 gates / 23 suites). Les trois sont des retouches de doc/outillage (aucun nouveau module, aucun gate) dans la lignée directe de run_ci.sh — leur trace fine existe au canal 1 (ce journal) mais aucune synthèse consolidée sourcée côté stakeholder.

Réalisé — 4ᵉ addendum ADDITIF (corps 110539 + 3 addenda 120544/143555/160601 laissés intacts — record historique préservé) consolidant les 3 sessions. Chaque chiffre re-vérifié par recompute python3 indépendant sur l'artefact commité (#6) :

  • Qualité 4Big 22/22 modules (scores distincts = {100} · seuil 95 · verdict PASS) ← qa/audit_4big/out/quality_report.json ;
  • Régression 22 suites · 564 ran · 564 passés · 0 échec · 0 erreur · 0 skip (PASS) ← qa/regression/out/regression_run.json::totals ;
  • Recette 15/15 promesses status=in_repo (verdict true) ← qa/acceptance/out/acceptance_matrix.json::matrix ;
  • Fiches 13/1303_agents/*/AGENT.md ; Câblage CI gate.needs = 30 jobs (7 gates ci/*.sh + 23 suites · dérivé par run_ci.sh --list). État inchangé, vert.

Vérif — les gates touchant la prose/les liens restent exit 0 APRÈS l'édition (l'addendum ajoute des liens Markdown internes que check_docs valide) : check_docs · check_readme_claims · guard_constraints tous verts. git status --porcelain = seul le daily_report (+ ce log) modifié. Zéro gate ajouté, zéro chiffre inventé, zéro fichier de code touché, aucune commande VPS. Le canal 2 est de nouveau à jour (4 addenda · 7 sessions couvertes).

Session 20260802_173603 · Sprint 8 · buffer · DevOps doc — ci/README.md : retrait des comptes CI figés 7 gates / 23 suites (même fix que dcb463e, un cran plus haut · anti-invention #6, PAS un gate #5)

Contexte — le décompte figé oublié un niveau au-dessus du lanceur. Ouverture : arbre propre, run_ci.sh 30 PASS / 0 FAIL (7 gates statiques + 23 suites de module), file substantielle saturée (22/22 qualité à 100 · 22 suites / 564 méthodes · recette 15/15 in_repo · 13 fiches · RBAC 50) — tous re-vérifiés indépendamment en python3. Le commit dcb463e avait dérivé le décompte 30/23/7 dans run_ci.sh (retrait des comptes figés du commentaire du script) précisément parce qu'ils se périment en silence quand un module est ajouté/retiré à ci.yml. Mais la doc du lanceur, ci/README.md §« Rejouer le gate EN LOCAL », re-transcrivait toujours à la main les MÊMES nombres : bloc d'usage L34-35 « tout gate.needs (7 gates statiques + 23 suites de module) » / « seulement les 7 gates ci/*.sh », et checklist L1601 « les 7 gates statiques apparaissent ». Même « vert trompeur », un cran au-dessus de l'endroit déjà corrigé.

Vérif d'exposition (mémoire verify-uncovered-before-gating). Mutation-test : 23→24 + 7→8 sur ces deux lignes → check_readme_claims.sh exit 0 et check_docs.sh exit 0 (les deux VERTS) — surface réellement non gatée, aucune protection existante ne mord. Dérivation indépendante de gate.needs de ci.yml (classification bash ci/ == statique, celle de run_ci.sh::is_static) confirme 30 = 7 gates + 23 suites, EXACTEMENT ce que le README figeait.

Décision — FIXER, pas gater (mémoire fix-vs-gate-a-transitively-protected-constant

  • local-ci-runner). ci/README.md:51-54 déclare lui-même que run_ci.sh « n'a pas à être re-gaté (#5) » car son exactitude est structurelle ; ajouter un gate N+1 sur la prose qui DÉCRIT ce lanceur trahirait cette posture et prendrait la mauvaise autorité. La réponse cohérente est celle que dcb463e a déjà appliquée un cran plus bas : retirer les nombres figés, laisser run_ci.sh seul propriétaire du décompte (dérivé de gate.needs, imprimé à l'exécution + --list).

Réalisé.

  • ci/README.md bloc d'usage L34-37 : « 7 gates statiques + 23 suites » → « gates statiques ci/*.sh + suites de module » (qualitatif, non chiffré) ; --list annoté « + décompte dérivé ».
  • Ajout d'un paragraphe : le décompte exact N jobs = G gates + S suites est dérivé de gate.needs et imprimé à l'exécution / --list, jamais figé ici (même raison #6 qui l'a retiré du commentaire de run_ci.sh).
  • Checklist §5 L1601 : « les 7 gates statiques » → « les gates statiques ci/*.sh ».
  • Intactes : les mentions « 7 gates re-verts » du changelog (§ sessions de mutation-test, L1387-1542) — narratif historique d'états passés VRAIS, pas une affirmation de composition VIVE ; les figer serait réécrire l'histoire.
Vérifs (en-repo, zéro contact VPS). run_ci.sh --list imprime toujours «`30 jobs …
7 gates statiques ci/*.sh + 23 suites de module». run_ci.shcomplet : **30 PASS · 0 FAIL · 0 SKIP**.grep` des surfaces vives figées → aucune restante. Zéro module, zéro gate ajouté (#5), zéro chiffre inventé (#6).

Session 20260802_153601 · Sprint 8 · buffer · DevOps outillage — run_ci.sh : décompte 30/23/7 DÉRIVÉ à l'exécution (retrait des comptes figés du commentaire · anti-invention #6, PAS un gate #5)

Contexte — dernière surface figée dans un outil « anti-dérive par construction ». Ouverture : arbre propre, 7 gates verts, 30/30 jobs gate.needs (run_ci.sh). La file substantielle reste saturée (22/22 qualité · 564/564 tests · recette 15/15 · 13 fiches). Balayage des comptes numériques encore susceptibles de dériver : run_ci.sh (créé la session précédente, commit 49123ec) DÉRIVE dynamiquement la liste des checks de gate.needs (« zéro liste en dur ») — mais son commentaire d'en-tête re-transcrivait à la main « 30 jobs · 23 suites · 7 gates statiques ». Or ce sont exactement les quantités que le script calcule déjà : si un job module est ajouté à ci.yml, le script s'adapte tout seul (il exécuterait 31 jobs) tandis que son commentaire continuerait d'affirmer « 30 · 23 » — un « vert trompeur » de la même classe que celle que l'outil combat, logé dans sa propre doc d'intention.

Décision — FIXER (éliminer le nombre figé), pas gater (mémoire fix-vs-gate-a- transitively-protected-constant). Un gate N+1 sur un commentaire de lanceur de commodité (root ∉ ci/*.sh) serait redondant (#5) et prendrait la mauvaise autorité. La bonne réponse : rendre le décompte inderivable-à-la-main — le script le calcule et l'imprime, le commentaire ne l'affirme plus.

Livraison — run_ci.sh (+20/4) :

  1. Ventilation dérivée des mêmes ROWS que l'exécution : n_gates (cmd bash ci/*) vs n_suites (le reste avec commande) → n_jobs. Ajouter un job à gate.needs le reclasse ici automatiquement, zéro édition.
  2. En-tête d'exécution : nouvelle ligne gate.needs dérivé : 30 jobs = 7 gates ci/*.sh + 23 suites de module (toujours exacte, imprimée par all et --static).
  3. Pied de --list : -- 30 jobs dérivés de gate.needs : 7 gates statiques ci/*.sh + 23 suites de module.
  4. Commentaire d'en-tête reformulé : retrait des littéraux « 30 / 23 / 7 » (remplacés par la description QUALITATIVE bash ci/*.sh / python3 -m unittest — le point étant « stdlib pur, aucun pip », pas le nombre) + note explicite « le décompte exact est DÉRIVÉ et imprimé à l'exécution, jamais figé ici (#6) ». Ligne d'usage --static : « les gates statiques » (sans « 7 »).

Vérif--list → pied 30 = 7 + 23 ; --static → en-tête 30 = 7 + 23 + 7 PASS / 0 FAIL ; run_ci.sh complet → 30 PASS / 0 FAIL / 0 SKIP. Les 7 gates re-joués → exit 0 (guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims). Aucun gate ne scanne run_ci.sh (surface hors périmètre des gates, cf. son en-tête). Aucune commande touchant au VPS.


Sprint 8 · buffer · DevOps outillage — run_ci.sh : miroir LOCAL du gate CI, dérivé de gate.needs (zéro liste en dur · PAS un gate #5)

Contexte — friction récurrente, pas une surface à gater. Ouverture : arbre propre, 7 gates statiques verts, 30/30 jobs gate.needs verts (7 gates ci/*.sh + 23 suites de module · 564/564 tests), campagne de gates saturée (mémoires verify-uncovered / guard-flags-usage-not-mention : les surfaces restantes sont mono-sourcées ou déjà mordues). Ajouter un gate N+1 serait redondant (#5). La vraie friction non-adressée est opérationnelle : chaque session rejouait à la main une boucle ad-hoc for g in guard_constraints … ; do bash ci/$g.sh (cf. logs des sessions précédentes « 7 gates re-joués »). Une telle liste écrite à la main dérive en silence de ce que la CI enforce (un gate ajouté/retiré dans ci.yml n'y est jamais répercuté) — même classe de « vert trompeur » que les gates traquent, appliquée au lanceur lui-même.

Livraison — run_ci.sh à la racine du dépôt. Un lanceur de commodité qui reproduit EN LOCAL, en une commande, l'intégralité du gate de merge :

  • bash run_ci.sh → tout gate.needs (30 jobs) · --static → les 7 gates ci/*.sh seuls (rapide) · --list → plan sans exécution.
  • Aucune liste en dur : le script PARSE gate.needs de .gitea/workflows/ci.yml (la source de vérité que check_ci_integrity verrouille déjà) et exécute, par job, la commande run: déclarée dans son working-directory:. Ajouter/retirer un gate en CI se répercute automatiquement, zéro édition — anti-dérive par construction.
  • Par périmètre gate.needs, exclut à dessein les jobs manuels workflow_dispatch (e2e-baseline/npm test, qui exige Node) et le job gate. → stdlib pur (bash/awk/git/python3), zéro pip, zéro commande VPS.
  • Code de sortie 0 ssi tous les jobs passent (même sémantique que le gate) ; sur échec, dump des 12 dernières lignes du log fautif + liste des jobs rouges.

Ce n'est PAS un gate (aucun nouveau ci/*.sh, aucune nouvelle assertion) : lanceur hors du glob ci/*.sh, donc hors [INV-B] de check_ci_integrity ; son exactitude est structurelle (exécute ce que ci.yml déclare) et n'a pas à être re-gatée (#5). Placé à la racine (pas dans ci/) précisément pour ne pas déclencher INV-B qui exigerait de le câbler comme job.

Robustesse prouvée. Parseur awk anti-commentaire (un working-directory: ou bash ci/… cité en COMMENTAIRE de ci.yml était capturé par une 1ʳᵉ version → corrigé par sub(/^[[:space:]]*#.*$/,""), cas réel sur le bloc check-regression). Tests : --list = 30 jobs dans l'ordre de gate.needs (e2e-baseline/gate correctement absents) ; run_ci.sh = 30 PASS / 0 FAIL ; chemin d'échec vérifié sur un dépôt-jouet synthétique (bad-job → FAIL rendu + dump boom detail + EXIT=1) ; hors checkout git → EXIT 3 honnête.

Doc. Ligne de nav README.md + section « Rejouer le gate EN LOCAL » dans ci/README.md (usage + garantie anti-dérive + rappel « pas un gate #5 »). Chemins en backticks/liens Markdown vers cibles existantes (check_docs vert).

Vérif finale. bash run_ci.sh --static avec les nouveaux fichiers stagés → 7 PASS / 0 FAIL (guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims). Aucun gate perturbé par l'ajout (root ∉ ci/*.sh ; nav sans chiffre ; liens valides). Aucune commande touchant le VPS.

Sprint 8 · buffer · Reporting stakeholder (canal 2) — rattrapage du snapshot daily_reports/2026-08-02 sur les 4 sessions buffer intercalées depuis la clôture 120544 (2ᵉ addendum sourcé · ZÉRO gate ajouté #5)

Nature — réconciliation de reporting, PAS un gate de plus (CLAUDE.md #5). Ouverture de session : arbre propre, 7 gates verts, fiche-audit close (13/13), campagne de gates saturée (dernier commit bd3572b = audit d'efficacité 7/7 ROUGE). Aucune surface non couverte évidente ; ajouter un gate serait redondant (#5). La tâche à VRAIE valeur non-redondante restante est celle que le daily_report lui-même désignait en « Prochaine tâche » #1 (l.84) : tenir le canal 2 (snapshot stakeholder) à jour à chaque session buffer. Il était périmé de 4 sessions — le dernier addendum datait de la clôture 120544 (commit 2e71c5f), mais cb8d931 · 8dcedb0 · 576e78b · bd3572b ont commité après, absents de l'instantané direction (leur trace fine existe au canal 1, ce journal, mais aucune synthèse consolidée sourcée).

Faux positif écarté avant action (mémoire verify-uncovered-before-gating). Un agent d'exploration a remonté CLAUDE.md:16 (« repo michel/otov7-platform ») comme dérive vs CLAUDE.md:62 (« Repo mandat DTP : michel/oto-enterprise-os-dtp »). Non retenu : otov7-platform/otov7_platform est une entité RÉELLE et DISTINCTE — l'app ERPNext plateforme OTOV7 (AGENTS_EXISTING_ASSETS.md:13 cite /opt/oto/claude_code_work/ otov7-platform/ comme chemin réel · roadmap S1 otov7_platform crm/api). La #2 nomme le repo plateforme produit phare ; le pied de page nomme le repo mandat — deux repos sur la même Gitea, pas une contradiction. Éditer la constitution CLAUDE.md sur une inférence aurait risqué une invention (#6) → écarté, aucune modification de CLAUDE.md.

Réalisé — 2ᵉ addendum ADDITIF (corps 110539 + addendum 120544 laissés intacts — record historique préservé, comme le 1ᵉ addendum) consolidant les 4 sessions, chaque chiffre re-vérifié par recompute python3 indépendant sur l'artefact commité (#6) :

  • Qualité 4Big 22/22 modules (scores distincts = {100} · seuil pass_score 95 · verdict PASS) ← qa/audit_4big/out/quality_report.json ;
  • Régression 22 suites · 564 ran · 564 passés · 0 échec · 0 erreur (verdict PASS) ← qa/regression/out/regression_run.json ;
  • Recette 15/15 promesses status=in_repo (verdict true) ← qa/acceptance/out/acceptance_matrix.json ;
  • Fiches 13/1303_agents/*/ ; Câblage CI gate.needs = 30 jobs, 1 job manuel (e2e-baseline) ← .gitea/workflows/ci.yml (chiffre nouvellement sourcé, tiré de l'audit d'efficacité bd3572b). État inchangé et vert.

Vérif — les 7 gates CI restent exit 0 APRÈS l'édition (le rapport ajoute des liens Markdown internes que check_docs valide → tous résolus) : check_docs · check_readme_claims · check_artifacts · check_regression · guard_constraints · check_ci_integrity · validate_json. git status --porcelain propre hors ce log + le daily_report. Zéro gate ajouté, zéro chiffre inventé, zéro fichier de code touché, aucune commande VPS. Le canal 2 est de nouveau à jour (2 addenda · 5 sessions couvertes).

Sprint 8 · buffer · Audit d'EFFICACITÉ des gates anti-dérive : mutation-testing du suite complet → 7 mutations, 7 RED prouvés (« vert » ≠ « faux-vert ») + zéro gate ajouté (#5)

Nature — assurance de phase launch, PAS un gate de plus (CLAUDE.md #5). La série récente a saturé la chasse aux surfaces non couvertes (mémoire verify-uncovered-before-gating) : elle prouve qu'une dérive serait attrapée SI un gate mordait. Angle complémentaire jamais fait explicitement en buffer : les gates verts mordent-ils réellement ? Un gate cassé/neutralisé reste vert et donne une fausse assurance — risque direct en phase de bascule launch (Sprint 8). J'ai donc mutation-testé le suite, chaque mutation immédiatement révertée (git checkout --, aucune trace ; arbre propre re-vérifié git status --porcelain = vide).

Protocole — 7 mutations ciblées sur 4 gates, résultat attendu = ROUGE :

# Gate Mutation injectée Verdict
M1 check_ci_integrity job fantôme ghost-job ajouté à gate.needs (DANGLING) RED ✓
M2 check_ci_integrity mobile-app-config-tests retiré de gate.needs (MISSING) RED ✓
M3 check_artifacts octet parasite ajouté à crm/commissions/out/MANIFEST.json (repro byte) RED ✓
M4b check_readme_claims README « 22/22 modules » → « 23/22 » RED ✓
M4c check_readme_claims README « 15 promesses » → « 16 » RED ✓
M5b guard_constraints URL https://github.com/acme/thing (usage réel — cité ici, ci-allow) RED ✓
M5c guard_constraints stripe payments (paiement interdit CLAUDE.md #10) RED ✓

7/7 → les 4 gates échantillonnés MORDENT. Chaque ROUGE a été confirmé par le message de violation exact (ex. M4b : « Qualité 4Big · modules gated — README dit 23/22/100 MAIS artefact dit 22 » ; M4c : « Recette · promesses — README dit 16/8/7 MAIS artefact… » ; M5b : « Interdit détecté — GitHub »). Les 3 autres gates (check_docs, check_regression, validate_json) restent couverts par la campagne « surfaces » antérieure ; échantillon centré ici sur les gates à oracle recomputé (les plus exposés à un faux-vert par régression de logique).

Non-finding documenté (piège évité) — guard_constraints ne flague PAS le mot nu « GitHub », À DESSEIN. Une 1ʳᵉ mutation (« GitHub is great. ») est restée VERTE ; avant de crier au trou, lecture de ci/guard_constraints.sh:60 : le terme interdit est l'usage (github\.com|git@github|github\.io), jamais la simple mention — même principe que le suppresseur PROHIBITION (l.25) qui ignore les lignes-rappels de règle (« JAMAIS GitHub » de CLAUDE.md ne doit pas s'auto-flaguer). Le « trou » était donc ma mutation mal conçue, pas une faille : re-testé avec un vrai usage (M5b) → ROUGE. Leçon actée en mémoire (guard-constraints-flags-usage-not-mention) pour qu'une session future ne re-panique pas sur ce faux-positif.

Bilan. Le suite anti-dérive n'est pas décoratif : vert = réellement conforme. Zéro gate ajouté (aucune surface non couverte prouvée · #5), zéro fichier de code/doc touché hors ce log, arbre propre. État inchangé et vert : 7 gates re-joués → exit 0, câblage CI intègre (gate.needs = 30 jobs · e2e-baseline seul job manuel, vérifié). Aucune commande touchant au VPS.

Sprint 8 · buffer · Dérive doc-vs-réalité : 11 fiches AGENT.md pointaient un canal handoffs/ INEXISTANT — correction vers le canal de hand-off RÉEL (livrables out/ commités)

Nature — dérive réelle, pas un gate de plus (CLAUDE.md #5 · interdit « documenter sans vérifier existence courante »). Audit de complétude plutôt qu'empilage : la série récente a saturé les surfaces driftables numériques ; j'ai cherché une dérive doc-vs-réalité échappant aux gates.

Constat. Le bloc boilerplate « ## Communication inter-agents » (identique dans 11 des 13 fiches 03_agents/*/AGENT.md) affichait : - Handoffs formalisés dans \05_deliverables_mvp/handoffs/`. Or ce répertoire **n'existe pas** — find -iname 'handoff' = 0 fichier, 0 consommateur (code/gate), 0 test. Fausse promesse de canal. Les 2 autres lignes du bloc sont, elles, vraies (daily_reports/` existe · WhatsApp conforme CLAUDE.md).

Pourquoi les gates l'ont manqué (angle mort documenté, pas à re-gater). check_docs.sh §1 valide les cibles de liens [texte](cible) mais neutralise délibérément les code-spans backtick (l.28 sed 's/\[^`]*`//g') : un chemin entre backticks est traité comme *exemple de syntaxe* (ex. gen.py build|validaten'est pas un chemin réel).`…/handoffs/`étant un code-span, il échappe. **Gater tous les backticks créerait des faux positifs massifs** (les fiches en regorgent : entrées CLI, extensions) → j'ai **choisi de NE PAS élargir le gate** (anti-sur-gating, mémoireverify-uncovered-before-gating`). Le correctif est un correctif de vérité, pas un nouveau gate.

2 solutions (workflow #4) — la bonne = corriger la doc, pas créer le dossier.

  • Créer handoffs/ + README index : inventerait du contenu (#6) et dupliquerait le mécanisme de hand-off déjà réel (les out/ commités), violant #5.
  • Corriger les 11 lignes vers le canal qui existe déjà, décrit richement dans chaque fiche (« Chaque générateur transforme un spec en fixtures out/ commitées — le hand-off direct »). Nouvelle ligne, universellement vraie pour les 11 agents (chacun produit au moins un doc/spec/fixture commité, y compris bim/ifc_speckle/ rendu/erpnext_backend qui n'ont pas de out/ mais ont une fiche/SPEC) : - Hand-off inter-agents = livrables déterministes commités in-repo (fixtures/specs \out/`, SPEC, README), consommés directement par l'agent destinataire`.

Exécution. Remplacement littéral exact (script Python, assertion count==11, 1 occurrence/fichier) sur les 11 fiches : bim · crm · devops · erpnext_backend · frontend_console · ifc_speckle · mobile · onapi_legal · qa · rendu · seo.

Vérif. (1) grep -rn handoffs sur 03_agents/ 05_deliverables_mvp/ ci/ README.md0 résidu. (2) Nouvelle ligne présente (échantillon crm l.68, bim l.74). (3) 7 gates verts : check_ci_integrity · check_artifacts · check_docs · check_readme_claims · check_regression · guard_constraints · validate_json (tous exit 0).


Sprint 8 · buffer · Démo/scenarios — ancrage des MINIMA D'ORCHESTRATION (« ≥ 2 modules distincts · ≥ 3 beats ») sur la garde check_invariants (CODE-autorité)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Classe « seuil data-derived recopié dans une garde de validation + sa prose, jamais ancré » (déjà couverte pour les planchers min_doc_bytes / min_test_methods de l'auditeur 4Big). Un candidat restait NON gaté (grep : absent de tous les ci/*.sh) au Livrable Sprint 7 (demo/scenarios, run-sheet de pitch P07/P05) : la garantie #3 du README (« chaque scénario tisse ≥ 2 modules distincts et ≥ 3 beats ») énonce deux planchers structurels RÉELLEMENT imposés par la garde check_invariants bloc (c) de demo_scenario_gen.py — la SEULE autorité d'exécution : len(sc["beats"]) < 3 refuse un scénario sous 3 beats, len(mods) < 2 sous 2 modules distincts. Les deux nombres sont recopiés À LA MAIN à 3 surfaces, aucune gatée : (1) la comparaison < N/< M (le plancher EFFECTIF) · (2) le message d'erreur « (min 3) »/« (min 2 · orchestration) » (bornes annoncées au caller) · (3) la prose README.

Le « vert trompeur » — PROUVÉ ungate avant ajout (mémoire verify-uncovered-before-gating) : muter « ≥ 3 beats »→« ≥ 4 »/« ≥ 2 modules »→ « ≥ 5 » dans le README laissait check_readme_claims exit 0. Resserrer la règle (démo plus riche : < 2< 3 modules) sans toucher la prose ⇒ README PÉRIMÉ en silence, documentant un plancher que le code n'applique plus — dérive qu'aucune suite tests/ (exerce la FONCTION check_invariants, jamais la prose) n'attrape, invisible à check_artifacts (.pyout/*.json).

Gate ajouté (ci/check_readme_claims.sh, bloc « Démo · run-sheet · MINIMA D'ORCHESTRATION », juste après le bloc « modules cités ») — RE-DÉRIVE les 2 minima depuis la SEULE comparaison < de la garde (source unique · zéro duplication #5) et exige : (1) autorité = < de check_invariants ; (2) le message d'erreur « (min N) »/« (min M · orchestration) » == plancher effectif ; (3) la prose #3 du README == plancher effectif. Une surface INTROUVABLE échoue AUSSI (ancre morte · anti-évaporation #6).

Vérif — 3 checks verts sur l'arbre propre (≥ 2 modules · ≥ 3 beats). 7 morsures adversariales, gate lancé SEUL, reverts par cp ciblé (jamais git checkout large — cf. incident consigné) : M1 README ≥3 beats≥4 (3) · M2 README ≥2 modules≥5 (3) · M3 SILENT-GREEN garde <3<4 beats (code resserré, prose+message figés) ⇒ message ET README mordent (preuve que l'autorité est le CODE, pas la cohérence interne de la prose) · M4 message (min 3)(min 4) désync de < ⇒ message mord, README reste OK · M5 garde <2<3 modules ⇒ message ET README mordent · M6 prose #3 supprimée ⇒ INTROUVABLE · M7 comparaison < de la garde retirée ⇒ autorité INTROUVABLE. Arbre restauré (git status propre, seule l'addition au gate subsiste). Suite unittest re-verte : 39 tests OK (générateur inchangé). Suite CI complète re-verte : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.

Sprint 8 · buffer · CRM/workflow_vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate_bundle (9ᵉ instance du ledger · dialecte # Invariant N ·)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles CI depuis le registre-source des marqueurs numérotés de la fonction de validation, jamais une liste à la main) était consignée « close à 8 générateurs » (Chat OTOIA · SEO · Fiscal e-CF · Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM Commissions · Mobile) + 2 (deploy_runbook/acceptance, dialecte # INVn). La mémoire invariant-ledger-count-gate avertit précisément : « CLOSED a été prouvé FAUX DEUX FOIS ; re-scanner TOUS les dialectes de marqueur AVANT de croire la clôture ». Un 3ᵉ oubli a été exhumé : crm/workflow_vente/workflow_vente_gen.py (Livrable Sprint 4 l.50 · workflow de vente Frappe lead→…→CONFOTUR) porte un ledger de 9 invariants métier de graphe dans _validate_bundle, marqueurs # Invariant N · (style Mobile), phrasé « schéma + 9 invariants ». Le nombre « 9 » est recopié À LA MAIN à 4 surfaces vives, aucune gatée : générateur l.223 (message CLI de succès) · README l.78 (bloc de commande) · README l.114 (prose de vérification) · README l.125 (prose d'auto-score).

Le « vert trompeur » — PROUVÉ ungate avant ajout (mémoire verify-uncovered-before-gating) : muter « 9 invariants »→« 10 » dans le README laissait check_readme_claims exit 0. Ni check_artifacts (le .py source n'est pas un out/*.json) ni les suites tests/ (FONCTIONS de validation, jamais la prose) n'attrapent la dérive. Ajouter un contrôle # Invariant 10 · à _validate_bundle sans toucher les 4 chaînes ⇒ README + self-report CLI mentent en silence pendant que la CI applique 10 contrôles.

Particularités du registre (vs les 8 blocs # N ·) — style # Invariant N · + fonction _validate_bundle (comme Mobile ; les blocs ancrés # N ·/def _validate\b ne le voient pas). Ledger PROPRE 1..9 compté À PART du schéma (« schéma + N invariants » · pas de schéma-#1, pas de split a/b ; filet doublon nu conservé). Un SECOND nombre distinct — « 25 tests » (suite unittest, déjà gatée) — présent au README : l'ancre sur le mot « invariants » l'exclut nativement ⇒ aucun faux positif. Le README N'A NI titre « ## Les N invariants » (son titre « ## Invariants vérifiés par le CLI » ne porte aucun chiffre) NI énumération 1:1 avec le ledger — sa liste compte le SCHÉMA en item 1 puis FUSIONNE les invariants 8 & 9 dans son item 9 (« Maîtres … ; comptes du manifeste cohérents ») : elle N'EST PAS le ledger, la gater comme contiguë==ledger serait un faux positif fragile (présentation éditoriale). On gate donc UNIQUEMENT (a)(b)(d), comme Audit 5D.

Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · workflow_vente · IDENTITÉ du COMPTE D'INVARIANTS », juste après le bloc Mobile) — RECOMPUTE le ledger depuis le SEUL registre-source (les # Invariant N · de _validate_bundle, corps extrait jusqu'au def suivant) et exige : (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ; (b) toute mention « N invariants » du .py == |ledger| ; (d) toute mention « N invariants » du README == |ledger|. Mention ou registre absent = échec (anti-évaporation #6).

Vérif — 3 checks verts sur l'arbre propre (ledger 1..9 = 9 marqueurs # Invariant N ·, schéma compté à part). 7 morsures adversariales, gate lancé SEUL, reverts par cp ciblé (jamais git checkout large — cf. incident consigné) : M1 .py 9→10 (b) · M2 README 9→10 métier (d) · M3 SILENT-GREEN # Invariant 10 · injecté ⇒ ledger→10 pendant que la prose reste 9 (mord .py ET README, preuve que l'autorité est le CODE) · M4 # Invariant 5 · supprimé ⇒ ledger non contigu (a) + self-reports désync · M5 doublon nu # Invariant 3 · ⇒ vrai doublon (a) · M6 toutes les mentions « 9 invariants » retirées du README ⇒ self-report ÉVAPORÉ · M7 no-false-positive : muter « 25 tests »→« 26 » ⇒ le gate ledger reste SILENCIEUX (l'ancre « invariants » l'exclut ; le rouge vient du gate test-count préexistant, pas du mien). Arbre restauré (git status propre, seule l'addition +113 subsiste). Suite unittest re-verte : 25 tests OK (générateur inchangé). Suite CI complète re-verte : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0. La classe ledger passe à 9 générateurs (+2 dialecte # INVn) ; re-scanner tout NOUVEAU dialecte AVANT de re-déclarer close — ce 3ᵉ oubli confirme l'avertissement de la mémoire.

Sprint 8 · buffer · Faisabilité/bancable — ÉLIMINATION du taux 0.52 codé en dur dans l'oracle anti-invention (source unique CANONICAL · #6/#5)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres » · workflow #5 « zéro duplication »). En balayant les copies MANUELLES du paramètre canonique #9 « point d'équilibre 52 % » restées HORS de la source unique, une a été trouvée au cœur de l'oracle anti-invention du générateur bancable : faisabilite/bancable/bancable_gen.py::_check_derived_arithmetic (l.131) recopiait le taux en littéral 0.52 (exp_pe = math.ceil(0.52 * exp_total)) pour re-calculer indépendamment le point d'équilibre et prouver qu'« aucune valeur n'a été posée ». Paradoxe : le module PRÊCHE l'inverse — son deps.py (« une seule source de vérité pour tout le mandat ») importe CANONICAL = genlib.model.CANONICAL (gaté sur CLAUDE.md #9/#10) précisément pour NE PAS dupliquer 3 % / 8.5 % / 52 %, et finance._pct porte le docstring explicite « jamais codé en dur ici — évite toute dérive ». Le 0.52 de l'oracle était le SEUL survivant en dur (grep : aucun autre 0.03/0.085/0.52 littéral dans le code du module ; finance.py/report.py passent tous par CANONICAL).

Pourquoi un FIX et pas un gate — mutation-test préalable (mémoire verify-uncovered-before-gating) : AUCUNE mutation ne produit un « vert trompeur ». Le 0.52 est transitivement protégé — l'invariant du module compare l'oracle à la figure finance (issue de CANONICAL) au moment de la génération, et le gate bancable (check_readme_claims.sh) LANCE cette génération (bancable_gen validate) ; toute divergence 0.52≠CANONICAL casse validate → gate ROUGE. Un gate d'ancrage du littéral serait donc redondant (interdit #5). MAIS le 0.52 en dur est un BUG LATENT de sens inverse : un changement LÉGITIME du mandat (#9 52 %→55 %, propagé à CANONICAL par les gates model/ template) laisserait l'oracle à 21 pendant que finance produit 22 ⇒ le générateur REFUSERAIT en silence un dossier bancable VALIDE (invariant « manifeste 22 != recalcul 21 »), avec un message cryptique « génération réelle échoue » côté CI. La bonne réponse n'est pas de gater la copie, mais de la supprimer — dériver le taux de la source unique, comme le fait déjà finance.py.

Correctif (bancable_gen.py, _check_derived_arithmetic) — 0.52 remplacé par pe_pct = finance._pct(deps.CANONICAL["point_equilibre_pct"]) (RÉUTILISE le parseur %→fraction déjà livré · zéro nouvelle logique) + garde pe_pct is not None. Docstring l.117 réécrit : le taux est « lu à la SOURCE UNIQUE (CANONICAL · CLAUDE.md #9, gaté), jamais recopié en dur ici ». L'indépendance de l'oracle est préservée là où elle compte (Σ quantités · × prix · ceil) ; le TAUX, lui, est une constante de mandat déjà ancrée en amont (CANONICAL gaté sur CLAUDE.md #9), donc le re-dériver de cette source n'affaiblit rien.

Vérifvalidate fixtures/brief_bancable.json byte-identique à 52 % (point_equilibre_unites = 21 = ⌈52 % × 40⌉). 22 tests unittest verts (inchangés · oracles fixture self-consistants). Preuve du bug levé : en simulant la source à 55 % (deps.CANONICAL["point_equilibre_pct"]="55 %"), les invariants restent OK et point_equilibre_unites passe à 22 = ⌈55 % × 40⌉ (AVANT le fix : oracle figé à 21 ⇒ invariant cassé ⇒ dossier valide rejeté). Suite CI complète re-verte : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0. Leçon : toute copie manuelle d'une constante de mandat n'appelle pas un gate N+1 — quand elle est déjà transitivement protégée, la SUPPRIMER (source unique) vaut mieux qu'un checker redondant (#5), et lève au passage le bug-miroir « le mandat change → le module casse ».

Sprint 8 · buffer · Ledger d'invariants — 2ᵉ FAMILLE (# INVn · / validate_bundle / « N familles d'invariants ») : ancrage du COMPTE de deploy_runbook (11) + acceptance (11)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles CI depuis le registre-source des marqueurs numérotés de la fonction _validate, jamais une liste à la main) avait été déclarée close à 8 générateurs — mais sur le SEUL dialecte # N · / « N invariants » / _validate(_validate_bundle). La mémoire invariant-ledger-count-gate consigne précisément le risque : « un prior CLOSED après 7 était FAUX — un style de marqueur différent avait glissé ; re-scanner LES DEUX styles avant de croire la clôture ». Un re-scan systématique a exhumé une DEUXIÈME famille entièrement HORS gate : marqueurs # INVn · (préfixe INV) dans validate_bundle, phrasé « N **familles d'**invariants ». DEUX générateurs la portent :

  • devops/deploy_runbook/deploy_runbook_gen.py — ledger # INV1..INV11 dans validate_bundle, compte « 11 familles d'invariants » recopié à 3 surfaces vives : générateur l.272 (message CLI de succès) · README l.73 (bloc de commande) · suite tests l.4 (docstring) ;
  • qa/acceptance/acceptance_gen.py — ledger # INV1..INV11 dans validate_bundle, compte à 2 surfaces vives : README l.46 « 11 invariants » · suite tests l.4 « 11 familles d'invariants ». (Le .py acceptance ne porte AUCUN compte numéroté → surface non exigée.)

Le « vert trompeur » — ajouter un contrôle # INV12 · à validate_bundle sans toucher ces chaînes ⇒ README + self-reports + docstrings de test mentent en silence pendant que la CI applique 12 contrôles. Ni check_artifacts (le .py source n'est pas un out/*.json) ni les suites tests/ (FONCTIONS de validation, jamais la prose) n'attrapent la dérive. Prouvé (M4 SILENT-GREEN) : injecter # INV12 · dans validate_bundle (ledger→12, toute la prose reste 11) ⇒ les 3 surfaces deploy_runbook mordent « [11] MAIS ledger en compte 12 » — preuve que l'autorité est le CODE-registre, pas la cohérence interne de la prose.

Particularités — Ledger PROPRE 1..N compté À PART du schéma (« schéma + N invariants »). Les mentions NON numérotées de « invariants » (docstrings « invariants de déploiement/recette », README « Un invariant refuse … ») ne sont pas ancrées par un chiffre → aucun faux positif. Le compte-regex accepte les DEUX formes (« N invariants » ET « N familles d'invariants ») via un préfixe optionnel (?:familles d')?. Les daily_reports/activity_log (snapshots datés — ex. acceptance « 10 » d'avant la croissance 10→11) ne sont PAS des surfaces vives et ne sont volontairement pas ancrés (aucune dérive à surveiller sur un log figé).

Gate ajouté (ci/check_readme_claims.sh, bloc « DevOps deploy_runbook + QA acceptance · IDENTITÉ du COMPTE d'INVARIANTS de validate_bundle », juste après le bloc Mobile) — paramétré sur les 2 modules (zéro duplication). RECOMPUTE le ledger depuis le SEUL registre-source (# INVn · de validate_bundle, corps extrait jusqu'au def suivant) et exige : (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ; (b) CHAQUE surface déclarée porte ≥1 mention « N [familles d']invariants » toutes == |ledger|. Une surface sans mention = self-report ÉVAPORÉ (échec · anti-évaporation #6).

Vérif — 7 checks verts sur l'arbre propre (2 ledgers 1..11 + 5 surfaces). 8 morsures adversariales, gate lancé SEUL, reverts par cp ciblé (jamais git checkout large — cf. incident consigné) : M1 deploy .py 11→12 (b générateur) · M2 deploy README 11→12 (b) · M3 deploy test 11→12 (b) · M4 SILENT-GREEN # INV12 · injecté ⇒ 3 surfaces deploy mordent (autorité = CODE) · M5 # INV7 · supprimé ⇒ ledger non contigu (a) + 3 surfaces désync · M6 acceptance README 11→12 (b) · M7 acceptance test 11→12 (b) · M8 compte retiré du README acceptance ⇒ self-report ÉVAPORÉ. Arbre restauré (git status propre). Suites unittest re-vertes : deploy_runbook 29 tests OK · acceptance 37 tests OK (générateurs inchangés). Suite complète re-verte : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0. Les DEUX familles de ledger sont désormais gatées ; re-scanner tout NOUVEAU dialecte de marqueur (préfixe/phrasé) avant de re-déclarer la classe close.

Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'encadré « Défense en profondeur » (NOM du rôle détenteur de set_user_permissions · scope · famille) sur l'artefact byte-gaté

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le §2 « Modèle RBAC » de 05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md ferme sur un encadré de sécurité « Défense en profondeur » : le pouvoir set_user_permissions (élévation de privilège) est « réservé au SEUL rôle OTO Plateforme RBAC Admin (scope_donnees = groupe, famille = plateforme) — séparation des pouvoirs vérifiée par test automatisé ». La SPEC se réclame donc d'un fait de sécurité tiré du contrat rbac_50_roles.json, sur trois attributs : NOM du détenteur · scope · famille. Or le test cité (test_rbac::test_set_user_permissions_reserve_admin) impose bien scope==groupe ET famille==plateforme à TOUT détenteur — MAIS jamais le NOM du rôle (OTO Plateforme RBAC Admin) NI cette prose §2. L'UNICITÉ (« au SEUL rôle ») et le NOM étaient DÉJÀ gatés côté artefact (bloc « RBAC fixtures », singleton len(fx_sup)==1 depuis custom_docperm.json byte-gaté + prose du README fixtures « émis que pour le rôle NAME ») — mais PAS la 3ᵉ surface prose, le §2 de la SPEC.

Le « vert trompeur » — RENOMMER le détenteur dans le contrat (+régénérer fixtures/out + audit 4Big) laisse le §2 citer un rôle DISPARU en silence : check_artifacts ne voit qu'un HASH de TAILLE du doc (agnostique au nom · la SPEC n'est pas un out/*.json), les 10 tests restent verts (scope/famille inchangés). Prouvé (M6 SILENT-GREEN) : renommer …RBAC Admin…Securite Admin dans le contrat + régénérer les 4 générateurs RBAC & l'audit ⇒ check_artifacts VERT pendant que le nouveau gate mord « §2 nomme ['OTO Plateforme RBAC Admin'] MAIS set_user_permissions réservé à 'OTO Plateforme Securite Admin' » — preuve que l'autorité est l'ARTEFACT byte-gaté, pas la cohérence interne de la SPEC.

Gate ajouté (ci/check_readme_claims.sh, extension du bloc « RBAC fixtures », points 4 & 5 — pas un bloc neuf) — RÉUTILISE fx_sup (le singleton déjà byte-gaté ci-dessus · zéro re-dérivation de l'unicité, interdit #5 « jamais de doublon ») comme NOM-autorité et src_roles (contrat byte-gaté) pour scope/famille. Capte l'encadré **Défense en profondeur** … vérifiée par test automatisé (spec_rd whitespace-normalisé) et exige : le pouvoir set_user_permissions y est nommé ; (a) le NOM du détenteur cité (accents/casse neutralisés) == le seul porteur du flag ; (b)/(c) les scope_donnees = … / famille = … cités == attributs du détenteur dans rbac_50_roles.json ; (5) §6 (row « Séparation des pouvoirs ») cite bien set_user_permissions réservé (anti-évaporation). Encadré/row INTROUVABLE = échec (#6). La glose « (gestion des habilitations) » N'est PAS gatée (éditoriale).

Note process (interdit #5) — un premier jet ajoutait un bloc §2 autonome RECALCULANT le singleton depuis src_roles : doublon de la vérification d'unicité déjà portée par le bloc fixtures (via custom_docperm.json byte-gaté, équivalent par check_artifacts). Écarté ; les 4 checks réécrits en extension du bloc existant, sans ré-affirmer l'invariant d'unicité.

Vérif — 4 checks verts sur l'arbre propre. 6 morsures adversariales, gate lancé seul, reverts par cp/tar ciblés (jamais git checkout large — cf. incident consigné) : M1 §2 renomme le détenteur (prose) ⇒ NOM ≠ artefact · M2 §2 scope groupeentite ⇒ (b) ≠ détenteur · M3 §2 famille plateformeventes ⇒ (c) ≠ détenteur · M4 encadré supprimé ⇒ INTROUVABLE · M5 §6 retire set_user_permissions ⇒ ancre évaporée · M6 SILENT-GREEN (ci-dessus) contrat renommé + artefacts régénérés, check_artifacts VERT ⇒ §2 (a) mord. Suite complète re-verte : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.

Sprint 8 · buffer · Traçabilité roadmap — ancrage de la CITATION roadmap_ref VERBATIM (n° ligne + libellé) des artefacts sur la ligne RÉELLE de la roadmap

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention · toujours vérifier sources archives »). Le pattern « citation vérifiée, pas déclarée » (INV11 de qa/acceptance ancre les roadmap_line NUMÉRIQUES de son artefact au bullet DELIVERABLE réel) portait un angle mort : quatre modules — demo/scenarios, frontend/chat_otoia, frontend/portails, mobile/app_config — publient dans leur out/MANIFEST.json (byte-gaté) un champ roadmap_ref de forme « …ROADMAP… · Sprint N l.A[-B] « <libellé bullet> » », où le libellé est une transcription VERBATIM du bullet de la roadmap à la ligne citée. Or aucun gate ne vérifiait que la ligne A[-B] porte réellement ce texte : check_artifacts ne prouve QUE spec==MANIFEST byte-for-byte (la MÊME citation recopiée des deux côtés, jamais contredite) ; INV11 ne touche QUE les roadmap_line numériques de SON artefact ; l'ancrage Expo mobile ne teste que « roadmap » + « Expo N » dans expo_sdk_source, pas la citation complète.

Le « vert trompeur » — RÉORDONNER la roadmap (les n° de ligne glissent) ou REFORMULER un bullet laisse ces 4 citations pointer une ligne qui dit AUTRE CHOSE : un agent lisant le MANIFEST pour retrouver la promesse d'origine tomberait sur le mauvais bullet. Prouvé par mutation : reformuler le bullet l.63 (Chat OTOIA embedded…) laissait les 6 gates VERTS pendant que chat_otoia/out/MANIFEST.json citait encore « … Chat OTOIA embedded … » à l.63. Aucune suite tests/ (FONCTIONS de résolution, jamais l'ancre à la roadmap) ne mord.

Gate ajouté (ci/check_readme_claims.sh, bloc « Traçabilité roadmap », juste avant sys.exit) — DÉCOUVRE tous les out/MANIFEST.json (glob 1 & 2 niveaux) portant un roadmap_ref citant (l.A[-B] «…») et exige, par module : (1) le fichier cité == la roadmap canonique ; (2) le libellé, NORMALISÉ (markdown **/- retirés · espaces compressés · NFC), est SOUS-CHAÎNE des lignes A..B réelles (robuste à la troncature du bullet et à la plage multi-lignes l.56-57) ; (3) le spec du module porte le MÊME roadmap_ref (identité spec↔artefact publié). Bornes de ligne hors-plage attrapées (renumérotation). Les 4 modules connus sont EXIGÉS présents (anti-évaporation #6 : retirer un roadmap_ref échoue) ; tout AUTRE MANIFEST à citation est ancré en bonus (future-proof).

Vérif — 5 checks verts sur l'arbre propre (4 citations ancrées + recensement « 4 modules attendus présents »). 7 morsures adversariales, gate lancé SEUL, reverts par cp ciblé (jamais git checkout large — cf. incident consigné) : M1 SILENT-GREEN — reformuler le bullet l.63 (AUTORITÉ), MANIFEST inchangé ⇒ libellé ABSENT (preuve que l'autorité est la ROADMAP, pas la cohérence interne de l'artefact) · M2 renuméroter le MANIFEST l.63→l.62 (ligne existante mais autre bullet) ⇒ ABSENT · M3 dériver le libellé du MANIFEST lui-même ⇒ ABSENT · M4 citer un autre fichier roadmap ⇒ fichier ≠ canonique · M5 dériver la citation du SEUL spec ⇒ spec ≠ MANIFEST (artefact incohérent) · M6 retirer roadmap_ref du MANIFEST ⇒ ÉVAPORÉE ['frontend/chat_otoia'] · M7 citer l.9999 ⇒ plage hors roadmap (1..92). Suite complète re-verte : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.

Sprint 8 · buffer · RBAC SPEC §6 — ancrage de l'ÉNUMÉRATION des DocTypes custom sur le catalogue du contrat (2ᵉ surface prose)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le générateur rbac_fixtures_gen émet MANIFEST.custom_doctypes_a_confirmer = l'ensemble des DocTypes custom: true du contrat rbac_50_roles.json (les DocTypes DTP à créer côté VPS avant import — Faisabilité, Publiciste Log, CONFOTUR Application, API Access). Cet ensemble data-derived est transcrit À LA MAIN à deux surfaces prose : (1) rbac/fixtures_gen/README.mddéjà gatée (bloc « RBAC fixtures · catalogue custom ») ; (2) RBAC_50_ROLES_SPEC.md §6 l.117 (« DocTypes custom: true (Faisabilité, Publiciste Log, CONFOTUR Application, API Access) ») — NON gatée. Le bloc apply_plan ne gate que le COMPTE (len(custom_doctypes)), aveugle aux NOMS ; check_artifacts est aveugle (la SPEC n'est pas un out/*.json).

Le « vert trompeur » — AJOUTER un DocType custom au contrat (le MANIFEST rebâtit l'entrée) ou RENOMMER/ÉCHANGER l'un des 4 laisse §6 à ses 4 noms périmés en silence : la cible d'application VPS énumérée par la SPEC ne correspond plus aux DocTypes que l'agent ERPNext doit réellement créer. Vérifié par mutation : dropper Publiciste Log de §6 laissait le gate à exit 0. Même patron que la 2ᵉ surface CONFOTUR/entités (README gaté, fiche non).

Gate ajouté (ci/check_readme_claims.sh, extension du bloc « RBAC fixtures », point 3) — RÉUTILISE fx_cdt (= MANIFEST.custom_doctypes_a_confirmer, byte-gaté, déjà dérivé par le point 1) et le normaliseur d'accents _fx_acc (zéro duplication du contrat) : capte l'énumération §6 (DocTypes custom: true** (…) sur le texte whitespace-normalisé, robuste au retour-ligne 117↔118), extrait les tokens backtickés, et exige set-diff EXACT == catalogue MANIFEST (absent ET en trop, casse/ accents neutralisés). Énumération INTROUVABLE = échec (anti-évaporation #6).

Vérif — 1 check vert sur l'arbre propre (les 4 == MANIFEST). 5 morsures adversariales : A dropper Publiciste Log ⇒ absent · B renommer API AccessAPI Acces ⇒ absent+en-trop · C ajouter Ghost DocType fantôme ⇒ en-trop · D SILENT-GREEN : le MANIFEST GAGNE un 5ᵉ DocType custom (§6 inchangé) ⇒ mord « absents= [new custom dt] » — preuve que l'autorité est l'ARTEFACT, pas la cohérence interne de la SPEC · E énumération §6 supprimée ⇒ INTROUVABLE. Reverts par chemin ciblé (jamais git checkout large — cf. incident consigné). Suite complète re-verte : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.

Note process — un candidat initial (ancrage des attributs de rôle des fiches mobile/rendu/ifc_speckle) s'est révélé DÉJÀ couvert par les blocs existants « Fiche Mobile » / « Fiches Faisabilité » : la ligne d'index MEMORY.md était périmée vs le corps détaillé de la mémoire (classe close le 2026-08-01). Doublon écarté (interdit #5 « jamais accumuler doublons »), index mémoire corrigé.


Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'ÉNUMÉRATION des scope_donnees (ligne Row-level) sur l'ENUM du schéma

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). La table §2 « Modèle RBAC » de 05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md ouvre par « trois niveaux, tous couverts par le schéma » puis sa 3ᵉ ligne (« Row-level (User Permission) | scope_donnees | … ») ÉNUMÈRE à la main l'ensemble EXHAUSTIF des portées row-level : own · equipe · entite (Company) · groupe (consolidé). Par sa phrase d'intro, cet ensemble EST l'enum scope_donnees de rbac.schema.json (la DÉFINITION du modèle). Or aucun gate ne le touchait : §3 compte les rôles par portail, §4 les entités, §5 le résumé, et le bloc userperm gate la table scope → mécanisme d'un autre artefact (userperm_gen/out) — tous aveugles à l'énumération du §2. Même classe que §4 (colonne ancrée sur une source-de-vérité structurée), côté schéma cette fois.

Le « vert trompeur » — si le schéma AJOUTE une portée (region pour un besoin ops réel) ou en RETIRE une (own supprimé, schéma resserré), le §2 reste PÉRIMÉ en silence : il documente un modèle qui CONTREDIT le schéma tout en se réclamant « couvert par le schéma ». Ni check_artifacts (la SPEC n'est pas un out/*.json) ni tests/ (FONCTIONS de validation, jamais l'ancre SPEC↔schéma) ne mordent.

Gate ajouté (ci/check_readme_claims.sh, bloc « 1octies », juste après le §5) — charge l'enum scope_donnees depuis rbac.schema.json (source de vérité du modèle) puis exige : (a) ENSEMBLE des jetons scope backtickés de la ligne §2 == enum (set-diff absent ET fantôme) ; (b) ANCRAGE — la colonne du milieu NOMME bien scope_donnees (sinon on gate la mauvaise ligne) ; (c) sanity — toute portée RÉELLEMENT exercée par le contrat ∈ enum (une portée en usage jamais dans l'enum = schéma/contrat incohérents). Les gloses parenthétiques (Company)/ (consolidé) NE sont PAS gatées (éditoriales · #6). Ligne/section/enum absents = échec (traçabilité #6).

Vérif — 3 checks verts sur l'arbre propre (4 portées own/equipe/entite/groupe == enum). 6 morsures adversariales : M1 §2 retire own ⇒ manquant (a) · M2 §2 ajoute fantôme region ⇒ fantôme (a) · M3 SILENT-GREEN : le schéma GAGNE region, §2 inchangé ⇒ (a) mord manquant region — preuve que l'autorité est le SCHÉMA, pas la cohérence interne de la SPEC · M4 colonne du milieu renommée scope_donneesscope ⇒ ancre perdue (b) · M5 ligne Row-level supprimée ⇒ INTROUVABLE · M6 le schéma RETIRE own (encore exercé par 2 rôles) ⇒ (a) fantôme + (c) portée hors enum. Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.


Sprint 8 · buffer · Faisabilité — ancrage du DOMAINE DES CODES PROJET (garde d'entrée _validate_brief + 2 schémas JSON) sur CLAUDE.md §Projets

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). La classe « constante constitutionnelle recopiée dans une GARDE DE VALIDATION en code, jamais ancrée » (déjà couverte pour le CONTRAT DE FORMAT e-NCF de ncf.py) portait un candidat resté NON gaté et paradoxalement au cœur du Livrable Sprint 3 (« P07 Aqua Terra <1h ») : le DOMAINE des codes projet valides du générateur 4 volets. Ce domaine EST exactement l'ensemble CLAUDE.md §Projets (P01..P09), recopié À LA MAIN à QUATRE surfaces, aucune ancrée, toutes invisibles à check_artifacts (ni .py ni schémas ne sont des out/*.json) :

  1. faisabilite/generator/faisabilite_gen.py::_validate_brief — la GARDE runtime code[:2] == "P0" and code[2] in "123456789" (le domaine EFFECTIF) + le message « … invalide (attendu P01..P09) » (les bornes) ;
  2. faisabilite/version.schema.jsonproperties.projet.pattern "^P0[1-9]$" (validation du version.json par projet) + sa description ;
  3. faisabilite/generator/brief.schema.json — même pattern "^P0[1-9]$" (validation du brief d'entrée) + sa description (qui n'en avait aucune).

Le « vert trompeur » — si CLAUDE.md §Projets AJOUTE un 10ᵉ projet (P10 …, évènement de portefeuille réel), la garde ET les deux patterns REJETTENT P10 comme « invalide (attendu P01..P09) » : le générateur REFUSE de scaffolder un projet LÉGITIMEMENT mandaté (bug d'exécution) pendant que la suite tests/ reste VERTE (oracles P01/P02 self-consistants, jamais comparés à §Projets). Symétriquement, RETIRER un projet laisse la garde trop PERMISSIVE (accepte un code démobilisé). Ni check_artifacts (schémas/.pyout/*.json) ni tests/ (teste la FONCTION _validate_brief, pas son ANCRAGE à §Projets) ne mordent.

Gate ajouté (ci/check_readme_claims.sh, bloc « Faisabilité/generator · DOMAINE DES CODES PROJET », juste après « Fiches #10 ») — RE-DÉRIVE le catalogue de codes depuis CLAUDE.md §Projets (\bP\d{2,}\b sur la ligne ## Projets · source unique) puis exige que chaque surface accepte EXACTEMENT cet ensemble : (a) la garde _validate_brief — reconstruit l'ensemble ACCEPTÉ {préfixe+chiffre} depuis le code source et set-diff == §Projets ; le message cite les bornes min..max ; (b)/(c) les 2 patterns JSON — test COMPORTEMENTAL (compile le pattern, l'applique à une sonde §Projets {P00,P10,P99,P0,PXX,P1,P010,Q01}, exige accepté == §Projets · robuste à la forme de la classe de caractères) + la description DÉCLARE l'ancrage « CLAUDE.md §Projets » et cite les bornes. Une surface INTROUVABLE échoue AUSSI (ancre morte · anti-évaporation #6). Deux descriptions de schéma enrichies pour porter l'ancrage (brief.schema en gagne une).

Vérif — 6 checks verts sur l'arbre propre. 6 morsures adversariales : M1 garde retire le chiffre 7 (validateur trop restrictif) ⇒ set ≠ · M2 message P01..P09→P01..P08 ⇒ bornes ≠ · M3 pattern version élargi ^P0[0-9]$ ⇒ accepte P00 en trop · M4 brief.schema perd l'ancrage §Projets ⇒ ne DÉCLARE plus · M5 SILENT-GREEN : CLAUDE.md §Projets ajoute P10 SEUL (les 4 surfaces inchangées) ⇒ CASCADE sur les 6 lignes (garde accepte≠, 2 patterns manquent P10, message +2 descriptions bornes P09≠P10) — preuve que l'autorité est §Projets, pas la cohérence interne · M6 version.schema perd les bornes de sa description. Les 16 tests unittest du générateur restent verts (garde inchangée). Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.


Sprint 8 · buffer · RBAC SPEC §3 — ancrage de la colonne « Familles rattachées » PAR portail sur le contrat

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). La table §3 « Cartographie portails ↔ familles » de 05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md porte DEUX colonnes data-derived du contrat rbac_50_roles.json : « Nb rôles » (déjà gatée, bloc §3 count) et — juste à sa gauche — « Familles rattachées » : pour chaque portail, l'ENSEMBLE des famille distinctes de ses rôles (ex. direction → {direction, finance, faisabilite, legal}). Cette 2ᵉ colonne était NON gatée : le bloc §3 count la capture avec [^|]* (il ne lit QUE le compte), le §5 « résumé » traite volontairement les regroupements comme ÉDITORIAUX (pas de mapping famille↔groupe, cf. memory rbac-spec-portail-count-gate), et check_artifacts est aveugle (la SPEC n'est pas un out/*.json). Même classe que « colonne SET gatée seulement sur son COUNT voisin » (roleprofile per-profil count vs quels rôles).

Le « vert trompeur » — reclasser la famille d'un rôle À L'INTÉRIEUR d'un portail change le SET de familles sans changer le « Nb rôles » du portail → la cellule §3 ment en silence pendant que le count-gate reste VERT. Prouvé (M4) : reclasser les DEUX rôles legal de direction (Directeur Juridique + Agent ONAPI) en finance laisse le compte à 9==9 VERT tandis que le nouveau gate mord FANTÔME {legal} (la SPEC liste encore legal, le contrat ne l'a plus). Ajouter/retirer une famille (rôle d'une famille inédite) laisse aussi la cellule périmée. Aucune suite tests/ (FONCTIONS de validation, pas l'ancre SPEC↔contrat) ne mord.

Gate ajouté (ci/check_readme_claims.sh, bloc « 1quater-bis » juste après le §3 count-gate, RÉUTILISE src_roles/spec_rd déjà chargés) — RECOMPUTE le SET de familles distinctes PAR portail depuis rbac_50_roles.json[roles][].famille et exige, par ENSEMBLE (absent ET fantôme), l'égalité avec les jetons famille de la cellule. On ne gate QUE les jetons famille bruts (identifiant de tête de chaque item séparé par ,, via (?:^|,)\s*([a-z]+)), PAS les parenthèses ÉDITORIALES (« finance (CFO) », « faisabilite (analyste/rendu/IFC/économiste) » — paraphrase, pas contrat, #6). Ordre NON gaté (réordonner les rôles dans le contrat byte-gaté est sémantiquement neutre pour l'appartenance). Un portail sans cellule échoue AUSSI (INTROUVABLE = régression de traçabilité #6).

Vérif — 6 checks verts sur l'arbre propre (un par portail). 5 morsures adversariales : M1 SPEC retire legal de direction ⇒ MANQUE {legal} · M2 SPEC ajoute rh à achat ⇒ FANTÔME {rh} · M3 SPEC financefinanc sur compta ⇒ MANQUE {finance} · FANTÔME {financ} · M4 SILENT-GREEN : contrat reclasse les 2 rôles legalfinance dans direction, compte inchangé (count-gate 9==9 VERT) ⇒ familles-gate mord FANTÔME {legal} — preuve que l'autorité est le CONTRAT, pas la cohérence interne de la SPEC · M5 SPEC supprime la ligne direction ⇒ cellule INTROUVABLE. Gates re-verts : check_artifacts · check_ci_integrity · check_docs · check_readme_claims · check_regression · guard_constraints tous exit 0.


Sprint 8 · buffer · Faisabilité — ancrage du TEMPLATE CANONIQUE v1.0 (5 surfaces #9/#10) sur CLAUDE.md

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le pattern « constante canonique CLAUDE.md recopiée à la main dans une surface data-derived » (re-dérive la valeur depuis la contrainte, jamais la cohérence interne) couvre déjà le générateur (model.py/README/renderer/scorer/ oracle-test), les fiches ERPNext/CRM (« Fiches #10 »), le module fiscal (moneda), la marque SEO §Entités, la persona Chat OTOIA, les tokens branding #4, etc. Un fichier restait NON gaté (grep : absent de tous les ci/*.sh) et c'est paradoxalement le plus normatif : 05_deliverables_mvp/faisabilite/ TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md, le DOCUMENT-MODÈLE « CANONIQUE » (Livrable Sprint 1) que toute faisabilité en production DOIT reproduire, et que le générateur 4 volets + le Publiciste + la régénération batch S4 sont censés suivre.

Ce template recopie à la main les 6 valeurs de CLAUDE.md #9/#10 à CINQ surfaces structurées, toutes se réclamant de « contrainte #9 »/« #10 », aucune gatée :

  1. l.28 intro §0 « frais 3 % · marketing 8.5 % · point d'équilibre 52 % » (3 pourcentages #9, associés par mot-clé) ;
  2. l.144-146 §5.2 — 3 lignes de table « N % (fixe · contrainte #9) » ;
  3. l.29 intro §0 « USD + DOP · format Letter US · paiements Cardnet » ;
  4. l.149-151 §5.2 — 3 lignes « valeur (… contrainte #10) » (la ligne paiement citant l'exclusion « pas-X ») ;
  5. l.196 §7 checklist « 3 % / 8.5 % / 52 % · USD+DOP · Letter US · Cardnet présents » (énumération des 6 marqueurs).

Le « vert trompeur » — si CLAUDE.md #9 passe 52 %→55 % (ou #10 renomme le prestataire) et que les modules s'alignent, ce TEMPLATE — la RÉFÉRENCE de toutes les faisabilités — reste PÉRIMÉ en silence, imposant à chaque projet un paramètre qui CONTREDIT le mandat pendant qu'il se réclame « contrainte #9/#10 ». Ni check_artifacts (ce n'est pas un out/*.json) ni check_docs/guard_constraints ni aucune suite tests/ (FONCTIONS, jamais l'ancre à CLAUDE.md) ne mordent.

Gate ajouté (ci/check_readme_claims.sh, bloc « TEMPLATE FAISABILITÉ CANONIQUE v1.0 · PARAMÈTRES #9/#10 », juste après « Fiches #10 ») — RÉUTILISE exp (les 6 valeurs déjà recomputées des lignes #9/#10 de CLAUDE.md · zéro re-dérivation) + exp_excl (prestataire exclu du (pas X)). Exige que chacune des 5 surfaces == CLAUDE.md : (1) 3 pcts associés par mot-clé ; (2) SET des 3 lignes « (fixe · contrainte #9) » == {3 %,8.5 %,52 %} (mord aussi ligne ajoutée/retirée) ; (3)/(4) chaque valeur #10 présente en gras dans sa région ancrée + l'exclusion ; (5) énumération des 6 marqueurs == ensemble EXACT (set-diff absent ET en trop). Une surface INTROUVABLE échoue AUSSI (recensement évaporé = régression #6).

Vérif — 5 checks verts sur l'arbre propre. 11 morsures adversariales : M1 intro#9 52→55 · M2 ligne#9 8.5→9 · M3 intro#10 Cardnet→Azul · M4 ligne#10 Letter US→A4 · M5 exclusion Stripe→PayPal · M6 checklist perd Cardnet · M7 checklist 52→60 · M8 intro#9 supprimée (INTROUVABLE) · M9 checklist « présents »→ « présent » (INTROUVABLE) · M10 CLAUDE.md #9 52→55 SEUL ⇒ cascade sur 3 régions (1)(2)(5) · M11 CLAUDE.md #10 Cardnet→Azul SEUL ⇒ cascade sur 3 régions (3)(4)(5) — les deux preuves que l'AUTORITÉ est CLAUDE.md, pas la cohérence interne du template. Un piège corrigé en route : le groupe capturé est le NOMBRE nu (sans %), donc _pct() (qui exige un %) renvoyait Nonefloat() direct. Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.


Sprint 8 · buffer · Fiscal e-CF — ancrage du CONTRAT DE FORMAT e-NCF dans le COMPOSEUR ncf.py (CODE) + son ORACLE de test

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le bloc « Fiscal · e-CF DGII · CONTRAT DE FORMAT » existant ancre le format de l'identifiant fiscal DGII (E + tipoeCF(2) + secuencia(10) = 13, Ley 32-23) sur le SEUL bloc e_ncf byte-gaté de out/ecf_plan.json (dérivé de ecf_spec.json) et couvre 4 surfaces de PROSE : README ×3 + fiche erpnext_backend. Mais le composeur d'exécution lui-mêmeecflib/ncf.py, le code Frappe qui VALIDE et COMPOSE réellement le numéro e-NCF côté VPS/Compupar — porte TROIS copies INDÉPENDANTES du format, AUCUNE dérivée de l'artefact, + un oracle de test qui les bénit, tous NON gatés :

  1. ENCF_RE = re.compile(r"^E([0-9]{2})([0-9]{10})$") (l.26) — la regex de VALIDATION (cœur de is_valid_encf/parse_encf) ;
  2. FORMULE = "E + tipoeCF(2) + secuencia(10)" (l.27) — la chaîne publiée par compose_encf (renvoyée au caller VPS) ;
  3. docstring l.8 « "E" + tipoeCF (2 chiffres) + secuencia (10 chiffres) → 13 caracteres » + commentaire l.25 « Ley 32-23 : E + tipo(2) + secuencia(10) = 13 » (deux proses-en-code) ;
  4. tests/test_ecf_dgii.py:246 — oracle hardcodé assertEqual(…["formule"], "E + tipoeCF(2) + secuencia(10)").

Le « vert trompeur » — un rango DGII à 12 chiffres (évènement ops réel, cité dans le commentaire du gate existant) régénère ecf_spec→ecf_plan, donc les 4 proses gatées SUIVENT. Mais ENCF_RE reste {2}{10} : compose_encf zfill à 10 et is_valid_encf REJETTE un e-NCF désormais valide → bug d'exécution du composeur fiscal, pendant que la suite tests/ reste VERTE (son oracle == FORMULE périmée, self-consistante). Ni check_artifacts (ncf.py n'est pas un out/*.json) ni tests/ (teste les FONCTIONS, pas leur ANCRAGE à l'artefact) ne mordent. Même patron que l'oracle/formateur du Publiciste déjà gaté.

Gate ajouté (ci/check_readme_claims.sh, extension du bloc e-NCF · surfaces 5→9) — RÉUTILISE prefix/tipo_len/seq_len/total déjà dérivés du bloc e_ncf byte-gaté + le helper ecf_check (zéro re-dérivation du format #6). Ancre les 5 surfaces CODE/test au SEUL artefact : (5) ENCF_RE, (6) FORMULE, (7) docstring, (8) commentaire, (9) oracle de test. Une surface absente échoue AUSSI (INTROUVABLE · anti-évaporation #6).

Vérif — 5 checks verts sur l'arbre propre. 7 morsures adversariales : M1 ENCF_RE {10}→{12} · M2 FORMULE secuencia(10)→(12) · M3 docstring 10→12 chiffres · M4 commentaire secuencia(10)→(12) · M5 oracle test (10)→(12) · M6 SILENT-GREEN — l'ARTEFACT passe à seq=12 pendant que ncf.py reste à 10 ⇒ les 5 surfaces mordent TOUTES (preuve que l'autorité est l'artefact byte-gaté, pas la cohérence interne du composeur) · M7 FORMULE renommée ⇒ INTROUVABLE. Les 39 tests unittest e-CF restent verts (composeur inchangé). Gates re-verts : check_artifacts · check_ci_integrity · check_docs · check_readme_claims · check_regression · guard_constraints tous exit 0.


Sprint 8 · buffer · CRM Commissions — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (7ᵉ instance du ledger)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles CI depuis le registre-source # N · de _validate, jamais une liste à la main) a déjà couvert Chat OTOIA, SEO, Fiscal e-CF, Legal CONFOTUR, CRM Dossier Vente et QA Audit 5D. Le 7ᵉ générateur — dernier candidat restant recensé au ledger (crm/commissions ·10) — était NON gaté : crm/commissions/commissions_gen.py (barème de commissions vendeurs · roadmap l.51).

Le compte « 10 invariants » y est transcrit à la main à CINQ endroits, aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un out/*.json) :

  1. message de succès CLI validate « … schéma + 10 invariants verts. » (l.209) ;
  2. aide du sous-parseur validate « schéma + 10 invariants » (l.221) ;
  3. README bloc de commande « schéma + 10 invariants » (l.60) ;
  4. README titre de section « ## Les 10 invariants » (l.64) ;
  5. README prose de score « 10 invariants de cross-cohérence » (l.92).

Ajouter un contrôle # 11 · à _validate sans toucher les 5 chaînes ⇒ le README ET les self-reports du .py mentent en silence pendant que la CI applique 11 contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la prose) n'attrape ce « vert trompeur ».

Particularité du registre Commissions (≈ Dossier Vente) : le ledger est PROPRE 1..10 — le schéma est compté À PART (phrasé « schéma + N invariants »), donc pas de schéma-#1 non marqué et pas de split a/b ; on garde la détection de doublon nu comme filet. Le module porte AUSSI un SECOND nombre — « 25 tests » (la suite unittest, déjà gatée par ailleurs) présent dans le README : l'ancre sur le mot « invariants » l'exclut nativement (« 25 tests » ≠ « invariants ») ⇒ aucun faux positif. Comme Dossier Vente, ce README A un titre « ## Les N invariants » ET une énumération numérotée du ledger → on gate (a)(b)(c)(d)(e).

Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · Commissions · IDENTITÉ du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source (# N · de _validate) et exige :

  • (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
  • (b) toute mention « N invariants » du .py == |ledger| ;
  • (c) l'annonce du titre README « ## Les N invariants » == |ledger| ;
  • (d) toute mention « N invariants » du README == |ledger| ;
  • (e) l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N. Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).

Vérif — 5 checks verts sur l'arbre propre (ledger 1..10 = 10 marqueurs # N ·, schéma compté à part). 7 morsures adversariales : M1 self-report .py 10→11 (b) · M2 titre README 10→11 (c+d) · M3 prose README 10→11 (d) · M4 ajout marqueur # 11 · ⇒ ledger→11 pendant que la prose reste 10 (mord .py ET README titre/mention ET l'énumération, preuve que l'autorité est le CODE) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de vrai doublon (a) · M7 énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Le distinct « 25 tests » reste INVISIBLE au gate (ancre « invariants »). Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0. Série ledger close : les 7 générateurs portant le nombre magique d'invariants sont désormais tous gatés.


Sprint 8 · buffer · QA Audit 5D — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (6ᵉ instance du ledger)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles CI depuis le registre-source # N · de _validate, jamais une liste à la main) a déjà couvert Chat OTOIA, SEO, Fiscal e-CF, Legal CONFOTUR et CRM Dossier Vente. Le 6ᵉ générateur portant le même nombre magique restait NON gaté : qa/audit_5d/audit_5d_gen.py (candidat restant recensé : crm/commissions ·10, ledger propre non gaté).

Le compte « 15 invariants » y est transcrit à la main à SIX endroits, aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un out/*.json) :

  1. docstring sous-commande module (l.19 « valide schéma + 15 invariants ») ;
  2. docstring _validate (l.80 « 15 invariants de cohérence audit/contrôles ») ;
  3. message de succès CLI validate « … 15 invariants — verdict … » (l.218) ;
  4. aide du sous-parseur validate « valide schéma + 15 invariants » (l.229) ;
  5. README bloc de commande « schéma + 15 invariants » (l.66) ;
  6. README prose d'architecture « CLI build/validate + 15 invariants » (l.81).

Ajouter un contrôle # 16 · à _validate sans toucher les 6 chaînes ⇒ le README ET les self-reports du .py mentent en silence pendant que la CI applique 16 contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la prose) n'attrape ce « vert trompeur ».

Particularité du registre Audit 5D (≈ Dossier Vente, ≠ Fiscal) : le ledger est PROPRE 1..15 — le schéma est compté À PART (phrasé « schéma + N invariants »), donc pas de schéma-#1 non marqué et pas de split a/b ; on garde la détection de doublon nu comme filet. MAIS le module porte un SECOND nombre distinct — « 17 contrôles » (le compte de fonctions-contrôle de qalib/controls.py, déjà gaté par ailleurs), présent dans le .py ET le README : un \d+ invariants ancré sur le mot « invariants » l'exclut nativement (« 17 contrôles » ≠ mot « invariants ») ⇒ aucun faux positif sur l'arbre propre. Contrairement à Dossier Vente/Fiscal, ce README n'a NI titre « ## Les N invariants » NI énumération numérotée du ledger → on ne gate que (a)(b)(d).

Gate ajouté (ci/check_readme_claims.sh, bloc « QA · Audit 5D · IDENTITÉ du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source (# N · de _validate) et exige :

  • (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
  • (b) toute mention « N invariants » du .py == |ledger| ;
  • (d) toute mention « N invariants » du README == |ledger|. Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).

Vérif — 3 checks verts sur l'arbre propre (ledger 1..15 = 15 marqueurs # N ·, schéma compté à part). 6 morsures adversariales : M1 self-report .py 15→16 (b) · M2 mention README 15→16 (d) · M3 ajout marqueur # 16 · ⇒ ledger→16 pendant que la prose reste 15 (mord .py ET README, preuve que l'autorité est le CODE) · M4 suppression # 7 · ⇒ gap non-contigu (a) · M5 doublon nu # 5 · ⇒ détection de vrai doublon (a) · M6 no-false-positive : le distinct « 17 contrôles » reste INVISIBLE au gate (l'ancre « invariants » l'exclut, arbre propre VERT). Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.


Sprint 8 · buffer · CRM Dossier Vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (5ᵉ instance du ledger)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles CI depuis le registre-source # N · de _validate, jamais une liste à la main) a déjà couvert Chat OTOIA, SEO, Fiscal e-CF et Legal CONFOTUR. Le 5ᵉ générateur portant le même nombre magique restait NON gaté : crm/dossier_vente/doctype_dossier_vente_gen.py (candidats restants recensés : qa/audit_5d ·15, crm/commissions ·10, tous ledgers propres non gatés).

Le compte « 12 invariants » y est transcrit à la main à SEPT endroits, aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un out/*.json) :

  1. docstring module (l.14 « schéma + 12 invariants de ») ;
  2. docstring _validate (l.77 « 12 invariants de cross-cohérence ») ;
  3. message de succès CLI validate « … schéma + 12 invariants verts. » (l.218) ;
  4. aide du sous-parseur validate (l.230) ;
  5. README bloc de commande « schéma + 12 invariants » (l.56) ;
  6. README titre de section « ## Les 12 invariants » (l.60) ;
  7. README prose de score « 12 invariants de cross-cohérence » (l.83).

Ajouter un contrôle # 13 · à _validate sans toucher les 7 chaînes ⇒ le README ET les self-reports du .py mentent en silence pendant que la CI applique 13 contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la prose) n'attrape ce « vert trompeur ».

Particularité du registre Dossier Vente (≈ CONFOTUR, ≠ Fiscal) : le ledger est PROPRE 1..12 — le schéma est compté À PART (phrasé « schéma + N invariants »), donc pas de schéma-#1 non marqué et pas de split a/b. On garde néanmoins la détection de doublon nu comme filet. Le README porte AUSSI une énumération numérotée sous le titre de section (item 1 = conformité au schéma, puis les contrôles), gatée en contiguïté 1..N.

Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · Dossier Vente · IDENTITÉ du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source (# N · de _validate) et exige :

  • (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
  • (b) toute mention « N invariants » du .py == |ledger| ;
  • (c) l'annonce du titre README « ## Les N invariants » == |ledger| ;
  • (d) toute mention « N invariants » du README == |ledger| ;
  • (e) l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N. Une mention ou le registre absent échoue AUSSI (traçabilité #6).

Vérif — 5 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs # N ·, schéma compté à part). 7 morsures adversariales : M1 self-report .py 12→13 (b) · M2 titre README 12→13 (c+d) · M3 prose README 12→13 (d) · M4 ajout marqueur # 13 · ⇒ ledger→13 pendant que la prose reste 12 (mord .py ET README ET l'énumération, preuve que l'autorité est le CODE) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de vrai doublon (a) · M7 énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.


Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles CI depuis le registre-source # N · de _validate, jamais une liste à la main) a déjà couvert Chat OTOIA, SEO et Fiscal e-CF. Le 4ᵉ générateur portant le même nombre magique restait NON gaté : legal/confotur/confotur_application_gen.py.

Le compte « 14 invariants » y est transcrit à la main à SIX endroits, aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un out/*.json) :

  1. docstring module (l.24 « schéma + 14 invariants de ») ;
  2. docstring _validate (l.87 « 14 invariants de cross-coherence ») ;
  3. message de succès CLI validate « … schéma + 14 invariants verts. » (l.283) ;
  4. aide du sous-parseur validate (l.295) ;
  5. README bloc de commande « schéma + 14 invariants » (l.56) ;
  6. README commentaire d'arborescence « CLI build/validate · 14 invariants » (l.82).

Ajouter un contrôle # 15 · à _validate sans toucher les 6 chaînes ⇒ le README ET les self-reports du .py mentent en silence pendant que la CI applique 15 contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la prose) n'attrape ce « vert trompeur ».

Particularité du registre CONFOTUR (≠ Fiscal) : le ledger est PROPRE 1..14 (pas de schéma-#1 non marqué, pas de split a/b). MAIS le README porte AUSSI un compte de SOUS-ENSEMBLE éditorial — « 2 invariants refusent tout champ monétaire » (l.92) — à NE PAS confondre avec le total. Un \d+ invariants naïf mordrait donc l'arbre PROPRE (il verrait {2, 14}). Le gate exclut le sous-ensemble par lookahead négatif (\d+)\s+invariants(?!\s+refusent), appliqué au .py comme au README.

Gate ajouté (ci/check_readme_claims.sh, bloc « Legal · CONFOTUR · IDENTITÉ du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source (# N · de _validate) et exige :

  • (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
  • (b) toute mention-total « N invariants » du .py (hors « refusent ») == |ledger| ;
  • (c) toute mention-total du README (hors « refusent ») == |ledger|. Une mention ou le registre absent échoue AUSSI (disparition d'un chiffre = régression de traçabilité #6).

Vérif — 3 checks verts sur l'arbre propre (ledger 1..14 = 14 marqueurs # N ·). 6 morsures adversariales : M1 self-report .py 14→15 (b) · M2 README commande 14→15 (c) · M3 README arborescence 14→13 (c) · M4 ajout marqueur # 15 · ⇒ ledger→15 pendant que la prose reste 14 (mord .py ET README, preuve que l'autorité est le CODE) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de vrai doublon (a). M7 no-false-positive : sous-ensemble « 2 → 3 invariants refusent » reste VERT (le lookahead exclut bien le sous-ensemble). Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_regression · check_readme_claims tous exit 0.


Sprint 8 · buffer · Publiciste — gate d'ANCRAGE des DEVISES du site public sur CLAUDE.md #10

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le bloc Publiciste/branding existant ancre les 4 tokens DESIGN de CLAUDE.md #4 (2 couleurs + 2 typographies) sur leurs 4 copies. Mais le MÊME module publiciste porte une 2ᵉ constante constitutionnelle — les devises CLAUDE.md #10 (« USD + DOP ») — recopiée à 4 endroits data-derived, aucun gaté, tous invisibles à check_artifacts (pas un out/*.json) :

  1. lib/branding.py DEVISE_PRIMAIRE/DEVISE_SECONDAIRE — constantes auto- étiquetées « contrainte #10 · USD + DOP », sans gate ;
  2. lib/generator.py _fmt_usd/_fmt_dop — les formateurs qui IMPRIMENT le code devise sur la grille de prix du site public vente.otov7.com (sortie face-public : une devise hors #10 = mandat trahi en silence) ;
  3. lib/generator.py footer HTML « Prix … en USD + DOP (Cardnet) » ;
  4. tests/test_publiciste.py — l'oracle hardcodé assertIn("USD 150 000", …), une copie de plus jamais comparée à CLAUDE.md.

Aucune suite tests/ (qui teste des fonctions de rendu, pas l'ancre à CLAUDE.md) n'attrape la dérive : renommer la devise #10 et aligner le module reste vert alors que le site émettrait la mauvaise devise ; ne rien bouger reste vert alors que branding.py se dit « #10 » en mentant.

Gate ajouté (ci/check_readme_claims.sh, bloc « Publiciste/generator+branding · DEVISES CANONIQUES ») — RE-PARSE les 2 devises depuis la ligne #10 de CLAUDE.md (indépendamment de exp amont · aucun skip hérité) puis exige :

  • (a) branding.py DEVISE_PRIMAIRE/SECONDAIRE == #10 (ordre inclus) ;
  • (b) l'ensemble des codes émis par les formateurs generator.py == #10 ;
  • (c) le footer HTML cite les 2 devises dans l'ordre + (Cardnet) ;
  • (d) l'oracle du test == devise primaire #10. Un claim absent échoue AUSSI (traçabilité #6 · anti-évaporation).

Vérif — 5 checks verts sur l'arbre propre. 6 morsures adversariales : M1 branding primaire USD→EUR · M2 formateur _fmt_usd→EUR (hors #10) · M3 footer USD+DOP→USD+EUR · M4 oracle test → EUR · M5 CLAUDE.md #10 USD→EUR = cascade sur les 4 copies simultanément (preuve que l'ancre est CLAUDE.md, pas la cohérence interne) · M6 footer supprimé → INTROUVABLE. Gates re-verts : check_readme_claims · check_docs · guard_constraints · check_ci_integrity tous exit 0.


Sprint 8 · buffer · Fiscal e-CF DGII : gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le pattern « ledger d'invariants » a déjà été appliqué à Chat OTOIA et SEO (recompute le nombre d'invariants CI depuis le registre-source # N · de _validate, jamais une liste à la main). Le 3ᵉ générateur portant le même nombre magique restait NON couvert : fiscal/ecf_dgii/ecf_dgii_gen.py.

Le compte « 12 invariants » y est transcrit à la main à SEPT endroits, aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un out/*.json) :

  1. ecf_dgii_gen.py docstring module (l.24) ;
  2. docstring _validate (l.110) ;
  3. message de succès CLI validate « … schéma + 12 invariants verts. » (l.277) ;
  4. aide du sous-parseur validate (l.289) ;
  5. README bloc de commande « schéma + 12 invariants » (l.65) ;
  6. README titre de section « ## Les 12 invariants » (l.69) ;
  7. README prose de score « 12 invariants de cross-cohérence » (l.105).

Ajouter un contrôle # 13 · à _validate sans toucher les 7 chaînes ⇒ le README ET les self-reports du .py mentent en silence pendant que la CI applique 13 contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la prose) n'attrape ce « vert trompeur ».

Particularité du registre fiscal (≠ SEO/Chat OTOIA au ledger propre 1..N) : l'invariant 1 est la validation de SCHÉMA (validate(bundle, schema), non marquée # 1 ·), les marqueurs couvrent 2..N, et l'invariant 12 est splitté en # 12a · (unicité évènements) / # 12b · (comptes du manifeste) — deux volets d'un même invariant. Le gate reconstruit donc le ledger = {1:schéma} marqueurs-top-level, en collapsant 12a/12b, tout en mordant un vrai doublon d'un numéro NON suffixé.

Gate ajouté (ci/check_readme_claims.sh, bloc « Fiscal · e-CF DGII · IDENTITÉ du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source (# N · de _validate) et exige :

  • (a) ledger CONTIGU 1..N (schéma #1 présent + marqueurs 2..N sans lacune ; doublon nu = défaut mordu ; 12a/12b comptés 1×) ;
  • (b) toute mention « N invariants » du .py == |ledger| ;
  • (c) l'annonce du titre README == |ledger| ;
  • (d) toute mention « N invariants » du README == |ledger| ;
  • (e) l'ÉNUMÉRATION numérotée du README (« 1. … 2. … N. ») elle-même contiguë 1..N (item ni sauté ni dupliqué).

Vérif — 5 checks verts sur l'arbre propre (ledger 1..12 = schéma #1 + 11 marqueurs). 8 morsures adversariales : M1 self-report .py 12→13 (b) · M2 titre README 12→13 (c+d) · M3 mention README 12→13 (d) · M4 ajout marqueur # 13 · ⇒ ledger→13 pendant que la prose reste 12 (b+c+d, preuve que l'autorité est le CODE, pas la prose) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de vrai doublon · M7 énumération README 7.→8. ⇒ deux 8/pas de 7 (e) · M8 suppression validate(bundle, schema) ⇒ invariant 1 introuvable. Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_readme_claims · check_regression tous exit 0.


Sprint 8 · buffer · Mobile app_config : gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate_bundle (8ᵉ instance)

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). La classe « ledger d'invariants » (recompute le nombre d'invariants CI depuis le registre-source numéroté de la fonction de validation, jamais une liste à la main) était déjà appliquée à 7 générateurs : Chat OTOIA · SEO · Fiscal e-CF · Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM Commissions. Le 8ᵉ générateur portant le même nombre magique restait NON couvert : mobile/app_config/app_config_gen.py.

Le compte « 12 invariants » y est transcrit à la main à QUATRE endroits, aucun gaté, tous invisibles à check_artifacts (le .py/.md/test n'est pas un out/*.json) :

  1. app_config_gen.py message de succès CLI « … schéma + 12 invariants verts. » (l.228) ;
  2. README.md bloc de commande « schéma + 12 invariants » (l.48) ;
  3. README.md prose de récap « Schéma + 12\ninvariants verts » (l.67-68 — le « 12 » et le mot séparés par un saut de ligne) ;
  4. tests/test_mobile_app_config.py docstring « … + les 12 invariants du CLI » (l.4).

Ajouter un contrôle # Invariant 13 · à _validate_bundle sans toucher les 4 chaînes ⇒ le README, le self-report CLI ET la docstring de test mentent en silence pendant que la CI applique 13 contrôles. Aucune suite tests/ (qui EXÉCUTE la validation mais ne compte jamais la prose) ne l'attrape.

Particularités vs les 7 blocs précédents (documentées dans l'en-tête du gate) :

  • le registre-source utilise le style # Invariant N · (le mot « Invariant » précède le numéro) — les blocs ancrés sur le style nu # N · NE le voient PAS ; ancrage sur #\s*Invariant\s+(\d+)… ;
  • la fonction s'appelle _validate_bundle (pas _validate) — l'ancre def _validate\b des autres blocs échoue (\b échoue avant _bundle) ;
  • ce README n'a NI titre « ## Les N invariants » NI énumération numérotée → on gate (a)(b)(b')(d), en AJOUTANT la surface fichier-de-tests (b'), absente des 7 blocs précédents (leurs tests ne portaient pas le chiffre).

Gate ajouté (ci/check_readme_claims.sh, bloc « Mobile · app_config · IDENTITÉ du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source (# Invariant N · de _validate_bundle) et exige :

  • (a) ledger CONTIGU 1..N (schéma compté à part ; lacune OU doublon nu = défaut mordu) ;
  • (b) toute mention « N invariants » du .py == |ledger| ;
  • (b') toute mention « N invariants » du fichier de tests == |ledger| ;
  • (d) toute mention « N invariants » du README == |ledger|.

Vérif — 4 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs). 8 morsures adversariales : M1 self-report CLI 12→13 (b) · M2 README bloc-commande 12→13 (d) · M3 README récap (newline-split) 12→13 (d, preuve que \s+ traverse le saut de ligne) · M4 docstring test 12→13 (b', la surface neuve) · M5 ajout marqueur # Invariant 13 · ⇒ ledger→13 pendant que les 3 surfaces prose restent 12 (b+b'+d, preuve que l'autorité est le CODE, pas la prose) · M6 suppression # Invariant 7 · ⇒ gap non-contigu (a) + désync prose · M7 doublon nu # Invariant 5 · ⇒ détection de vrai doublon · M8 renommage _validate_bundle ⇒ registre introuvable. Gates re-verts : check_artifacts · check_docs · guard_constraints · check_ci_integrity · check_readme_claims · check_regression tous exit 0.


Sprint 8 · buffer · Doc QA : documentation du 2ᵉ étage QA (batterie de gates statiques ci/*.sh) dans 03_agents/qa/AGENT.md

Démarche — vérifier AVANT de gater (mémoire verify-uncovered-before-gating). La série récente empile des gates anti-dérive ; avant d'en ajouter un 9ᵉ, j'ai d'abord cherché une vraie dérive présente plutôt qu'un gate spéculatif :

  1. Scan ledger-vs-prose sur les 12 générateurs à ledger d'invariants. Faux signal relevé : fiscal/ecf_dgii semblait afficher ledger_markers=10 vs prose=12. Investigation : le ledger n'est pas nu 1..12 — l'invariant #1 (validation de schéma, validate(bundle, schema)) n'est pas marqué # 1 · par design, et #12 est scindé en 12a/12b. Le gate fiscal (check_readme_claims.sh l.1908) synthétise déjà ledger = marqueurs{2..12} {1} et vérifie self-report / titre README / mentions / énumération = 12. Aucune dérive ; mon compteur grossier ne voyait ni le schéma-#1 ni 12a/12b.
  2. Sondé les comptes de 03_agents/{crm,qa}/AGENT.md (« Total CRM 81 (25+31+25) », « 17 contrôles / 5 dimensions », « 8 livrables + 7 métriques ») → tous gatés (l.681, 871, 2176, 2624, 3671…). Coverage très dense, conforme à la mémoire.

Conclusion : pas de gate à ajouter (ç'aurait été redondant · CLAUDE.md #5).

Trou documentaire réel comblé. Le titre + le scope de qa/AGENT.md promettent des « gates de méta-niveau », mais le corps ne tabulait que les 4 modules Python d'audit (audit_5d · audit_4big · regression · acceptance) — il omettait entièrement la batterie de gates statiques ci/*.sh, qui sont pourtant l'application méta-niveau littérale (moitié de l'enforcement QA). Un lecteur croyait donc QA = 4 modules.

Ajout — section « Deuxième étage QA : la batterie de gates statiques (ci/*.sh) » listant les 7 gates courants par nom + rôle (check_ci_integrity, check_readme_claims, check_artifacts, check_regression, check_docs, guard_constraints, validate_json). Anti-dérive by design : le texte ancre l'autorité du périmètre sur check_ci_integrity (qui découvre l'ensemble via git ls-files 'ci/*.sh' et refuse tout gate décâblé/orphelin) et présente la liste comme l'état courant, PAS une constante figée → aucun compte agrégé driftable introduit.

Sûreté — prose sans lien Markdown neuf (check_docs), sans URL git interdite ni terme CRM/paiement banni nu ; les lignes citant les prohibitions portent un marqueur (proscrites/interdits/bannie) donc guard_constraints les laisse passer. Vérif : 7 gates re-verts (guard_constraints · check_docs · check_readme_claims · check_ci_integrity · check_artifacts · check_regression · validate_json tous exit 0).


Sprint 8 · buffer · Angle mort du gate « comptes de tests par suite » : la cellule rbac (fiche pointant une SPEC, pas un README)

Démarche — vérifier AVANT de conclure « couvert » (mémoire verify-uncovered-before-gating). La série récente ayant épuisé les surfaces driftables évidentes, j'ai audité la complétude d'un gate existant plutôt que d'en empiler un neuf (CLAUDE.md #5).

Constat. Le gate check_readme_claims.sh §« Fiches agents · comptes de tests PAR SUITE (colonne « Tests ») » recompute chaque cellule | N | des tables de livrables des 03_agents/*/AGENT.md depuis l'autorité regression_plan.json (plan.suites[path].test_methods). Son row_re exigeait pourtant que le lien de la ligne pointe …/README.md. Or une ligne y échappe : la fiche erpnext_backend (l.28) pointe le module rbac/ via son point d'entrée RBAC_50_ROLES_SPEC.md (le contrat 50-rôles, pas un README) — donc son row_re ne mordait pas, et sa cellule « Tests | 10 | » restait non vérifiée.

Mutation-test (preuve du trou). Cellule rbac 10 → 99 sur la fiche → check_readme_claims restait VERT (exit 0), aucune ligne rbac évaluée. C'est exactement la classe « vert trompeur » que cette section du gate existe pour tuer (un régénérateur de fiche pourrait y écrire un faux compte sans que la CI bronche). La somme prose « RBAC 60 tests (10+11+12+11+16) » est order-indépendante → ne contraint pas la cellule-table rbac individuelle. Vrai angle mort.

Correctif (extension du gate, pas gate N+1). row_re accepte désormais tout *.md d'entrée du dossier module (/[\w.-]+\.md au lieu de /README\.md), pas seulement README — le garde path not in auth (déjà présent) protège de tout élargissement fortuit : seules les lignes pointant une suite gatée connue sont évaluées, les autres restent ignorées. Une seule ligne entre ainsi dans le périmètre : rbac. Commentaire mis à jour pour documenter le cas SPEC.

Vérif. (1) Arbre propre VERT + la ligne rbac est désormais VUE (AGENT.md:28 · rbac — Tests 10 == source (10)). (2) Mutation 10→99 désormais MORDUE (Tests 99 MAIS source dit 10 → régénérer la fiche, exit 1). (3) Restauration → exit 0. 7 gates re-verts : check_ci_integrity · check_artifacts · check_docs · check_readme_claims · check_regression · guard_constraints · validate_json tous exit 0.


Sprint 8 · buffer · Reprise du canal daily_reports/ (lapsus depuis 2026-07-30) + sweep launch-readiness sourcée

Constat. Le canal canonique des rapports quotidiens (05_deliverables_mvp/daily_reports/YYYY-MM-DD.md · fixé roadmap §Escalations, référencé par les 13 fiches 03_agents/*/AGENT.md) n'avait plus d'instantané consolidé depuis le 2026-07-30. Or ~2 douzaines de sessions buffer ont continué depuis — leur trace fine vit dans 05_activity_log/ (journal technique par session), mais aucun snapshot MVP sourcé n'existait pour la phase Sprint 8. Trou de reporting réel au niveau stakeholder/direction.

Décision (workflow #5 — pas de gate redondant). La série récente ayant épuisé les surfaces driftables évidentes en durcissant des gates, et la phase Sprint 8 attendant une vérification de régression exhaustive (pas un module neuf), j'ai (a) mené une sweep de vérification launch-readiness et (b) repris le canal daily_reports/ plutôt que d'empiler un gate N+1.

Réalisé.

  • Créé 05_deliverables_mvp/daily_reports/2026-08-02.md : instantané MVP 100 % sourcé (chaque chiffre re-vérifié contre l'artefact commité · anti-invention #6),
    • note de convention réconciliant les 2 canaux de journalisation (05_activity_log/ = technique/session · daily_reports/ = synthèse/direction — pas des doublons ; un lecteur d'AGENT.md était mal aiguillé vers un canal figé au 30/07).
  • Audit doc-vs-réalité de 2 fiches (interdit « documenter du code sans vérifier son existence courante ») : crm/AGENT.md table livrables exacte (25/31/25 = 81, recomptés depuis regression_plan.json) ; bim/AGENT.md — son seul point de contact commité faisabilite/generator/genlib/renderer.py existe et rend bien le Volet 4 Ingénierie en {{…}}. Aucune sur-promesse.

État courant sourcé (recalculé indépendamment, concordant) : 22/22 modules qualité 4Big à 100/100 (PASS) · régression 22 suites · 564/564 tests (PASS) · recette 15/15 promesses in_repo (true) · 13 agents · 7 gates CI verts + reproductibilité out/ byte-identique.

Vérif. Les 7 gates re-verts (exit 0) avec le nouveau fichier : check_docs (liens du rapport résolus) · guard_constraints (aucun terme interdit introduit) · check_readme_claims · check_ci_integrity · validate_json · check_artifacts · check_regression. Sûreté : rapport dans le canal canonique (roadmap), zéro chiffre inventé, tous liens Markdown vers cibles existantes.


Sprint 8 · buffer · Audit d'exactitude doc-vs-réalité des 11 fiches AGENT.md restantes — 0 dérive réelle · 2 « faux positifs » d'audit levés · 1 clarification de lisibilité (faisabilite)

Nature — Poursuite de la tâche explicitement recommandée par le daily report 2026-08-02 (« Poursuivre l'audit d'exactitude des 11 fiches AGENT.md restantes, mêmes contrôles que crm/bim — repérer toute sur-promesse d'artefact ou compte périmé »). CLAUDE.md interdit « documenter du code sans vérifier son existence courante ». Pas un gate de plus (workflow #5) : vérification, pas empilage.

Méthode — 4 agents Explore en parallèle sur les 11 fiches (devops · erpnext_backend · faisabilite · frontend_console · ifc_speckle · mobile · onapi_legal · publiciste · qa · rendu · seo). Pour chaque fiche : existence de CHAQUE chemin in-repo cité · recompute de CHAQUE compte (tests via grep -c 'def test_' / regression_plan.json · invariants via marqueurs numérotés) · présence de CHAQUE artefact out/*.json · toute sur-promesse d'un livrable in-repo inexistant.

Résultat brut des agents — 9 fiches « FULLY ACCURATE » (comptes de tests recoupés : deploy_runbook 29 · e-CF 39 · confotur 44 · publiciste 23 · audit_5d 37 · audit_4big 34 · regression 26 · acceptance 37 · seo 36 · portails 19 · chat 31 ; SEO 258 mots-clés / 10 nœuds schema.org / 10 pages hreflang ; Expo SDK 54). 2 fiches signalées en « dérive » — toutes deux VÉRIFIÉES FAUX POSITIFS après contrôle manuel (mémoire verify-uncovered-before-gating : prouver avant d'agir) :

  1. onapi_legal (agent : 3 chemins « CRITICAL cassés » l.40/49/99 : ../rbac/rbac_50_roles.json, ../crm/workflow_vente/[out/]). Faux positif : ces chemins backtick sont relatifs à l'arbre des livrables (05_deliverables_mvp/legal/, sujet de la fiche §24), PAS à l'emplacement de la fiche. Ils MIROITENT le code du générateur : rbac_scan.py fait os.path.join(_DELIVERABLES, "rbac", "rbac_50_roles.json") et builder.py publie workflow_source: "crm/workflow_vente/workflow_vente_spec.json". Les 3 cibles existent bien depuis 05_deliverables_mvp/legal/ (vérifié ls). L'agent avait supposé à tort une résolution fiche-relative. Convention documentée dans la mémoire backtick-path-escapes-check-docs (les code-spans backtick sont volontairement NON link-checkés). Aucune correction — la fiche est exacte.

  2. faisabilite (agent : data_room/_TEMPLATE_…/, _META/version.json, otoia/capabilities/knowledge/faisabilite_template_v{X}.md « HIGH · sources inexistantes »). Faux positif : ces chemins sont explicitement VPS-runtime / « Composants à créer » — préfixe absolu /opt/oto/…, placeholder de version {X}, section §95 « Composants à créer ». La fiche les réconcilie déjà (§15-17 « composants runtime OTOIA/VPS … restent hors périmètre worker » ; §27-29 « aucune faisabilité concrète PXX n'est commitée ici … le rendu réel … s'exécute côté OTOIA/VPS #8 »). Hors-repo ASSUMÉ, pas une sur-promesse. Table §Livrables exacte : TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md (existe) · generator 16 tests · bancable 22 tests. Aucune sur-promesse in-repo.

Seule action — clarification de LISIBILITÉ (faisabilite), pas un correctif de dérive. Le point qui a induit l'agent en erreur est une vraie ambiguïté pour un lecteur humain : le bloc « Sources canoniques » (§31-34) listait 3 chemins VPS {X} sans signaler que la contrepartie commitée/testée/gatée du template canonique est l'in-repo TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md (pourtant déjà en tête de la table §Livrables). Ajout d'une note additive (style de réconciliation déjà employé §15-17) : bandeau « Chemins VPS runtime — hors périmètre worker (#8), non commités in-repo » + lien Markdown vers le template committé « référence de vérité pour le worker ». Zéro chiffre, zéro constante, zéro fait inventé ; purement désambiguïsant.

Sûreté / vérif — Avant édition : grep confirme qu'AUCUN gate ne parse la fiche faisabilite ni la section « Sources canoniques » (édition prose libre). Après : 7 gates re-verts (exit 0)check_ci_integrity · check_artifacts · check_docs (le nouveau lien Markdown résout vers une cible existante) · check_readme_claims · check_regression · guard_constraints (aucun terme interdit introduit) · validate_json. Bilan audit : les 13 fiches AGENT.md sont désormais TOUTES auditées exactes (crm/bim la session précédente + les 11 ici) ; les 2 « dérives » signalées étaient des artefacts de convention (backtick deliverables-relative · chemins VPS/futurs assumés), pas des dérives.


Session 20260802_120544 · Clôture de journée du canal daily_reports/

Contexte — Reprise en phase Sprint 8 · buffer (tous sprints 1→7 livrés côté repo). Relu CLAUDE.md, la roadmap, MEMORY.md et le journal du jour. Constat : la file de travail substantielle est saturée — 7 gates verts, 22/22 qualité, 564/564 régression, 15/15 recette, 13/13 fiches AGENT.md auditées exactes (campagne close au commit 2bc3afb, session 113542).

Décision (workflow #5 · pas d'empilage de gate redondant ; #6 · anti-invention) — Aucune tâche roadmap ouverte ni blind-spot prouvé. Plutôt qu'un gate N+1 ou un faux « module », réconcilier le reporting stakeholder : le snapshot canonique du jour 05_deliverables_mvp/daily_reports/2026-08-02.md (écrit à 11:11) était devenu vérifiablement périmé — sa propre « Prochaine tâche #2 » (auditer les 11 fiches restantes) et sa réserve 4 (« audit limité à 2/13 fiches ») ont été closes à 11:35 par le commit 2bc3afb.

Réalisé

  • Recompute indépendant (python3 sur les artefacts commités) de TOUS les agrégats de tête, concordant : qualité 22 modules PASS (pass_score 95) · régression 22 suites / 564 ran / 564 passés / 0 échec · recette matrice de 15 items, verdict true · 13 répertoires 03_agents/*/.
  • Addendum de clôture ADDITIF au snapshot 2026-08-02.md (corps 110539 laissé intact comme record historique) : acte la clôture 13/13 fiches, lève la réserve 4 caduque, réaffiche l'état re-vérifié (inchangé/vert), révise la « prochaine tâche » (file d'audit vide → veille buffer). Zéro chiffre inventé — chaque valeur re-dérivée d'un artefact.
  • Choix anti-doublon (interdit #5 · « JAMAIS accumuler doublons ») : addendum au fichier-jour canonique plutôt qu'un énième fichier per-session quasi-identique.

Vérifications7 gates CI re-joués → exit 0 (check_ci_integrity · check_artifacts byte-identique · check_docs liens résolus · check_readme_claims · check_regression · guard_constraints aucun terme interdit · validate_json). Aucune commande touchant au VPS.

Non fait (hors périmètre #8) — déploiement serveur réel, runner Gitea, Playwright serveur : VPS 153.75.250.214, plan ordonné devops/deploy_runbook.


Session 20260802_123546 · Dérive « 12 agents » sur les docs de mandat de tête (hors README)

Contexte — Reprise en Sprint 8 · buffer. Relu CLAUDE.md, roadmap (0 item ouvert), MEMORY.md, journal du jour. Re-vérification indépendante multi-angle de la santé (recompute python3 sur artefacts commités) : 7 gates → exit 0 · qualité 22/22 @ 100/100 (verdict PASS · scores tous à 100, pas seulement le seuil) · régression 22 suites / 564 tests / 0 échec, 13/13 modules présents dans la matrice · recette 15/15 in_repo · constantes canoniques (3 %/8.5 %/52 %) toutes ancrées · README (13 agents ×2, 22/22, 15 promesses) exact vs artefacts.

Blind-spot RÉEL trouvé (surface distincte de l'audit fiches 03_agents/* clos au 2bc3afb) — audit des docs de mandat de TÊTE, non couverts par le gate de claims (scopé README.md uniquement). PORTAIL_BANCABLES_4BIG.md vérifié exact (commande + sorties 50_financier_bancable/{fr,en,es}.md concordent avec bancable_gen.py). Mais deux docs affichaient encore « 12 agents » :

  • AGENTS_EXISTING_ASSETS.md:1 (titre) · 02_master_prompt/MASTER_PROMPT_DTP_v2.md:28.

Réalité re-dérivée : 13 agents — 13 répertoires 03_agents/, README canonique « 13 agents » (×2, déjà gaté), le 13e (publiciste) LIVRÉ+GATÉ (fiche + générateur 05_deliverables_mvp/publiciste/ 100/100 + suite de régression publiciste). Les « 12 » précédaient l'ajout du publiciste → dérive doc-vs-réalité.

Réalisé

  • Correction du compte 12 → 13 dans les deux docs (fait objectif, 03_agents/ = source unique · #6 anti-invention).
  • Clarification publiciste (AGENTS_EXISTING_ASSETS.md §13) : marqueur « (à créer) » levé, note « Statut mandat : LIVRÉ » pointant fiche + générateur gaté, tout en préservant la liste « À développer » comme runtime VPS hors périmètre worker (#8) — respecte la convention « VPS-runtime à créer ≠ dérive » (memory fiche-accuracy-audit-closed). Deux liens Markdown ajoutés → résolus (check_docs).
  • Extension du gate check_readme_claims.sh (surface prouvée NON couverte par mutation-test) : nouvelle boucle qui réutilise le n_agents déjà dérivé de git ls-files 03_agents/*/AGENT.md (pas de nouvelle machinerie · #5 pas de doublon) et exige que les DEUX docs de tête citent exactement ce compte. Classe doc-numeric-claims-gate.

Vérifications — nouveau check mutation-testé (12 réinjecté → gate ROUGE « dit 12 MAIS 03_agents/ en compte 13 », restauré → exit 0). 7 gates re-joués → exit 0. Aucune commande touchant au VPS.

Non fait (hors périmètre #8) — runtime VPS publiciste (publiciste.py, systemd/ inotify, WhatsApp), déploiement serveur : hors périmètre worker.


Sprint 8 · buffer · Compte d'agents — ancrage des 3 surfaces vives de GAP_ANALYSIS_SPRINT1.md (dont la coche de couverture DoD) sur la source unique n_agents

Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »). Le commit précédent (cb8d931) a ancré « 13 agents » à git ls-files 03_agents/*/AGENT.md (source unique n_agents) sur les 2 docs de mandat de tête (AGENTS_EXISTING_ASSETS titre + MASTER_PROMPT §28), en plus du README ×2. Un balayage de complétude a exhumé une 3ᵉ surface entièrement HORS gate : le livrable Sprint 1 05_deliverables_mvp/ GAP_ANALYSIS_SPRINT1.md (Definition of Done) retranscrit ce même compte à trois endroits vifs — l.12 périmètre d'audit (« audit des 13 agents DTP »), l.53 constat global (« 12 des 13 agents partent d'un socle existant »), et surtout l.214 la coche de couverture DoD (« Couverture 13 agents + OTOIA core »). Cette coche AFFIRME une complétude COURANTE : elle DOIT suivre le nombre réel de fiches.

Le « vert trompeur » — PROUVÉ ungate avant ajout (mémoire verify-uncovered-before-gating) : GAP_ANALYSIS n'est référencé par AUCUN ci/*.sh (grep = 0) ; muter l.214 « 13 »→« 14 » laissait check_readme_claims exit 0. Or ce NOMBRE a une dérive prouvée : le publiciste fut ajouté comme 13ᵉ agent (12→13), forçant la correction de cb8d931. Si un 14ᵉ agent était mandaté, la coche DoD mentirait en silence. check_artifacts est aveugle (la SPEC n'est pas un out/*.json) ; aucune suite tests/ ne touche l'ancre doc↔disque.

Gate ajouté — EXTENSION de la boucle head-docs existante (ci/check_readme_claims.sh, bloc « Comptes d'agents dans les docs de mandat de tête »), pas un bloc neuf : 3 tuples (GAP, motif, label) réutilisant le n_agents DÉJÀ dérivé (zéro nouvelle machinerie · zéro re-dérivation · #5). On n'ancre QUE le total — le « 12 » analytique de l.53 (agents à socle existant) N'EST PAS gaté (motif \d+ des (\d+) agents capture le 2ᵉ nombre). Surface INTROUVABLE = échec (anti-évaporation #6). ci/README.md mis à jour : le décompte d'agents est désormais documenté comme ancré à 7 surfaces vives.

Vérif — 3 checks verts sur l'arbre propre (les 3 == 13). 6 morsures adversariales, reverts par cp ciblé (jamais git checkout large — cf. incident consigné) : M1 DoD 13→14 (mord) · M2 périmètre 13→99 (mord) · M3 constat total 13→12 (mord) · M4 ligne DoD supprimée ⇒ INTROUVABLE · M5 no-false-positive : muter le « 12 » analytique (12→11), total intact ⇒ gate exit 0 (preuve qu'on ne gate que le total) · M6 SILENT-GREEN : dérive de la SOURCE (git rm --cached d'une fiche ⇒ n_agents=12), docs figés à 13 ⇒ les 5 surfaces head-docs+GAP mordent « dit 13 MAIS 03_agents/ en compte 12 » (preuve que l'autorité est le git-index, pas la cohérence interne). Arbre restauré. 7 gates re-joués → exit 0 : check_ci_integrity · check_artifacts · check_docs · check_readme_claims · check_regression · guard_constraints · validate_json. Aucune commande touchant au VPS.

Sprint 8 · buffer · roles_couverts (« 44 ») — enquête anti-dérive → non-surface prouvée + documentation du garde-fou mono-source (évite un gate N+1 redondant #5)

Contexte — Série anti-dérive Sprint 8. Un balayage des comptes numériques a exhumé un candidat de cohérence croisée : le total « 44 rôles couverts par les 5 portails » apparaît, identique, dans trois manifestes byte-gatés (frontend/portails, frontend/chat_otoia, mobile/app_config · counts.roles_couverts=44). Chaque « 44 » vif n'est ancré qu'à SON propre manifeste (mobile README · demo/scenarios/out/run_sheet.md). Aucun ci/*.sh ne compare les trois entre eux. Hypothèse initiale : surface non couverte, analogue au cross-check RBAC « 50 » (rbac/apply_plan/rbac_apply_plan.py:149 exige fixtures==userperm==roleprofile).

Enquête — l'analogie RBAC-50 ne tient PAS (mémoire fix-vs-gate-transitively- protected-constant). Le « 50 » EST cross-gaté car ses trois rendus re-dérivent chacun leur décompte avec leur propre logique. Ici, au contraire, les trois modules importent la même fonction de partition : mobilelib/deps.py:77 et chatlib/deps.py:84 font tous deux roles_by_portail = portails_builder.roles_by_portail (unique définition frontend/portails/wslib/builder.py:36), appliquée à l'unique contrat rbac/rbac_50_roles.json (une seule copie de chaque dans git ls-files). roles_couverts est donc transitivement mono-sourcé (code partagé + donnée partagée) : les trois totaux ne peuvent diverger que si cette fonction ou ce contrat change — auquel cas les trois changent ensemble et check_artifacts (reproductibilité byte-à-byte) vérifie chaque rebuild. Un gate « 44==44==44 » serait redondant (#5) et masquerait la vraie garantie (le partage de code), pas une surface à protéger.

Livraison — DOCUMENTATION du garde-fou, pas un gate. Ajout d'un bloc sourcé « Non-surface documentée — roles_couverts N'EST PAS cross-gaté, à dessein » dans ci/README.md (entre le bloc mobile Expo et le bloc CONFOTUR) : cite les 3 imports partagés + le contrat unique + contraste explicite avec le cross-check RBAC-50, pour qu'une session future ne rajoute PAS le gate redondant. Chemins en backticks (non link-checkés, cf. mémoire backtick-path-escapes-check-docs).

Vérif — 4 citations de lignes vérifiées exactes (deps.py:77, deps.py:84, wslib/builder.py:36, rbac_apply_plan.py:149). 7 gates re-joués → exit 0 : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims. Aucune commande touchant au VPS.

Sprint 8 · buffer · Reporting stakeholder (canal 2) — rattrapage du snapshot daily_reports/2026-08-02 sur les 2 sessions run_ci.sh (commits 49123ec/dcb463e)

Contexte — Série buffer Sprint 8. Le canal 2 (instantané stakeholder daily_reports/, mémoire two-logging-channels) avait été rattrapé à 14:35 (addendum 143555) jusqu'au commit 1c0cb8e. Depuis, 2 sessions buffer ont introduit l'outil DevOps run_ci.sh (49123ec @15:06 · dcb463e @15:36) sans mise à jour du canal 2 : l'instantané avait de nouveau glissé de 2 sessions, alors que sa trace fine existait déjà au canal 1 (ce fichier).

Livraison — 3ᵉ addendum ADDITIF dans 05_deliverables_mvp/daily_reports/2026-08-02.md (corps + 2 addenda intacts). Consolide les 2 sessions : (1) 49123ecrun_ci.sh, miroir LOCAL du gate dérivé de gate.needs (30 jobs = 7 gates ci/*.sh + 23 suites module · PAS un gate #5 · root ∉ ci/*.sh) ; (2) dcb463e — retrait du décompte figé 30/23/7 du commentaire, désormais DÉRIVÉ à l'exécution. Aucun nouveau module (contrainte respectée) · aucun gate ajouté (#5) · le seul livrable net est l'outil DevOps déjà gaté par construction.

Anti-invention #6 — chaque chiffre du tableau d'état re-dérivé par recompute python3 indépendant sur les artefacts commités : 22/22 qualité (scores distincts = {100} · verdict PASS) · 22 suites / 564 ran / 564 passés / 0 échec / 0 erreur / 0 skip (regression_run.json · PASS) · 15/15 promesses in_repo (acceptance_matrix.json · verdict true) · 13 fiches (03_agents/) · gate.needs = 30. Zéro donnée nouvelle introduite ; l'état launch-readiness est inchangé et vert.

Vérifrun_ci.sh30 PASS · 0 FAIL · 0 SKIP (miroir complet du gate) · check_docs.sh → conforme (les liens relatifs du nouvel addendum — ../../run_ci.sh, ../../.gitea/workflows/ci.yml, ../qa/... — mirrorent le style des addenda existants et passent le link-check). Arbre git status --porcelain vide avant l'édition. Aucune commande touchant au VPS.

Session 20260802_163601 · Sprint 8 · buffer · Fiche devops/AGENT.md : documentation du livrable run_ci.sh (angle mort de complétude)

  • Constat : run_ci.sh (miroir local du gate, ajouté aux commits 49123ec/dcb463e) est déjà indexé dans la table Navigation du README.md racine, mais absent de la fiche de son agent propriétaire (03_agents/devops/AGENT.md) — la table « Livrables DevOps réellement produits » n'en listait que 2 (ci.yml+ci/, deploy_runbook). Gap de complétude doc-vs-réalité, pas une dérive de chiffre.
  • Action : ajout d'une 3e ligne à la table + sous-section dédiée décrivant run_ci.sh avec exactitude : outillage local (confort dev), PAS un gate (CLAUDE.md #5 · aucun ci/*.sh, hors gate.needs, racine ∉ ci/ donc hors invariant INV-B), liste de checks dérivée de gate.needs (zéro liste en dur → zéro dérive), décompte imprimé à l'exécution (#6). Aligné sur la mémoire local-ci-runner.
  • Zéro nouveau module · zéro gate ajouté (#5). Seul changement = prose de fiche.
  • Vérif : check_docs.sh valide le nouveau lien ../../run_ci.sh (HARD) → vert · guard_constraints.sh vert · run_ci.sh30 PASS · 0 FAIL · 0 SKIP.

Session 20260802_170601 · Sprint 8 · buffer · DevOps — run_ci.sh : -h|--help + en-tête --static honnête (UX du lanceur, PAS un gate #5)

Contexte — Série buffer Sprint 8, tout vert (run_ci.sh → 30 PASS). L'outil run_ci.sh (miroir local du gate, mémoire local-ci-runner) était fonctionnellement complet mais avait 2 vraies aspérités d'UX, reproduites avant correction :

  1. run_ci.sh --help (et -h) échouait en « option inconnue » + exit 2, alors qu'un bloc d'usage riche dormait dans l'en-tête du script, jamais surfacé — la convention --help la plus universelle manquait.
  2. En mode --static, l'en-tête annonçait toujours « 30 jobs = 7 gates + 23 suites » alors que seuls les 7 gates s'exécutent → le récap final « 7 PASS » contredisait l'en-tête (vert honnête mais message trompeur).

Action (édition de l'outil run_ci.sh uniquement + accord doc) :

  • Ajout d'une fonction usage() (heredoc) et branchement -h|--help AVANT la résolution git → l'aide marche même hors checkout (vérifié : exit 0 depuis /tmp), alors qu'un vrai run hors git reste exit 3. Codes de sortie documentés (0/1/2/3).
  • En-tête d'exécution rendu mode-conscient : en --static, affiche « 30 jobs · mode --static → 7 gates ci/*.sh seuls (23 suites ignorées) » — plus de contradiction avec le récap. Le mode plein est inchangé.
  • Message d'option inconnue et bloc d'usage ci/README.md mis à jour (ajout --help).
  • Toutes les valeurs restent DÉRIVÉES de gate.needs (n_jobs/n_gates/n_suites) — aucune liste ni compte figé introduit (#6). Zéro nouveau module · zéro gate ajouté (#5) : run_ci.sh demeure un lanceur (racine ∉ ci/*.sh, hors gate.needs, INV-B).

Vérif--help/-h → exit 0 (dans + hors git) · --bogus → exit 2 · en-tête --static désormais « 7 gates seuls » cohérent avec « 7 PASS » · --list inchangé (30 jobs) · check_docs · check_readme_claims · guard_constraints verts · run_ci.sh (plein) → 30 PASS · 0 FAIL · 0 SKIP. Arbre propre avant édition. Aucune commande touchant au VPS.

Session 20260802_190604 · Sprint 8 · buffer · QA — vérification launch-readiness : mutation-test que les 7 gates statiques MORDENT encore (7/7 RED, revert propre)

Contexte. Repo 100 % construit (run_ci.sh → 30 PASS · 0 FAIL) ; sprints 1→7 livrés côté dépôt, phase buffer Sprint 8. Un audit doc-vs-réalité frais (top-level docs + 13 fiches 03_agents/*/AGENT.md + daily_reports + roadmap) n'a trouvé aucune dérive non gatée — la surface driftable évidente est épuisée. Décision (#5 — ne pas empiler un gate N+1 redondant) : plutôt qu'inventer du travail, exécuter le livrable QA propre de Sprint 8 (« Regression tests exhaustifs ») sous l'angle le plus fort : prouver que les gates existants attrapent réellement une régression (un gate vert qui ne mord pas est pire qu'absent — cf. mémoire verify-uncovered-before-gating

  • guard-constraints-flags-usage-not-mention).

Méthode. Pour chacun des 7 gates statiques ci/*.sh, injecter une mutation ciblée de la classe de dérive qu'il est censé traquer, exécuter le gate, exiger un exit non-zéro (RED), puis revert par chemin ciblé (jamais git clean — #interdits) et re-vérifier l'arbre à 0 fichier modifié.

# Gate Mutation injectée Résultat
1 guard_constraints URL interdite https://github.com/… ajoutée à README.md RED ✓ (Interdit détecté — GitHub)
2 check_ci_integrity retrait de constraints-guard de gate.needs (ci.yml) RED ✓ (ABSENT de gate.needs + INV-B)
3 check_docs lien Markdown interne cassé ./this/does/not/exist.md RED ✓ (lien cassé)
4 check_readme_claims compte fiches 1314 dans README.md RED ✓ (README dit 14 MAIS artefact dit 13)
5 check_artifacts octet parasite ajouté à crm/commissions/out/MANIFEST.json RED ✓ (DÉRIVE … diffère du build frais)
6 validate_json JSON syntaxiquement invalide dans un artefact commité RED ✓ (exit 1)
7 check_regression totals.passed 564565 dans out/regression_run.json RED ✓ (DÉRIVE : le run frais diffère du commité)

Deux « faux verts » — analysés, PAS des trous de couverture :

  • Mutation d'un MANIFEST.json de régression testée d'abord contre check_regressionvert : normal, check_regression ne possède QUE regression_run.json (artefact de run, compteurs exécutés) ; le MANIFEST.json (artefact de build) est possédé par check_artifacts — mutation re-testée contre lui → RED ✓. La frontière de propriété entre les deux gates est nette et correcte.
  • Première tentative de mutation de regression_run.json inefficace (bump d'un int de premier niveau — or les compteurs sont imbriqués sous totals, aucun int racine) → rewrite byte-identique → vert légitime. Corrigée en mutant totals.passed (ligne 7 ci-dessus) → RED ✓.

Conclusion. 7/7 gates statiques MORDENT sur leur classe de dérive respective, frontières de propriété inter-gates confirmées, aucun trou de couverture. Le verrou anti-régression de la plateforme est prouvé opérationnel pour le launch (pas seulement « vert par chance »). Zéro nouveau module · zéro gate ajouté (#5) · zéro fichier de production modifié — travail de vérification pur, chaque mutation revert par chemin ciblé. Arbre final : git status --short → 0 ligne · run_ci.sh30 PASS · 0 FAIL · 0 SKIP. Aucune commande touchant au VPS.


Session 20260802_200607 · Sprint 8 buffer · reporting canal 2 + audit doc-vs-réalité

Contexte. Relu CLAUDE.md, la roadmap (Sprint 8 · Launch + Buffer), MEMORY.md et le journal du jour. Tous les sprints 1→7 livrés côté repo ; run_ci.sh30 PASS · 0 FAIL · 0 SKIP en entrée de session. Surface driftable évidente épuisée (fiches 13/13 closes, gates saturés + les 7 statiques prouvés mordants en 190604).

Décision (workflow #5 — ne pas empiler de gate redondant). Plutôt qu'inventer un module ou un gate N+1, exécuter les deux tâches de veille légitimes du buffer saturé :

  1. Audit doc-vs-réalité (classe prose-fact non gatée). Recompute python3 indépendant des agrégats + sweep des faits-prose sur README racine, ci/README.md, AGENTS_EXISTING_ASSETS.md, PORTAIL_BANCABLES_4BIG.md, 13 fiches 03_agents/*/AGENT.md et les READMEs module. Résultat : aucune dérive (chaque fait spot-checké contre l'état courant est exact). Agrégats confirmés : 22/22 qualité (scores = {100}) · 564/564 régression (totals suites 22) · 15/15 recette (in_repo, verdict True) · 13 fiches · gate.needs = 30 (7 gates + 23 suites).

  2. Reporting stakeholder (canal 2). Le snapshot daily_reports/2026-08-02.md avait lapsé depuis le 4ᵉ addendum (180604, 18:06). Rattrapage des 3 sessions intercalées (183604 97d3b64+f954cc1 · 190604 7c79e43 · 193605 8a08065) → 5ᵉ addendum ADDITIF (corps + 4 addenda intacts), chaque chiffre re-sourcé par recompute indépendant.

Vérifications. run_ci.sh30 PASS · 0 FAIL · 0 SKIP après édition ; check_docs, check_readme_claims, guard_constraints verts (seuls fichiers touchés = docs de reporting, hors périmètre des gates de code). Zéro nouveau module · zéro gate ajouté (#5) · zéro fichier de production modifié. Aucune commande touchant au VPS (#8).


Session 20260802_203609 · Sprint 8 buffer · audit prose-fact ciblé + résolution faux-positif confotur

Contexte. Relu CLAUDE.md, la roadmap (Sprint 8 · Launch + Buffer), MEMORY.md et le journal du jour. Entrée de session : run_ci.sh30 PASS · 0 FAIL · 0 SKIP ; arbre git status --short → 0 ligne. Tous sprints 1→7 livrés. Aucun TODO/placeholder réel (les placeholder restants sont tous du design anti-invention #6, pas des trous).

Décision (workflow #5 — ne pas empiler de gate/module). Plutôt qu'un 6ᵉ addendum de reporting (canal 2 déjà à jour au 200607), cibler la classe prose-fact NON gatée : faits libres (non-numériques) dans les README modules que CI ne vérifie pas. Fan-out lecture (agent Explore) sur tous les 05_deliverables_mvp/*/README.md + sous-README, chaque affirmation factuelle spot-checkée contre le .py/.json du même module.

Résultat : aucune dérive prose-fact. Un seul candidat remonté par l'agent — legal/confotur/README.md:58 « 44 tests (dont 8 négatifs) » — résolu en FAUX-POSITIF avec preuve :

  • TestNegative (tests/test_confotur.py 242296) = exactement 8 test_reject_* → « 8 négatifs » exact.
  • TestBuilderGuards (297324, 3 assertRaises) est une classe distincte (guard/raises), pas des « négatifs » ; l'agent les avait agrégés à tort (→ 11). Total def test_ = 44 ✓.
  • Même README « 14 invariants » (56/82) confirmé : 14 entrées # INVn · dans confotur_application_gen.py, alignées sur ses 4 auto-phrasings (compte CONFOTUR déjà gaté).

Trace mémoire. Nouveau guard-fou confotur-8-negatifs-not-drift (genre fiche-accuracy-audit-closed / otov7-platform-not-drift) : un audit test-count naïf re-flaggera « 8 négatifs » → vérifier les frontières de classe avant de « corriger ».

Vérifications. run_ci.sh30 PASS · 0 FAIL · 0 SKIP. Zéro nouveau module · zéro gate ajouté (#5) · zéro fichier de production modifié — session de vérification pure (seul écrit repo = ce journal). Aucune commande touchant au VPS (#8).


Session 20260802_210609 · Sprint 8 · buffer · Dérive doc-vs-CODE réelle corrigée (docstring de production)

Contexte. État stationnaire : run_ci.sh = 30/30 vert, roadmap S1S8 livrée et gatée, reliquat roadmap (pipelines BIM/IFC/rendu Blender · déploiement VPS · soumissions stores) hors périmètre sandbox. Plutôt qu'un énième audit no-op, chasse ciblée d'un défaut réel dans un module de parsing (là où les cas limites mordent).

Trouvaille (classe NOUVELLE, distincte des dérives README gatées). Le docstring d'en-tête de 05_deliverables_mvp/publiciste/lib/parser.py (bloc « Sources lues », l. 26) annonçait :

40_llm_outputs/commercial.md → positionnement FR (fallback prix §5.3)

Or aucun fallback de prix depuis commercial.md n'existe dans le code : commercial.md n'alimente QUE parse_positionnement_fr (l. 275+, « 1er paragraphe non-titre »). Preuve : grep -rniE "5\.3|fallback.*prix|prix.*fallback|commercial.*prix" lib/ publiciste.pyune seule occurrence, le docstring lui-même. Le docstring décrivait donc un comportement inventé qui n'a jamais été implémenté — exactement l'anti-pattern que CLAUDE.md #6 (zéro invention) proscrit, transposé du côté documentation-du-code.

Correction. Reformulé en → positionnement FR (1er paragraphe non-titre), aligné mot pour mot sur la logique réelle de parse_positionnement_fr. Docstring seul — zéro changement de logique, zéro changement de signature, zéro test touché.

Distinction de classe. Ce n'est PAS une dérive de compte gatée (check_readme_claims) ni une dérive de prose README (check_docs) : c'est une inexactitude dans le docstring du module de production, surface non couverte par les gates existants et distincte des classes déjà cataloguées. Vérifié qu'aucun sibling (README.md publiciste, AGENT.md publiciste) ne répète la fausse allégation « fallback prix » → correction ponctuelle, source unique.

Vérifications. ./run_ci.sh30 PASS · 0 FAIL · 0 SKIP (inchangé). Zéro nouveau module · zéro gate ajouté (#5) · un seul fichier de production touché (docstring). Aucune commande VPS (#8). Commit a2c2e0b.


Session 20260802_213610 · Sprint 8 · buffer · Dérive doc-vs-CODE réelle : docstring demo_scenario omettait un output produit

Contexte. État stationnaire (run_ci.sh = 30/30 vert, roadmap S1S8 livrée et gatée, reliquat hors périmètre sandbox). Poursuite de la classe de dérive découverte la session précédente (docstring de production vs code réel — surface non gatée, distincte des gates README/compte). Fan-out de 3 audits read-only (Explore) sur les ~22 modules à docstring dense, chacun vérifiant CHAQUE allégation factuelle du docstring d'en-tête contre le code.

3 candidats remontés · 2 réfutés par vérification indépendante · 1 réel corrigé.

RÉFUTÉ (faux-positif de compte). fiscal/ecf_dgii/ecf_dgii_gen.py l.110 « 12 invariants » : l'agent alléguait « la numérotation commence à 2 → pas d'invariant 1 ». FAUX — l'invariant 1 EST la conformité au schéma (errors = list(maison.validate(bundle, schema)), l.112) et 12a/12b sont les deux moitiés de l'invariant 12 (unicité + comptes/tipos). Décompte réel : 1(schéma)+2..11+12(a,b) = 12 exactement, aligné mot-pour-mot sur le ledger README « Les 12 invariants » + help CLI + tests + message stdout. Piège de compte classique (cf. mémoire invariant-ledger-count-gate, confotur-8-negatifs-not-drift). Zéro changement.

RÉFUTÉ implicite. Cluster RBAC/legal/bancable (7 modules) : aucune dérive, tous les inputs/outputs/compteurs/comportements des docstrings exacts.

RÉEL — corrigé. demo/scenarios/demo_scenario_gen.py. Le générateur écrit TROIS fichiers — run_sheet.json (l.196), MANIFEST.json (l.196) ET run_sheet.md (l.202-203, le « prompteur » Markdown, annoncé jusque dans le message stdout l.206 « + prompteur run_sheet.md »). Or la documentation DANS le module n'en listait que DEUX à trois endroits : le diagramme de chaîne de valeur du docstring (l.19), le résumé sous-commande du docstring (l.31) et la help= de l'argparse build (l.236). Le run_sheet.md a été ajouté au commit aebe9b7 (« prompteur Markdown ») qui a MIS À JOUR le README (qui, lui, documente correctement les 3 sorties, l.27-29/76-78) mais a laissé le docstring/help du .py en arrière. Dérive doc-vs-CODE réelle, non gatée (check_docs/check_readme_claims ne couvrent pas la prose intra-module), même classe que le fix publiciste parser de la session 210609.

Correction. Les 3 emplacements alignés sur les 3 sorties réelles : diagramme → out/run_sheet.json + out/run_sheet.md + out/MANIFEST.json ; résumé sous-commande + help argparse → out/run_sheet.{json,md} + out/MANIFEST.json. Docstring/help seuls — zéro changement de logique, de signature ou de test.

Vérifications. ./run_ci.sh30 PASS · 0 FAIL · 0 SKIP (inchangé). Build réel re-exécuté en dossier temp → émet bien run_sheet.json, run_sheet.md, MANIFEST.json (3 fichiers = la doc corrigée). Zéro nouveau module · zéro gate ajouté (#5) · un seul fichier de production touché (docstring + help). Aucune commande VPS (#8).


Session 20260802_220611 — Rattrapage canal 2 + sweep doc-vs-CODE sorties générateurs

Contexte. Reprise du buffer Sprint 8 en veille saturée. CI vert d'entrée (./run_ci.sh30 PASS · 0 FAIL · 0 SKIP). Roadmap 8-semaines intégralement livrée ; toutes les files de travail buffer (fiches 13/13 closes, 7 gates statiques prouvés mordants, audit doc-vs-réalité) à jour aux sessions précédentes.

Analyse — recherche d'une dérive doc-vs-CODE réelle (classe RÉCURRENTE, non close). Sweep outil-assisté sur les 22 générateurs (*_gen.py + lib/generator.py + seolib/builder.py) : extraction des fichiers réellement écrits (os.path.join(out, "…")) comparés aux sorties commitées dans chaque out/, croisée avec les déclarations de docstring/help. Résultat : concordance totale. Les deux modules corrigés plus tôt aujourd'hui sont propres — demo/scenarios/demo_scenario_gen.py émet bien ses 3 sorties (run_sheet.json + run_sheet.md + MANIFEST.json), toutes commitées et désormais fidèlement documentées ; publiciste/lib/generator.py sans « fallback prix » fantôme résiduel. Aucune nouvelle dérive de la classe déclaration-de-sortie. Les faux-positifs de mon regex (fichiers _spec.json/.schema.json en « written ») sont des entrées lues, pas des sorties — vérifiés manuellement, non-dérives.

Réalisé — reporting canal 2 (tâche de veille #1). L'instantané stakeholder daily_reports/2026-08-02.md avait lapsé de 4 sessions depuis sa dernière mise à jour (200607 / commit 0e76411). Ajout d'un 6ᵉ addendum ADDITIF (corps + 5 addenda intacts) consolidant les commits 0e76411 · b3f131e · a2c2e0b · 12c784c · eae3787, dont les deux corrections d'honnêteté doc-vs-CODE réelles de la journée (docstring publiciste « fallback prix §5.3 » fantôme ; docstring/help demo_scenario_gen omettant run_sheet.md). Chaque agrégat re-sourcé par recompute python3 indépendant contre l'artefact commité (anti-invention #6) : 22/22 qualité (scores distincts = {100}) · 564/564 régression (totals 22 suites · 564 ran · 0/0/0) · 15/15 recette (in_repo, verdict True) · 13 fiches · gate.needs = 30 (7 gates + 23 suites).

Vérifications. ./run_ci.sh30 PASS · 0 FAIL · 0 SKIP (inchangé après édition, seuls des fichiers de doc/log touchés). Zéro nouveau module · zéro gate ajouté (#5) · zéro fichier de production modifié (reporting + vérification pure). Aucune commande touchant au VPS (#8) · aucun git clean (#interdits).


Session 20260802_223614 — Dérive doc-vs-CODE réelle : docstring q4lib/builder.py « 17 modules » périmé (17 → 22)

Contexte. Reprise buffer Sprint 8, veille saturée. CI vert d'entrée (./run_ci.sh30 PASS · 0 FAIL · 0 SKIP). Roadmap intégralement livrée.

Analyse — sweep docstring-vs-CODE des lib/ submodules (classe RÉCURRENTE, non close). Les sessions précédentes avaient balayé les déclarations de sortie des 22 générateurs de tête. Nouveau périmètre ce jour : les docstrings de module des sous-paquets lib/ (là où la vraie dérive du parser publiciste vivait). Extraction AST de tous les docstrings mentionnant des I/O (Sources lues / Entrées / Sortie / fallback / §), confrontés au code. Résultat : concordance quasi totale — parser publiciste propre (6 sources lues = 6 lues dans parse_projet/_sources_presentes), tous les builders/deps/frappe fidèles.

UNE dérive réelle trouvée. Docstring qa/audit_4big/q4lib/builder.py l.4 : « (b) les 17 modules atteignent ≥ 95/100 ». Or l'artefact commité out/quality_report.json porte totals.modules = **22** · coverage.ci_modules_count = 22 · pass = 22. Preuve d'antériorité de la dérive : le gate ci/check_readme_claims.sh l.414-417 la documente noir sur blanc — « 17/17 modules SAISI À LA MAIN … PÉRIMÉ en silence quand des modules ont été ajoutés (17 → 22) ». Le gate recalcule le compte du README de l'auditeur (désormais « 22/22 » l.68) — mais le docstring du module est resté hors couverture (les docstrings ne sont ni link- ni count-checkés · cf. mémoire docstring-vs-code-drift). Le même « 17 » gaté dans le README avait survécu, non gaté, dans le docstring de production.

Correction — honnête ET anti-re-dérive (pas de substitution d'un compte magique). Le verdict réel du builder est PASS ssi coverage.ok AND not below — le nombre de modules est DÉRIVÉ (len(modules)), jamais figé. Remplacer « 17 » par « 22 » ne ferait que re-dériver au module #23. Reformulé en énonçant la RÈGLE : « TOUS les modules audités atteignent ≥ 95/100 (aucun sous le seuil) », + note explicite que le compte est dérivé (len(modules) == totals.modules, 17 → 22 à ce jour) et ne se retouche pas ici. Aligné sur l'éthos fix-vs-gate (ne pas empiler un gate N+1 ; décrire depuis l'autorité = le code).

Vérifications. ./run_ci.sh30 PASS · 0 FAIL · 0 SKIP (inchangé — docstring seul, zéro changement de logique). README module gaté déjà à 22/22 (concordant). Zéro nouveau module · zéro gate ajouté (#5) · un seul fichier de production touché (docstring). Aucune commande VPS (#8) · aucun git clean .


Session 230619 · Régression constraints-guard RED réparée (log-prose auto-tripping)

Contexte. ./run_ci.sh au démarrage : 29 PASS · 1 FAIL — job constraints-guard RED. Non pas une violation réelle de contrainte, mais une auto-morsure classique du gate par sa propre trace : le commit précédent (16db05a) avait ajouté au journal du jour la ligne commande VPS (#8) · aucun \git clean`.— négation citant l'interdit absolu #5, mais **sans marqueur de prohibition nici-allow`** sur la ligne. Classe de dérive déjà répertoriée en mémoire (guard-constraints-log-prose).

Diagnostic. La ligne voisine 2080 (… aucun \git clean` (#interdits).) ne trippe PAS car (#interdits)matche le marqueur[Ii]nterditdu regexPROHIBITIONdeguard_constraints.sh:25. La ligne 2119 se terminait par « aucun \git clean`. » — aucun des marqueurs (JAMAIS//pas de/interdit/ci-allow…) présent → flaggée.

Correctif. Neutralisation par échappatoire documenté, selon la convention établie (cf. 2026-08-01.md:1689) : ajout de <!-- ci-allow : mention de l'interdit absolu, jamais exécuté --> sur la ligne. Un seul fichier touché (journal), une seule ligne. Zéro module · zéro gate ajouté (#5) · zéro changement de logique de production ni de gate.

Vérifications. bash ci/guard_constraints.shVERT (✅ Contraintes NON-NÉGOCIABLES respectées). ./run_ci.sh30 PASS · 0 FAIL · 0 SKIP (gate complet re-vert). Aucune commande touchant au VPS (#8). Aucun git clean .


Session 233620 · Sweep docstring-vs-CODE élargi (62 fichiers) + reporting canal 2 (7ᵉ addendum)

Contexte. ./run_ci.sh au démarrage : 30 PASS · 0 FAIL · 0 SKIP — gate vert. Sprint 8 buffer en veille saturée (fiches 13/13 closes, gates statiques 7/7 mordants). Tâche de veille #1 des addenda précédents : tenir le canal 2 (daily_reports/) à jour à chaque session + poursuivre l'audit doc-vs-CODE de la classe docstring-drift (RÉCURRENTE, non gatée, docstring-vs-code-drift).

Audit exhaustif à couverture élargie. Les sessions précédentes n'auditaient la classe docstring-vs-CODE que sur les entrypoints (*_gen.py). Extension cette session aux modules de bibliothèque — surface où la dernière dérive réelle du jour était apparue (q4lib/builder.py « 17 modules », commit 16db05a). Confrontation docstring (module + fonction) vs code réel (sorties écrites, entrées lues, fallbacks, règles, compteurs) sur :

  • 22 générateurs (entrypoints) — sorties déclarées vs open/write/dump réels.
  • 40 modules *lib/ (builders, scorers, parsers, finance, criteria, controls, runner, ncf, aggregator, renderer, model…) — inputs/fallbacks/règles/compteurs déclarés vs code.

Résultat : 62/62 fichiers de production CONCORDANTS · aucune dérive résiduelle. Y compris les deux modules corrigés ce jour (demo_scenario_gen 3 sorties, q4lib/builder.py compte dérivé). La surface docstring-drift est saturée à ce jour — signal fort de couverture, à re-scanner après toute évolution de code (n'est PAS une classe close par nature). Audit pur — zéro fichier de production modifié cette session.

Reporting canal 2 (7ᵉ addendum). Le stakeholder snapshot daily_reports/2026-08-02.md s'arrêtait au 6ᵉ addendum (220611, 22:06). Rattrapage additif des 2 sessions intercalées (223614/16db05a docstring 17→22 · 230619/56a8802 gate constraints-guard RED réparé), chaque agrégat de l'état MVP re-vérifié par recompute python3 indépendant contre l'artefact commité (anti-invention #6) : 22/22 qualité (scores = {100}) · 564/564 régression (22 suites, 0/0/0) · 15/15 recette (in_repo, verdict True) · gate.needs = 30 (7 gates + 23 suites). Corps et six addenda antérieurs intacts (convention additive).

Vérifications. ./run_ci.sh30 PASS · 0 FAIL · 0 SKIP (miroir complet du gate, inchangé — reporting seul, zéro logique). Recompute indépendant concordant sur les 4 dimensions. Arbre git status propre avant édition. Zéro nouveau module · zéro gate ajouté (#5) · aucune logique de prod ni de gate touchée. Aucune commande VPS (#8) · aucun git clean .