Files
oto-enterprise-os-dtp/05_activity_log/2026-07-31.md
T
Claude Code DTP Worker 7ee6882eb8 [DTP-Worker] Sprint 8 · buffer · Domaine Portails (2e surface) : le CATALOGUE des DocTypes custom à CRÉER avant import (« CONFOTUR Application, Faisabilité, Publiciste Log ») était énuméré À LA MAIN dans le §« Hand-off → agent ERPNext Backend » du README frontend/portails sans AUCUN gate d'IDENTITÉ. Le bloc portails existant ne gate que les COMPTES par workspace (cartes/liens/rôles, ×2 docs) — jamais ce catalogue de hand-off. Ce catalogue est DATA-DERIVED : doctypes_custom_a_creer de frontend/portails/out/MANIFEST.json (byte-gaté par check_artifacts), lui-même l'UNION des doctypes_custom par workspace (flag custom du contrat rbac_50_roles.json, pour les seuls DocTypes CÂBLÉS dans un portail). C'est un ensemble PROPRE au module (sous-ensemble de celui de rbac/fixtures_gen : API Access, custom mais non câblé, en est ABSENT). PIÈGE : check_artifacts ne prouve QUE MANIFEST==build (byte-for-byte) et le build RECOMPUTE l'union — donc AJOUTER une carte visant un nouveau DocType custom au contrat (le MANIFEST rebâtit le catalogue) · en RENOMMER un · en RETIRER un du câblage laisse la consigne de hand-off périmée pendant que l'artefact dit autre chose : l'agent Backend créerait le mauvais jeu de DocTypes (liens pointant dans le vide — le risque même que la consigne veut prévenir) — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la prose de hand-off) n'attrape → nouveau bloc recomputant le catalogue depuis MANIFEST (zéro duplication du contrat du générateur #6 · même patron que le catalogue custom de rbac/fixtures_gen) et exigeant que la prose l'énumère EXACTEMENT (absent ET en trop via set-diff · accents/casse normalisés via unicodedata). Cohérences croisées en bonus (mordent un MANIFEST INTERNEMENT incohérent) : catalogue NON VIDE, sans doublon et TRIÉ (byte-déterminisme) · égal à l'UNION des doctypes_custom par workspace (pas de DocType custom câblé mais oublié du hand-off, ni l'inverse). Un claim absent échoue AUSSI. 5 morsures vérifiées : prose renomme Faisabilité→Faisabilite2 (absents=[faisabilite] en trop=[faisabilite2]) · prose retire Publiciste Log (sous-ensemble) · énumération supprimée (INTROUVABLE) · MANIFEST retire du catalogue un DocType encore câblé (incohérence interne : câblés hors catalogue=[Publiciste Log] + prose en trop) · catalogue MANIFEST non trié (byte-déterminisme) ; restauré = green : [CONFOTUR Application,Faisabilité,Publiciste Log] == MANIFEST == union des workspaces · exit 0. État courant : aucun catalogue périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail « 2e surface Portails ») mis à jour · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 19:06:35 +00:00

142 KiB
Raw Blame History

Activity Log · 2026-07-31 · Claude Code DTP

Session 20260731_183124 · Buffer S8 · Domaine CRM/dossier_vente (2ᵉ surface) : le catalogue de projets du DocType porteur (« Projet (Select P01..P09 ancré sur CLAUDE.md) ») était ancré à CLAUDE.md EN PROSE sans AUCUN gate d'identité — le bloc CRM pipeline existant ne recompute que le COMPTE d'états (« les 9 états du pipeline ») du même README.

Tâche : Sprint 8 · buffer L60 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Cette session cible une 2ᵉ surface data-derived du README crm/dossier_vente — le champ Select projet du DocType porteur, dont les 9 options (P01 StructureP09 1069 Crisfer) sont une transcription de la section « ## Projets » de CLAUDE.md, jamais ANCRÉE par aucun gate.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/crm/dossier_vente/README.md:46 : « Projet (Select P01..P09 ancré sur CLAUDE.md) » ; doctype_spec.json _comment : « les Select projet/devise sont ancrés sur CLAUDE.md … pas inventés ».
  • Source faisant autorité : la section « ## Projets » de CLAUDE.md (contraintes #6/#7 · liste canonique des projets). Le DocType (out/doctype_oto_dossier_vente.json, byte-gaté par check_artifacts) porte l'ensemble ORDONNÉ des 9 options.
  • check_artifacts ne prouve QUE DocType == spec (byte-for-byte) ; la spec recopie elle-même le catalogue → toute la chaîne (spec → DocType) peut DÉRIVER de CLAUDE.md en restant byte-verte : RENOMMER un projet (P03 NakuaP03 Nakua Bay), en AJOUTER (P10) ou en RETIRER un laisse le Select périmé, contredisant silencieusement CLAUDE.md pendant que la prose « ancré sur CLAUDE.md » devient FAUSSE — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de builder, pas l'ancrage à CLAUDE.md) n'attrape.
  • État courant : aucun projet périmé — les 9 options recoupent CLAUDE.md exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute le catalogue depuis « ## Projets » de CLAUDE.md (zéro duplication · source unique · même esprit que le roadmap-anchor INV11) et exige que les options du Select projet l'égalent exactement (set-diff absent ET en trop, puis ordre · NFC), plus la plage P01..P<dernier> + le mot d'ancrage dans le README. Cohérences croisées en bonus (mordent un catalogue CLAUDE.md internement incohérent) : catalogue non vide, sans doublon et contigu depuis P01. Un claim absent échoue AUSSI.

Preuve (6 morsures vérifiées + restauration verte)

Injection Résultat
CLAUDE.md P03 Nakua → P03 Nakua Bay (DocType stale) ✗ Select DÉRIVE : absents=[P03 Nakua Bay] en trop=[P03 Nakua] · exit 1
CLAUDE.md +P10 (catalogue grandit) ✗ Select absents=[P10 …] + README dit P01..P09 MAIS catalogue P01..P10 · exit 1
DocType Select réordonné (bon ensemble, ordre ≠) ✗ bon ENSEMBLE mais ORDRE ≠ CLAUDE.md · exit 1
DocType renomme 1 libellé (P07 Aqua Terra Las Terrenas → P07 Aqua Terra) ✗ Select DÉRIVE : absents/en trop · exit 1
README plage P01..P09 → P01..P08 ✗ README dit P01..P08 MAIS catalogue P01..P09 · exit 1
README ancrage supprimé (reformulé) ✗ claim « Projet (Select P01..P09 ancré sur CLAUDE.md) » INTROUVABLE · exit 1
restauré ✓ Select projet == CLAUDE.md (9 projets, ordre inclus) · plage P01..P09 + ancrage · catalogue contigu · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (« catalogue de projets ancré sur CLAUDE.md »).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; édition hors 05_deliverables_mvp/*/out ⇒ 0 dérive d'artefact).

Auto-score 4Big : 96/100.


Session 20260731_180119 · Buffer S8 · Domaine SEO (3e surface) : la carte hreflang (« carte alternate FR/EN/ES + x-default par page ») était décrite EN PROSE dans le README du module seo sans AUCUN gate d'IDENTITÉ — les deux blocs SEO existants gatent le COMPTE de mots-clés (artefact seo_keywords) et les @type du graphe schema.org (artefact seo_schema_org), jamais le jeu de langues hreflang qui vit dans un troisième artefact.

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Cette session cible la 3ᵉ surface data-derived du même README SEO — la carte hreflang, dont le jeu de langues cité dans la table « Ce que ça fait » pointe l'artefact généré sans être recomputé par aucun gate.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/seo/README.md:17 : « | hreflang | … carte alternate FR/EN/ES + x-default par page | » — l'ENSEMBLE des langues de contenu + le x-default.
  • Source faisant autorité (byte-gatée par check_artifacts) : seo/out/seo_hreflang.json — chaque page porte un ensemble d'alternate (une langue de contenu par entrée) + un x-default ; langues de contenu = {a.hreflang} {x-default} = {en, es, fr} · x_default_lang = fr.
  • Les deux blocs SEO existants (mots-clés · schema.org) sont aveugles à ces codes : AJOUTER une langue au bundle (pt → 4 langues), RETIRER/ÉCHANGER es ferait dériver la prose en silence pendant que la carte byte-gatée dit autre chose — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de génération, pas la prose) n'attrape.
  • État courant : aucune langue périméeFR/EN/ES + x-default recoupe l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute l'ENSEMBLE des langues de contenu depuis seo_hreflang.json (zéro duplication de la carte du générateur · #6) et exige que la cellule les nomme exactement (absent ET en trop via set-diff · casse normalisée) + mentionne x-default. Cohérences croisées en bonus (mordent un artefact internement incohérent) : carte uniforme (toutes les pages portent le même jeu d'alternate) · x-default présent sur chaque page · langue par défaut (x_default_lang) ∈ langues de contenu (ni vacante ni étrangère) · pour chaque page x-default.href == canonical == href de la langue par défaut (l'invariant README:61). Un claim absent échoue AUSSI.

Preuve (7 morsures vérifiées + restauration verte)

Injection Résultat
README échange langue (ES → PT) ✗ absents=[es] · en trop=[pt] · exit 1
README retrait ES (sous-ensemble) ✗ absents=[es] · exit 1
README x-default supprimé x-default non mentionné · exit 1
README claim reformulé ✗ carte « alternate … par page » INTROUVABLE · exit 1
artefact +langue pt sur 1 page (non uniforme) ✗ carte NON uniforme (2 jeux) + prose absents=[pt] · exit 1
artefact x_default_lang → de (défaut étranger) de ∉ langues de contenu + href incohérent · exit 1
artefact x-default.href ≠ canonical (1 page) ✗ href incohérent sur [home] · exit 1
restauré [en,es,fr] + x-default == seo_hreflang · uniforme · défaut fr · href cohérent (10 pages) · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (« 3ᵉ surface du même README SEO »).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; édition hors 05_deliverables_mvp/*/out ⇒ 0 dérive d'artefact).

Auto-score 4Big : 96/100.


Session 20260731_173114 · Buffer S8 · Domaine SEO (2e surface) : la composition du graphe schema.org (« graphe JSON-LD (Organization + une Residence par projet) ») était décrite EN PROSE dans le README du module seo sans AUCUN gate d'IDENTITÉ — le bloc SEO existant ne gate que le COMPTE de mots-clés (total + fr/en/es).

  • Constat : les @type du @graph de seo/out/seo_schema_org.json (byte-gaté) sont l'ensemble faisant autorité : un nœud racine Organization (@id …#organization)
    • une Residence par projet. Le README:16 les transcrit à la main. Renommer le @type des listings (ResidenceApartment), ajouter un 3ᵉ type (Offer/Product) ou casser la bijection ferait dériver la prose en silence pendant que l'artefact dit autre chose — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de génération, pas la prose) n'attrape.
  • Gate ajouté (ci/check_readme_claims.sh) : recompute l'ENSEMBLE des @type depuis seo_schema_org.@graph (zéro duplication du contrat du générateur #6) et exige que la parenthèse README les nomme EXACTEMENT (set-diff absent ET en trop ; plus le type « par projet » nommé == le @type des listings). Cohérences croisées en bonus : racine Organization SINGLETON · listings UNIFORMES (un seul @type) et en BIJECTION avec les projets (listings == projects == schema_org_nodes1 du MANIFEST) · aucune offers (statuts sans prix · USD #10).
  • 6 morsures vérifiées : prose listing ResidenceApartment captée (absents=[Residence] en trop=[Apartment] + « par projet » désaligné) · prose racine OrganizationBusiness captée · artefact +nœud Offer (prose absente + listings non uniformes) capté · artefact 1 listing ResidenceApartment (non uniforme) capté · MANIFEST listings 9→8 (bijection cassée) capté · composition prose supprimée (INTROUVABLE) captée ; restauré = green (Organization racine singleton · 9 × Residence · total 10 nœuds · aucune offers).
  • État courant : aucun type périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail « 2ᵉ surface SEO ») mis à jour · 7 gates re-verts.

Session 20260731_170112 · Buffer S8 · Domaine RBAC/fixtures_gen : le catalogue des DocTypes custom à créer sur le VPS (« Faisabilité, Publiciste Log, CONFOTUR Application, API Access ») ET la séparation des pouvoirsset_user_permissions n'est émis que pour le rôle RBAC Admin ») étaient transcrits À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de l'apply_plan).

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets : roleprofile/apply_plan/userperm), SEO, CRM (pipeline vente + séparation des pouvoirs + commissions), mobile (quadruplet

  • onglets), Legal/CONFOTUR, DevOps, QA (audit 5D ×2 + 4Big ×2), Démo, Frontend/Chat OTOIA, recette et Fiscal/e-CF. Cette session cible le module rbac/fixtures_gen — jamais gaté hors son compte agrégé — dont deux faits data-derived du README pointent les artefacts générés sans être recomputés par aucun gate.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/rbac/fixtures_gen/README.md:56 : « listés dans MANIFEST.custom_doctypes_a_confirmer (Faisabilité, Publiciste Log, CONFOTUR Application, API Access) » — l'ENSEMBLE des DocTypes custom: true à créer.
  • …/README.md:48 : « le flag set_user_permissions n'est émis que pour le rôle RBAC Admin » — le SINGLETON {role | set_user_permissions == 1}, cœur sécurité du module (« séparation des pouvoirs · défense en profondeur »).
  • Sources faisant autorité (byte-gatées par check_artifacts) : rbac/fixtures_gen/out/MANIFEST.json.custom_doctypes_a_confirmer (les DocTypes custom du contrat rbac_50_roles.json) et out/custom_docperm.json (le seul rôle porteur du flag = OTO Plateforme RBAC Admin).
  • Le bloc racine ne gate QUE le compte (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de l'apply_plan). Aucun gate ne comparait ces ENSEMBLES aux artefacts : AJOUTER un DocType custom au contrat (le MANIFEST rebâtit 5 entrées), RENOMMER/ÉCHANGER l'un des 4, ou PROMOUVOIR un 2ᵉ rôle porteur de set_user_permissions (élévation de privilège) ferait dériver la prose en silence — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la prose) n'attrape.
  • État courant : aucun ensemble périmé — le catalogue (4) et le singleton recoupent les artefacts exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute le catalogue depuis MANIFEST.custom_doctypes_a_confirmer et le singleton depuis custom_docperm.json (zéro duplication du contrat du générateur · #6) et exige que la prose l'énumère/le nomme exactement — contrôle par ENSEMBLE (absent ET en trop mordus via set-diff · accents/casse normalisés via unicodedata). Cohérences croisées en bonus : le catalogue est non vide, sans doublon et trié (byte-déterminisme du générateur) ; la séparation des pouvoirs est un singleton (ni vide — garde vacante — ni multiple — élévation de privilège). Un claim absent échoue AUSSI.

Preuve (6 morsures vérifiées + restauration verte)

Injection Résultat
README échange nom DocType (Faisabilité → Faisabilite2) ✗ absents=[faisabilite] · en trop=[faisabilite2] · exit 1
README retrait API Access (sous-ensemble) ✗ absents=[api access] · exit 1
README rôle nommé faux (RBAC Admin → Ventes Conseiller) ✗ nomme « Ventes Conseiller » MAIS flag porté par OTO Plateforme RBAC Admin · exit 1
README énumération du catalogue supprimée ✗ énumération du catalogue custom INTROUVABLE · exit 1
artefact 2ᵉ rôle promu au flag (custom_docperm) ✗ set_user_permissions porté par […RBAC Admin, …Ventes Conseiller] (attendu 1) · séparation cassée · exit 1
artefact catalogue MANIFEST non trié ✗ non trié/dédupliqué · exit 1
restauré ✓ catalogue [API Access, CONFOTUR Application, Faisabilité, Publiciste Log] == MANIFEST · singleton OTO Plateforme RBAC Admin == custom_docperm · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « catalogue RBAC rbac/fixtures_gen »).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; édition hors 05_deliverables_mvp/*/out ⇒ 0 dérive d'artefact).

Auto-score 4Big : 96/100.


Session 20260731_163112 · Buffer S8 · Domaine CRM/Commissions : la cross-cohérence du barème vendeurs (« cœur du livrable ») — les états brouillon exclus (lead/visite/devis/abandonné), les champs de base (montant_reservation, montant_contrat) et l'invariant 3 « on ne commissionne que des états soumis » — était énumérée À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ ; le bloc racine ne gate que le COMPTE de tests (« 25 », via l'agrégat « Total CRM »).

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC, SEO, CRM (pipeline vente + séparation des pouvoirs), mobile (quadruplet + onglets), Legal/CONFOTUR, DevOps, QA (audit 5D + 4Big), Démo, Frontend/Chat OTOIA, recette et Fiscal/e-CF. Cette session cible le module crm/commissions — jamais gaté hors son compte de tests — dont la section « Cross-cohérence » (le CŒUR du livrable) énumère trois faits data-derived pointant les artefacts générés sans être recomputés par aucun gate.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/crm/commissions/README.md:47 : « on ne commissionne pas un brouillon (lead/visite/devis/abandonné) » — l'ensemble des états brouillon.
  • …/README.md:50 : « base_field … (montant_reservation, montant_contrat) » — l'ensemble des champs de base.
  • …/README.md:46 : « correspondre à un état soumis (doc_status = 1) » — INVARIANT 3 du module.
  • Sources faisant autorité (byte-gatées par check_artifacts) : crm/workflow_vente/out/workflow.json (brouillons = {update_value | doc_status==0} = {lead, visite, devis, abandonne} · soumis = {… | doc_status==1}) et crm/commissions/out/commission_plan.json (champs de base = {base_field} des évènements · états payés = {update_value}).
  • Le bloc racine ne gate QUE le compte de tests (« 25 »). Aucun gate ne comparait ces ENSEMBLES aux artefacts : PROMOUVOIR un brouillon en soumis (abandonne → doc_status 1) le retire de l'ensemble brouillon pendant que la prose l'énumère encore ; AJOUTER/RENOMMER un champ de base ou ÉCHANGER un état passe aussi — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS finance/résolution, pas la prose) n'attrape.
  • État courant : aucun ensemble périmé — les 2 énumérations recoupent les artefacts exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute chaque ensemble depuis SON artefact byte-gaté (zéro duplication du contrat du générateur · #6) et exige que la prose l'énumère exactement — contrôle par ENSEMBLE (absent ET en trop mordus via set-diff · accents/casse normalisés via unicodedata). Cohérence croisée en bonus (mord un plan internement incohérent avec le workflow) : tout update_value commissionné ∈ états soumis (doc_status=1) ET ∉ brouillons — l'invariant 3, non vide. Un claim absent échoue AUSSI.

Preuve (5 morsures vérifiées + restauration verte)

Injection Résultat
README brouillon abandonné → perdu (échange) ✗ absents=[abandonne] · en trop=[perdu] · exit 1
README base_field retire montant_contrat (sous-ensemble) ✗ absents=[montant_contrat] · exit 1
README énumération des brouillons supprimée ✗ énumération des brouillons INTROUVABLE · exit 1
artefact plan reservation → lead (commissionne un brouillon) ✗ incohérence plan↔workflow : [lead] PAS soumis · invariant 3 cassé · exit 1
artefact workflow abandonne doc_status 0→1 (draft set change) ✗ prose en trop=[abandonne] vs brouillons workflow · exit 1
restauré ✓ brouillons [abandonne,devis,lead,visite] == workflow (doc_status=0) · champs de base == plan · invariant 3 vert · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « cross-cohérence du barème de commissions »).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; édition hors 05_deliverables_mvp/*/out ⇒ 0 dérive d'artefact).

Auto-score 4Big : 96/100.


Session 20260731_160111 · Buffer S8 · Domaine Fiscal/e-CF : le contrat de format de l'e-NCFE + tipoeCF(2) + secuencia(10) » = 13) était retranscrit À LA MAIN dans QUATRE endroits de prose (README ×3 + fiche agent erpnext_backend) sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE de tests e-CF (« 39 »).

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC, SEO, CRM (pipeline + séparation des pouvoirs), mobile (quadruplet + énumération onglets), Legal/CONFOTUR, DevOps, QA (audit 5D + 4Big), Démo, Frontend/Chat OTOIA et recette. Cette session cible le domaine Fiscal/e-CF DGII (fiscal/ecf_dgii) — le contrat de format de l'e-NCF, cœur du module (le « composeur traçable »), dont les longueurs de segments citées dans quatre proses pointaient l'artefact généré sans être recomputées par aucun gate.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/fiscal/ecf_dgii/README.md × 3 : résumé l.10 (`E + tipoeCF(2) + secuencia(10)`), invariant 7 l.75 (E+tipo(2)+seq(10)=13, seul à porter la longueur totale), composition l.84 (e_ncf = "E" + tipo(2) + secuencia(10)).
  • 03_agents/erpnext_backend/AGENT.md:33 : `E + tipoeCF(2) + secuencia(10)`.
  • Source faisant autorité : le bloc e_ncf de fiscal/ecf_dgii/out/ecf_plan.json (byte-gaté par check_artifacts) — prefix (« E »), longueur (13), regex (^E[0-9]{12}$), structure (« E + tipoeCF (2 chiffres) + secuencia (10 chiffres) »). Format e-NCF standard DGII (Ley 32-23) porté avec source — pas un chiffre inventé (#6).
  • Le bloc racine ne gate QUE le compte de tests e-CF (« 39 »). Aucun gate ne comparait la LONGUEUR des segments : allonger la secuencia (rango DGII à 12 chiffres → longueur 15, regex 14 digits) OU changer le préfixe ferait dériver les QUATRE proses en silence pendant que l'ecf_plan byte-gaté dit autre chose — « vert trompeur » qu'aucune suite tests/ (qui teste les FONCTIONS compose_encf/ parse_encf, pas la prose) n'attrape.
  • État courant : aucun segment périmé — les 4 proses recoupent l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute (prefix, tipo_len, seq_len, total) depuis le SEUL bloc e_ncf (zéro duplication du format du générateur · #6) et exige que chaque prose l'encode exactement (4 sous-checks numérotés, patron du bloc DevOps). Cohérences croisées en bonus (mordent un artefact internement incohérent) : 1 + tipo_len + seq_len == longueur (1+2+10==13) ET le nombre de chiffres du regex == tipo_len + seq_len (12 == 2+10). Un claim absent échoue AUSSI (traçabilité).

Preuve (5 morsures vérifiées + morsure d'incohérence + restauration verte)

Injection Résultat
artefact secuencia 10→12 (longueur 15 · regex 14 · les 4 proses restent 10/13) les 4 proses mordues : dit E/2/10 MAIS e_ncf tipo=2 seq=12 total=15 · exit 1
README résumé tipo 2→3 ✗ dit E/3/10 MAIS e_ncf tipo=2 · exit 1
README invariant total 13→14 (aveugle au segment) ✗ dit E/2/10/14 MAIS e_ncf total=13 · exit 1
fiche erpnext_backend secuencia 10→9 ✗ dit E/2/9 MAIS e_ncf seq=10 · exit 1
README composition supprimée (reformulée) ✗ format e-NCF INTROUVABLE · exit 1
artefact regex désaligné ({11} vs tipo+seq=12) ✗ incohérence artefact : regex 11 chiffres MAIS tipo+seq=12 · exit 1
restauré E + tipo(2) + secuencia(10) = 13 == e_ncf (4 proses + 2 cohérences croisées) · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « contrat de format de l'e-NCF fiscal »).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; édition hors 05_deliverables_mvp/*/out ⇒ 0 dérive d'artefact).

Auto-score 4Big : 96/100.


Session 20260731_150110 · Buffer S8 · Domaine Mobile (2e surface) : la ligne récap du README app Expo énumérait À LA MAIN ses 5 onglets ((Ventes/Construction/Achat/Compta/Direction)) sans gate d'IDENTITÉ — le bloc mobile existant ne recompute que le COMPTE (« 5 onglets ») et son patron matche la parenthèse par \([^)]*\) en ignorant son contenu, aveugle à un échange / retrait / réordre de nom.

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Le bloc mobile gaté précédemment recompute le quadruplet de la ligne récap (onglets · rôles couverts · langues · identifiants a_confirmer) depuis MANIFEST.counts. Restait, dans la même ligne du même README, une surface data-derived distincte : la parenthèse qui énumère les 5 onglets — matchée par le patron du compte mais dont le contenu est ignoré.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/mobile/app_config/README.md:66 — « 5 onglets (Ventes/Construction/Achat/Compta/Direction) · … ». La parenthèse est une liste ordonnée data-derived ; le patron du compte (\*\*(\d+) onglets\*\* \([^)]*\)) la matche mais IGNORE son contenu.
  • Source faisant autorité : mobile/app_config/out/role_navigation.json (byte-gaté par check_artifacts) — un onglet par portail métier du contrat RBAC (bijection 1 onglet ⇔ 1 portail · plateforme exclu), chaque entrée portant portail (id)
    • label « OTO ». Nom court énuméré = le label sans le préfixe « OTO ».
  • Le bloc mobile ne gate QUE le compte (« 5 onglets »). Un échange (Ventes → Marketing), un retrait (Compta absent — l'énumération tombe à 4 tandis que « 5 onglets » reste juste, aveugle au compte) ou un réordre laisse le compte à 5 pendant que l'artefact dit autre chose. « Vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS, pas la prose) n'attrape.
  • État courant : aucun onglet périmé — la liste des 5 recoupe l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute la liste ordonnée des noms courts depuis role_navigation.json (zéro duplication de la navigation du générateur · #6) et exige que la parenthèse l'énumère exactement : contrôle par ENSEMBLE (absent ET en trop mordus via set-diff), puis ordre. Même patron que l'énumération des confirmations DevOps, la carte de renormalisation par archétype 4Big ou la table « Verdict courant » de l'audit 5D. Cohérence croisée en bonus : len(role_navigation) == MANIFEST.counts.onglets == MANIFEST.counts.portails (la bijection du générateur) — mord un compteur du manifeste désynchronisé de sa navigation.

Preuve (4 morsures vérifiées + restauration verte)

Injection Résultat
échange Ventes → Marketing (compte reste 5 · gate-compte aveugle) ✗ énumère [Marketing,…] MAIS role_navigation [Ventes,…] · absents=[Ventes] · en trop=[Marketing] · exit 1
retrait Compta (énumération=4 · « 5 onglets » reste juste) ✗ énumère 4 MAIS role_navigation 5 · absents=[Compta] · exit 1
réordre Construction/Ventes/… (même ensemble) ✗ même ensemble, ordre ≠ · exit 1
énumération supprimée (claim disparu) ✗ énumération des onglets INTROUVABLE · exit 1
restauré Ventes/Construction/Achat/Compta/Direction == role_navigation · bijection 5==onglets==portails · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (2ᵉ surface du même README mobile).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact).

Auto-score 4Big : 96/100.


Session 20260731_143104 · Buffer S8 · Domaine QA/Audit 5D (2e surface) : la table « Verdict courant » de l'audit de conformité énumérait À LA MAIN ses 4 open items (control → dimension → propriétaire) sans gate d'IDENTITÉ — le bloc existant ne recompute que le COMPTE (« 4 à confirmer »), aveugle à un échange d'open item.

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Le bloc audit_5d gaté précédemment couvre « 17 contrôles / 5 dimensions » (×2 docs) et la ventilation du verdict « 13 PASS · 0 FAIL · 4 à confirmer » (recomputée d'audit_report.totals). Restait, dans le même README, une surface data-derived distincte : la table « Verdict courant » qui énumère les 4 contrôles ouverts avec leur dimension et leur propriétaire.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/qa/audit_5d/README.md:51-56 énumère 4 lignes | D1.1 · … | D1 | Direction | … — chacune porte trois faits data-derived : l'id du contrôle, sa dimension, son propriétaire.
  • Source faisant autorité : qa/audit_5d/out/audit_report.json.open_items[] (byte-gaté par check_artifacts, chaque item = {control, dimension, owner, detail} recalculé en rejouant les contrôles sur les hand-off amont).
  • Le bloc de ventilation ne gate QUE le compte (« 4 à confirmer »). Un échange d'open item (ex. D2.3 → D3.1) laisse le compte à 4 — le compteur reste AVEUGLE — pendant que l'artefact dit autre chose ; idem une dimension mal étiquetée (D1.3 rangé sous D2) ou un propriétaire réattribué. « Vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS, pas la prose) n'attrape.
  • État courant : aucun open item périmé — les 4 lignes recoupent l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute l'ensemble {control:(dimension, propriétaire)} depuis audit_report.open_items (zéro duplication du modèle de l'auditeur · #6) et exige que la table README l'énumère exactement : contrôle par ENSEMBLE (absent ET en trop mordus, pas seulement présence), puis (dimension, propriétaire) par LIGNE. Même patron que l'énumération des confirmations DevOps ou la carte de renormalisation par archétype 4Big. Cohérence croisée en bonus : l'ensemble des control == MANIFEST.open_items (le manifeste qui résume le rapport) — mord un manifeste désynchronisé de son rapport.

Preuve (4 morsures vérifiées + restauration verte)

Injection Résultat
échange D2.3 → D3.1 (compte reste 4 · gate-compte aveugle) ✗ énumère […,D3.1] MAIS référentiel […,D2.3] · exit 1
dimension D1.3 étiquetée D2 (compte + ids intacts) ✗ open item D1.3 dit dimension=D2 MAIS artefact D1 · exit 1
propriétaire D1.1 Direction → Compta (compte + ids + dims intacts) ✗ D1.1 dit propriétaire=Compta MAIS artefact Direction · exit 1
ligne D1.2 supprimée (sous-ensemble) ✗ énumère [D1.1,D1.3,D2.3] MAIS référentiel [D1.1,D1.2,D1.3,D2.3] · exit 1
restauré [D1.1,D1.2,D1.3,D2.3] (control→dimension→propriétaire) == audit_report.open_items · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (2e surface du même README audit 5D).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact).

Auto-score 4Big : 96/100.


Session 20260731_140101 · Buffer S8 · Domaine QA/Audit 4Big (2e surface) : la carte de renormalisation PAR ARCHÉTYPE de l'auditeur (« data_room (sans HANDOFF) · parser (sans CONTRAT … ni HANDOFF) · contract (sans CLI ni HANDOFF) ») était transcrite À LA MAIN dans le README du module — la session précédente a gaté le barème (critères + poids + generator (N critères)) mais PAS les exclusions des trois autres archétypes.

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). La session 133057 a gaté le barème de l'auditeur (nombre de critères + poids par critère + generator (5 critères)). Restait, dans le même README, une surface data-derived distincte : la carte de renormalisation par archétype, qui dit quels critères CHAQUE archétype exclut du dénominateur.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/qa/audit_4big/README.md:32-34 énumère « generator (5 critères) · data_room (sans HANDOFF) · parser (sans CONTRAT local ni HANDOFF) · contract (sans CLI ni HANDOFF) ».
  • Cette carte est DATA-DERIVED : pour un archétype, l'ensemble des critères applicables = {check.criterion} de ses modules dans out/quality_report.json (byte-gaté par check_artifacts) — les critères non-applicables sont absents des checks. Donc exclus = ordre du barème applicables.
  • Le bloc barème de la session précédente ne recompute QUE generator (« 5 critères », zéro exclusion). Les exclusions de data_room/parser/contract n'étaient recomputées par AUCUN gate : changer la règle de renormalisation d'un archétype (ex. data_room exclut aussi CLI) ferait dériver la prose en silence pendant que quality_report dit autre chose — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS, pas la prose) n'attrape.
  • État courant : aucune exclusion périmée — les 3 cartes recoupent l'artefact exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc (6) dans ci/check_readme_claims.sh (section barème qa/audit_4big) : recompute par archétype l'ensemble des critères exclus depuis les checks de quality_report.modules (zéro duplication du modèle de renormalisation du générateur · #6) et exige que la parenthèse du README l'énumère exactement (mauvais critère, sous-ensemble, sur-ensemble → tous mordus). Bonus d'uniformité : tous les modules d'un même archétype portent le même jeu applicable (« 1 archétype → 1 jeu de critères », base même de la carte — mord un référentiel où un module d'un archétype divergerait des autres). Un claim (énumération) absent échoue AUSSI (traçabilité).

Preuve (3 morsures vérifiées + restauration verte)

Injection Résultat
data_room « sans HANDOFF » → « sans CLI » (mauvais critère exclu) ✗ exclut [CLI] MAIS référentiel [HANDOFF] · exit 1
parser exclusion HANDOFF retirée (sous-ensemble) ✗ exclut [CONTRAT] MAIS référentiel [CONTRAT, HANDOFF] · exit 1
énumération contract supprimée (claim disparu) ✗ énumération contract INTROUVABLE · exit 1
restauré ✓ 4 archétypes uniformes · data_room/parser/contract == référentiel · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « carte de renormalisation par archétype 4Big »).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Session 20260731_133057 · Buffer S8 · Domaine QA/Audit 4Big : le barème de notation de l'auditeur (« 5 critères 4Big déterministes » + la table Critère | Poids + l'archétype « generator (5 critères) ») était transcrit À LA MAIN dans le README du module sans AUCUN gate — le bloc racine ne gatait que sa ligne « Résultat courant » (verdict/modules), jamais le référentiel de scoring lui-même.

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Restait une surface de « vert trompeur » non gatée dans le module qui NOTE les autres : son propre barème.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/qa/audit_4big/README.md retranscrit trois surfaces data-derived du référentiel de scoring : la prose « Note chaque module sur 5 critères 4Big déterministes », la table « Critère | Poids | Fait vérifié » (une ligne id→poids par critère : DOC 20 · CONTRAT 20 · TESTS 25 · CLI 15 · HANDOFF 20) et l'archétype « generator (5 critères) ».
  • La source faisant autorité est out/quality_report.json.criteria (byte-gaté par check_artifacts, recomputé du référentiel à chaque build).
  • Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter/retirer un critère ou changer un poids ferait dériver le README en silence — dérive qu'aucune suite tests/ (qui teste des FONCTIONS, pas la prose) n'attrape.

Nouveau bloc dans ci/check_readme_claims.sh : recompute le nombre de critères (prose + « generator »), et chaque poids depuis criteria[].weight (zéro duplication du barème du générateur · #6), exige l'égalité avec le README. Cohérences croisées : Σ poids == 100 (base de renormalisation documentée) ET tout module d'archétype generator porte EXACTEMENT len(criteria) checks (« generator = tous les critères » — 18 modules generator × 5 checks vérifiés). Une compensation de poids (+5 / 5) laisserait Σ == 100 juste et deux lignes fausses → d'où le contrôle par critère, pas seulement de la somme.

Preuve (4 morsures vérifiées + restauration verte)

Injection Résultat
README prose 5 critères → 6 (poids table intacts) ✗ dit 6 MAIS criteria en compte 5 · exit 1
README poids TESTS 25 → 30 (compte intact) ✗ dit poids 30 MAIS criteria dit 25 · exit 1
README archétype generator (5→4 critères) ✗ dit 4 MAIS generator applique les 5 · exit 1
README claim prose reformulé (chiffre disparu) ✗ « N critères 4Big » INTROUVABLE · exit 1
restauré ✓ 5 critères · poids 20/20/25/15/20 · Σ=100 · 18 gen×5 == artefact · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « barème auditeur 4Big »).

Suite CI complète re-verte (7 gates) : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Session 20260731_130055 · Buffer S8 · Domaine QA/Recette : le couple « 8 livrables de sprint · 7 métriques succès MVP » du module d'acceptation (qa/acceptance) était transcrit À LA MAIN dans DEUX docs de module (README du module + fiche agent QA) sans AUCUN gate — le bloc racine ne gate QUE le README d'entrée

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR), QA (audit 5D + audit 4big), DevOps (run-book), Démo (run-sheet) et Frontend/Chat OTOIA. Cette session cible le module de recette (qa/acceptance) — son couple data-derived « livrables de sprint / métriques succès MVP », cité dans deux docs de module que le bloc racine ne couvrait pas.

Défaut trouvé — couple ungated, transcrit dans DEUX docs de module

  • 05_deliverables_mvp/qa/acceptance/README.md:8-9 : puces « 8 livrables de sprint (L33..L76) » et « 7 métriques succès MVP (L81..L87) ».
  • 03_agents/qa/AGENT.md:27 : cellule « mappe chaque promesse roadmap (8 livrables + 7 métriques) vers sa preuve » — même couple dérivé de la même matrice (source unique · #5).
  • L'artefact source est byte-gaté par check_artifacts : qa/acceptance/out/acceptance_matrix.json porte une ligne par promesse roadmap de kind sprint_deliverable OU mvp_metric. Le bloc racine de check_readme_claims recompute déjà ce couple pour le SEUL README d'entrée (« N promesses (X sprint + Y métriques) ») — mais jamais ces deux docs de module.
  • Le roadmap-anchor (INV11) ancre les roadmap_line de l'ARTEFACT au fichier roadmap ; il ne compare pas la prose des docs. Ajouter une promesse roadmap → l'artefact rebâtit 9/7 pendant que les deux docs restent à 8/7 en silence — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS, pas la prose) n'attrape.
  • État courant : aucun nombre périmé — 8/7 recoupe la matrice exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute chaque nombre depuis son propre genre de la matrice (kind sprint_deliverable / mvp_metric, n_sprint/n_metric déjà dérivés plus haut · zéro duplication #5) et exige l'égalité avec les DEUX docs. Recomputer les DEUX (sprint ET métriques) mord une dérive de l'un là où l'autre resterait aveugle. Cohérence croisée en bonus : MANIFEST.counts.{sprint_deliverables,mvp_metrics} == le compte de la matrice qu'il résume — mord un compteur du manifeste désynchronisé de sa liste. Un claim absent échoue AUSSI (traçabilité).

Preuve (4 morsures vérifiées + restauration verte)

Injection Résultat
README sprint 8→9 (métriques reste 7) ✗ dit 9/7 MAIS matrice 8/7 · exit 1
README métriques 7→6 (sprint reste 8) ✗ dit 8/6 MAIS matrice 8/7 · exit 1
fiche QA parenthèse 8→10 (README intact) ✗ dit 10/7 MAIS matrice 8/7 · exit 1
fiche QA claim supprimé (reformulé) ✗ claim INTROUVABLE · exit 1
restauré ✓ 8/7 == matrice (2 docs) · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « module de recette qa/acceptance »).

Suite CI complète re-verte : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Session 20260731_123055 · Buffer S8 · Domaine Frontend/Chat OTOIA : le montage de l'assistant conversationnel par portail (frontend/chat_otoia) affichait « 5 Custom Block + 5 configs runtime » (fiche agent frontend_console) et « 5 portails · 5 fixtures · 5 configs runtime » (README du module) — trois nombres data-derived, TOUS = 5 (piège aveugle), transcrits À LA MAIN dans DEUX docs sans AUCUN gate

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR), QA (audit 5D), DevOps (run-book) et Démo (run-sheet). Cette session cible le domaine Frontend/Chat OTOIA — le montage de l'assistant conversationnel par portail (livrable Sprint 6 « Chat OTOIA embedded »), seul grand livrable dont les comptes data-derived cités dans deux docs pointaient l'artefact généré sans être recomputés par aucun gate.

Défaut trouvé — triplet ungated, TOUS = 5 (piège aveugle), transcrit dans DEUX docs

  • 03_agents/frontend_console/AGENT.md:29 : cellule « 5 Custom Block + 5 configs runtime » — deux nombres (custom_blocks + mounts).
  • 05_deliverables_mvp/frontend/chat_otoia/README.md : « Portails (5, plateforme exclu) » (l.24) · « 5 fixtures Custom Block » (l.48) · « 5 configs runtime » (l.49) — trois transcriptions (portails + custom_blocks + mounts).
  • L'artefact source est byte-gaté par check_artifacts : frontend/chat_otoia/out/MANIFEST.json recompte counts.custom_blocks, counts.mounts et counts.portails du contrat RBAC à chaque build (bijection 1 block ⇔ 1 mount ⇔ 1 portail métier, plateforme exclu — invariant 2 du générateur).
  • Aucun gate ne comparait la VALEUR écrite à l'artefact. Piège aveugle caractéristique (cf. DevOps phases confirmations) : custom_blocks, mounts ET portails valent tous trois 5 aujourd'hui — un gate qui n'en recompterait qu'un serait aveugle à la dérive des deux autres (ajouter un portail au contrat RBAC → chat_otoia rebâtit 6 6 6 pendant que les deux docs restent à 5). « Vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS, pas la prose des docs) n'attrape.
  • État courant : aucun nombre périmé — 5/5/5 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute chaque nombre depuis son propre champ du MANIFEST (custom_blocks · mounts · portails, jamais figé ici · zéro duplication du compteur du générateur #5) et exige l'égalité avec les deux docs. Cohérence croisée : custom_blocks == mounts == portails == |MANIFEST.portails| (la bijection du générateur) — mord un compteur désynchronisé de sa propre liste. Un claim absent échoue AUSSI (traçabilité).

Preuve (5 morsures vérifiées + restauration verte)

Injection Résultat
fiche custom_block 5→6 (mounts reste 5, aveugle si 1 seul recompté) ✗ dit 6/5 MAIS artefact 5/5 · exit 1
fiche configs runtime 5→4 (blocks reste 5) ✗ dit 5/4 MAIS artefact 5/5 · exit 1
README portails 5→6 ✗ dit 6 portails MAIS artefact 5 · exit 1
README fixtures 5→7 ✗ dit 7 fixtures MAIS artefact 5 · exit 1
README claim « configs runtime » supprimé (reformulé) ✗ claim INTROUVABLE · exit 1
restauré ✓ 5/5/5 == artefact (2 docs) · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « montage Chat OTOIA par portail »).

Suite CI complète re-verte : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Session 20260731_120054 · Buffer S8 · Domaine Démo : le run-sheet de pitch (demo/scenarios) affichait « 10 modules cités » dans le diagramme d'architecture de son README — un compte data-derived transcrit À LA MAIN sans AUCUN gate

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR), QA (audit 5D) et DevOps (run-book). Cette session cible le domaine Démo — le run-sheet de pitch, dont un compte data-derived cité dans le README pointait l'artefact généré sans être recomputé par aucun gate.

Défaut trouvé — compte ungated, transcrit dans le diagramme du README

  • 05_deliverables_mvp/demo/scenarios/README.md:23 : diagramme d'architecture → « scenario_spec.json … out/ des modules livrés (10 modules cités) ».
  • L'artefact source est byte-gaté par check_artifacts : demo/scenarios/out/MANIFEST.json recompte counts.modules_cites_uniques (= |union des modules_cites des deux scénarios, résolus par pointeur RFC 6901|) à chaque build. Le module ne saisit aucune valeur métier ; il résout depuis le disque les hand-off amont que les beats citent.
  • Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter un beat qui cite un module encore non tissé (11) ou retirer la dernière citation d'un module d'un scénario (9) ferait dériver le diagramme en silence — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS, pas la prose du README) n'attrape.
  • État courant : aucun nombre périmé — 10 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated.

Nouveau bloc dans ci/check_readme_claims.sh : recompute le nombre depuis counts.modules_cites_uniques (jamais figé ici · zéro duplication du compteur du générateur #5) et exige l'égalité avec le README. Cohérence croisée en bonus : counts.modules_cites_uniques == |modules_cites| (liste dédupliquée du manifeste, elle-même sans doublon) — mord un compteur désynchronisé de sa propre liste. Un claim absent échoue AUSSI (traçabilité).

Preuve (2 morsures vérifiées + restauration verte)

Injection Résultat
README drift 10→11 ✗ dit 11 modules cités MAIS artefact 10 · exit 1
README claim supprimé (reformulé) ✗ claim « (N modules cités) » INTROUVABLE · exit 1
restauré ✓ 10 modules cités == artefact · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « run-sheet de démo »).

Suite CI complète re-verte : guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Session 20260731_113048 · Buffer S8 · Domaine DevOps : le run-book de déploiement VPS (devops/deploy_runbook) affichait « 7 phases » (fiche agent devops, ×2) et « 7 confirmations préalables » (README du module) — deux nombres data-derived transcrits À LA MAIN dans DEUX docs sans AUCUN gate

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR) et QA (audit 5D). Cette session cible le domaine DevOps — le run-book de déploiement VPS, dont deux comptes data-derived cités dans deux docs pointaient l'artefact généré sans être recomputés par aucun gate.

Défaut trouvé — comptes ungated, transcrits dans DEUX docs

  • 03_agents/devops/AGENT.md:25 : « graphe de 7 phases ordonnées », puis ligne 88 « exécute les 7 phases sur le VPS » — DEUX transcriptions du nombre de phases dans la même fiche.
  • 05_deliverables_mvp/devops/deploy_runbook/README.md:94 : « renseigner les 7 confirmations » + énumération de ces 7 confirmations (lignes 55-58).
  • L'artefact source est byte-gaté par check_artifacts : devops/deploy_runbook/out/MANIFEST.json recompte counts.phases (= len(graph.phase_order)) et counts.confirmations (= len(confirmations_ouvertes)) à chaque build.
  • Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter une phase au graphe OU une confirmation au catalogue ferait dériver les docs en silence. Piège aveugle caractéristique : phases ET confirmations valent tous deux 7 aujourd'hui — un gate qui n'en recompterait qu'un serait aveugle à la dérive de l'autre.

Nouveau bloc dans ci/check_readme_claims.sh : recompute chaque nombre depuis son propre champ du MANIFEST (jamais figé ici · zéro duplication du compteur du générateur #5) et exige l'égalité avec les docs. Le 22 (« suites gated » de la fiche devops) est volontairement écarté : sa sémantique (suites CI) ≠ counts.modules du run-book (qui s'auto-exclut) → pas de transcription propre, pas de gate hasardeux. Bonus anti-dérive : l'énumération des confirmations du README (tokens snake_case) doit être l'ensemble exact de confirmations_ouvertes — mord l'échange d'un nom là où le seul compte reste aveugle. Cohérences croisées : counts.phases == len(phase_order) · counts.confirmations == len(confirmations_ouvertes).

Preuve (5 morsures vérifiées + restauration verte)

Injection Résultat
fiche devops phases 7→8 (1re transcription) ✗ dit 8 MAIS artefact 7 · exit 1
fiche devops 2e transcription « exécute les 7 »→6 (aveugle si 1re reste 7) ✗ dit 6 MAIS artefact 7 · exit 1
README confirmations 7→8 (count) ✗ dit 8 MAIS artefact 7 · exit 1
README swap d'un nom de confirmation (count reste 7, aveugle au compte) ✗ énumération ≠ confirmations_ouvertes · exit 1
README claim confirmations supprimé (reformulé) ✗ claim INTROUVABLE · exit 1
restauré ✓ 7 phases + 7 confirmations + énumération == artefacts · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « run-book de déploiement VPS »).

Suite CI complète re-verte : check_artifacts · check_readme_claims · check_docs · check_regression · check_ci_integrity (gates statiques verrouillés) · guard_constraints · validate_json.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes data-derived des domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente) et mobile. Cette session cible le domaine Legal — la structure du DocType CONFOTUR, dont un quintuplet data-derived cité dans deux docs pointait l'artefact généré sans être recomputé par aucun gate.

Défaut trouvé — comptes du DocType ungated, transcrits dans DEUX docs

  • 05_deliverables_mvp/legal/confotur/README.md:17 : ligne de synthèse « 18 champs (14 de donnée) · 4 sections · 3 rôles · soumissible · 2 évènements de dépôt ».
  • 03_agents/onapi_legal/AGENT.md:28 : cellule de table reprenant les mêmes 5 nombres (chacun en gras), dérivés du même spec (source unique · #5).
  • Pourquoi non gaté : check_readme_claims couvrait tests, portails, RBAC, SEO, CRM pipeline et mobile — jamais la structure du DocType CONFOTUR. check_docs ne valide que les liens ; check_artifacts prouve que legal/confotur/out/MANIFEST.json est reproductible byte-for-byte (bloc counts = {fields, data_fields, sections, permissions, roles, depot_events} recomptés de confotur_spec.json à chaque build) mais ne compare pas ce que les DEUX docs en écrivent. Ajouter un champ/section/rôle ferait dériver les deux docs en silence — même « vert trompeur ».
  • État courant : aucun nombre périmé — 18/14/4/3/2 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated (faux-vert latent), pas un chiffre faux.

Fix — extension de check_readme_claims.sh (recompute les 5 volets depuis MANIFEST)

Nouveau bloc qui RECOMPUTE le quintuplet (fields, data_fields, sections, roles, depot_events) depuis legal/confotur/out/MANIFEST.counts (source déjà byte-gatée · zéro duplication du compteur du générateur) et exige l'égalité avec les deux docs : la ligne du README (sans gras) et la cellule de la fiche onapi_legal (chaque nombre en gras). Recomputer les 5 volets (et pas le seul total de champs) mord une dérive de N'IMPORTE lequel là où un seul nombre resterait aveugle. Un claim absent échoue AUSSI. En-tête du gate + ci/README.md (§ table + § détail) mis à jour.

Preuves de morsure

Injection Résultat
README sections 4→5 exit 1 dit 18/14/5/3/2 MAIS MANIFEST dit 18/14/4/3/2
Fiche champs 18→19 (README intact) exit 1 dit 19/14/4/3/2 MAIS MANIFEST 18/14/4/3/2 — mord la 2e doc
Fiche évènements dépôt 2→3 exit 1 dit 18/14/4/3/3 MAIS MANIFEST 18/14/4/3/2 — mord le dernier volet là où le total de champs est aveugle
Claim disparu (README reformulé) exit 1 synthèse DocType INTROUVABLE (attendu champs=18 …)
Restauré exit 0 (2 docs verts)

Vérifs

Contrôle Résultat
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte des out/) (édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact)
Matrice de régression 564/22 · PASS inchangée (ni tests/ ni doc-evidence touchés)
Diff 2 fichiers (ci/check_readme_claims.sh + ci/README.md) · 0 untracked

Anti-invention (#6) : rien inventé — le gate recompute les 5 volets depuis confotur MANIFEST.counts au lieu de figer un compteur ; les nombres des deux docs découlent de l'artefact. Hors périmètre worker (VPS · #8) : néant (gate python/stdlib en-repo). Auto-score 4Big : 96/100.


Session 20260731_093041 · Buffer S8 · Domaine CRM (non couvert hors prose « tests ») : le pipeline vente « 9 états · 11 transitions » était transcrit À LA MAIN dans DEUX README de module (titre workflow_vente + « 9 états du pipeline » de dossier_vente, même compte du même graphe) sans AUCUN gate — ajouter un état ferait dériver les deux docs en silence

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes data-derived des domaines tests, portails (structure), RBAC (3 volets) et SEO (mots-clés). Cette session cible le domaine CRM — la structure du pipeline vente, dont un compte data-derived cité dans deux README de module pointait le graphe généré sans être recomputé par aucun gate.

Défaut trouvé — comptes du pipeline ungated, transcrits dans DEUX docs

  • 05_deliverables_mvp/crm/workflow_vente/README.md:23 : titre de section « ## Le pipeline (9 états · 11 transitions) ».
  • 05_deliverables_mvp/crm/dossier_vente/README.md:30 : « options = les 9 états du pipeline » — même compte d'états, dérivé du même graphe (source unique · #5), recopié à la main.
  • Pourquoi non gaté : check_readme_claims couvrait tests, portails, RBAC et SEO — jamais la structure du pipeline CRM. check_docs ne valide que les liens ; check_artifacts prouve que crm/workflow_vente/out/MANIFEST.json est reproductible byte-for-byte (bloc counts = {states, transitions, …} recomptés de workflow_vente_spec.json à chaque build) mais ne compare pas ce que les DEUX docs en écrivent. Ajouter un état/une transition ferait dériver les deux docs en silence — même « vert trompeur ».
  • État courant : aucun nombre périmé — 9/11 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated (faux-vert latent), pas un chiffre faux.

Fix — extension de check_readme_claims.sh (recompute states + transitions depuis MANIFEST)

Nouveau bloc qui RECOMPUTE le couple (states, transitions) depuis crm/workflow_vente/out/MANIFEST.counts (source déjà byte-gatée · zéro duplication du compteur du générateur) et exige l'égalité avec les deux docs : le titre du README workflow (## Le pipeline (N états · M transitions)) et le compte d'états du README dossier_vente (les **N états** du pipeline). Recomputer transitions en plus de states mord une dérive du 2ᵉ nombre là où le compte d'états seul (repris dans dossier_vente) resterait aveugle. Un claim absent échoue AUSSI. En-tête du gate + ci/README.md (§ table + § détail) mis à jour.

Preuves de morsure

Injection Résultat
README workflow états 9→10 (titre) exit 1 dit 10/11 MAIS MANIFEST dit 9/11
README workflow transitions 11→12 (états inchangés) exit 1 dit 9/12 MAIS MANIFEST dit 9/11 — mord le 2ᵉ nombre
README dossier_vente états 9→8 (2e doc) exit 1 dit états=8 MAIS MANIFEST dit états=9 — mord là où le titre seul est aveugle
Claim disparu (dossier reformulé sans le motif) exit 1 claim « N états du pipeline » INTROUVABLE (attendu états=9)
Restauré exit 0 (2 docs verts)

Vérifs

Contrôle Résultat
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte des out/) (édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact ; aucun README/AGENT.md touché)
Matrice de régression 564/22 · PASS inchangée (ni tests/ ni doc-evidence touchés)
Diff 2 fichiers (ci/check_readme_claims.sh + ci/README.md) · 0 untracked

Anti-invention (#6) : rien inventé — le gate recompute states + transitions depuis workflow_vente MANIFEST.counts au lieu de figer un compteur ; les nombres des deux docs découlent de l'artefact. Hors périmètre worker (VPS · #8) : néant (gate python/stdlib en-repo). Auto-score 4Big : 96/100.


Session 20260731_090037 · Buffer S8 · Domaine SEO : le total ET la ventilation par langue des mots-clés (« 258 mots-clés (fr=87 · en=87 · es=84) »), transcrits À LA MAIN dans DEUX docs (table du README module + ligne de la fiche agent), n'étaient couverts par AUCUN gate — ajouter un projet ferait dériver les deux docs en silence

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes data-derived des domaines tests, portails (structure), RBAC (3 volets). Cette session cible le domaine SEO, dernier grand livrable dont un compte data-derived cité dans deux docs pointait un artefact sans être recomputé par aucun gate.

Défaut trouvé — comptes de mots-clés ungated, transcrits dans DEUX docs

  • 05_deliverables_mvp/seo/README.md:15 : table « Livrables » → « out/seo_keywords.json258 mots-clés (fr=87 · en=87 · es=84) ».
  • 03_agents/seo/AGENT.md:24 : même ligne, mêmes 4 nombres recopiés.
  • Pourquoi non gaté : check_readme_claims couvrait les comptes tests, les triplets par workspace des portails et les 3 volets RBAC — jamais le domaine SEO. check_docs ne valide que les liens ; check_artifacts prouve que seo/out/MANIFEST.json est reproductible byte-for-byte (bloc counts = {keywords_total, keywords_per_lang{fr,en,es}} recomptés de projets_master.json à chaque build) mais ne compare pas ce que les DEUX docs en écrivent. Ajouter un projet / du vocabulaire éditorial ferait dériver les deux docs en silence pendant que l'artefact dit autre chose — même « vert trompeur ».
  • État courant : aucun nombre périmé — 258=(87+87+84) recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated (faux-vert latent), pas un chiffre faux.

Fix — extension de check_readme_claims.sh (recompute total + 3 langues depuis MANIFEST)

Nouveau bloc qui RECOMPUTE le quadruplet (total, fr, en, es) depuis MANIFEST.counts (source déjà byte-gatée · zéro duplication du compteur du générateur) et exige l'égalité avec les deux docs. Recomputer les trois langues (pas seulement le total) attrape une compensation entre langues (fr 1 / en +1) qui laisserait le total juste et la ventilation fausse. Un claim absent échoue AUSSI. En-tête du gate + ci/README.md (§ table + § détail) mis à jour.

Preuves de morsure

Injection Résultat
README total 258→259 exit 1 dit 259/87/87/84 MAIS MANIFEST 258/87/87/84
Fiche compensation fr=87/en=87 → fr=86/en=88 (total inchangé) exit 1 dit 258/86/88/84 MAIS MANIFEST 258/87/87/84 — mord là où le total seul est aveugle
Claim disparu (README) exit 1 claim mots-clés INTROUVABLE (attendu total=258 …)
Restauré exit 0 (2 docs verts)

Vérifs

Contrôle Résultat
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte des out/) (édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact ; aucun README/AGENT.md touché)
Matrice de régression 564/22 · PASS inchangée (ni tests/ ni doc-evidence touchés)
Diff 2 fichiers (ci/check_readme_claims.sh + ci/README.md) · 0 untracked

Anti-invention (#6) : rien inventé — le gate recompute total + 3 langues depuis MANIFEST.counts au lieu de figer un compteur ; les nombres des deux docs découlent de l'artefact. Hors périmètre worker (VPS · #8) : néant (gate python/stdlib en-repo). Auto-score 4Big : 96/100.


Session 20260731_080025 · Buffer S8 · Hors du domaine « tests » : les comptes PAR WORKSPACE (cartes/liens/rôles) des 5 portails frontend, transcrits À LA MAIN dans DEUX docs (table du README module + prose de la fiche agent), n'étaient couverts par AUCUN gate — ajouter un rôle/DocType au contrat RBAC ferait dériver les deux docs en silence

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes du domaine tests (sommes, cellules par-suite, agrégats en prose). Cette session sort de ce domaine : audit des comptes data-derived cités dans les fiches et README de module qui pointent un artefact mais n'étaient recomputés par aucun gate.

Défaut trouvé — comptes par workspace ungated, transcrits dans DEUX docs

  • 05_deliverables_mvp/frontend/portails/README.md:29-33 : table « Portail | Workspace | Cartes | Liens | Rôles restreints » — 5 lignes × 3 nombres saisis à la main.
  • 03_agents/frontend_console/AGENT.md:28 : prose « 5 Workspace natifs (OTO Ventes 4 cartes/11 liens/12 rôles · OTO Construction 4/9/10 · OTO Achat 3/8/5 · OTO Compta 4/11/8 · OTO Direction 4/14/9) » — mêmes triplets recopiés.
  • Pourquoi non gaté : check_readme_claims couvrait les comptes de tests (README racine, fiches QA/Backend, cellules par-suite, prose CRM/RBAC/e-CF) et audit_4big/acceptance — jamais les comptes de structure des portails. check_docs ne valide que les liens ; check_artifacts prouve que frontend/portails/out/MANIFEST.json est reproductible byte-for-byte (tableau workspaces = {nb_cartes, nb_liens, nb_roles} recalculés depuis le contrat RBAC à chaque build) mais ne compare pas ce que les DEUX docs en écrivent. Ajouter un rôle/DocType au contrat ferait dériver les deux docs en silence pendant que l'artefact dit autre chose — même « vert trompeur » que les cellules « Tests ».
  • État courant : aucun nombre périmé — les 15 triplets et le total « 5 » des deux docs recoupent MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la surface ungated (faux-vert latent), pas un chiffre faux.

Fix — extension de check_readme_claims.sh (recompute par workspace depuis MANIFEST)

Nouveau bloc qui, pour chaque workspace de MANIFEST.workspaces (source déjà byte-gatée · zéro duplication du compteur du générateur), RECOMPUTE le triplet (nb_cartes, nb_liens, nb_roles) et exige l'égalité avec les deux docs : la ligne de table du README (ancrée | <portail> | \` | C | L | R |) **et** la cellule de prose de la fiche (`` C cartes/L liens/R rôles, forme abrégée C/L/Rtolérée). + contrôle du **total** « NWorkspacenatifs » == len(workspaces). Un workspace **absent** d'un doc échoue AUSSI. En-tête du gate +ci/README.md` (§ table + § détail) mis à jour.

Preuves de morsure

Injection Résultat
README table OTO Compta liens 11→12 exit 1 README dit 4/12/8 MAIS MANIFEST dit 4/11/8
Fiche prose OTO Achat 3/8/5→3/8/6 (table inchangée) exit 1 fiche dit 3/8/6 MAIS MANIFEST 3/8/5 — mord là où la table seule est aveugle
Cellule disparue (OTO Direction retirée de la prose) exit 1 claim INTROUVABLE (attendu 4/14/9)
Total 5→6 Workspace natifs exit 1 fiche dit 6 MAIS MANIFEST dit 5
Restauré exit 0 (10 lignes doc + total verts)

Vérifs

Contrôle Résultat
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte des out/) (édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact ; aucun README/AGENT.md touché)
Matrice de régression 564/22 · PASS inchangée (ni tests/ ni doc-evidence touchés)
Diff 2 fichiers (ci/check_readme_claims.sh + ci/README.md) · 0 untracked

Anti-invention (#6) : rien inventé — le gate recompute chaque triplet depuis MANIFEST.workspaces au lieu de figer un compteur ; les nombres des deux docs découlent de l'artefact. Hors périmètre worker (VPS · #8) : néant (gate python/stdlib en-repo). Auto-score 4Big : 96/100.


Session 20260731_073024 · Buffer S8 · Dernière surface de la classe « compte par suite » : les agrégats de tests rédigés EN PROSE dans deux fiches (crm « 81 (25+31+25) » · erpnext_backend « RBAC 60 (10+11+12+11+16) + e-CF 39 ») n'étaient couverts par AUCUN gate — une compensation entre suites laisserait la table gatée juste et la prose fausse

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). La session précédente a gaté les cellules « Tests » par suite des tables de fiches (recompute générique depuis plan.suites[path].test_methods), motivée par le fait qu'une compensation +1/1 entre deux suites passerait sous la seule somme agrégée. Recherche de la même classe un cran au-dessus : les agrégats rédigés en prose (hors table) que ce recompute par-cellule ne voit pas.

Défaut trouvé — 2 agrégats en PROSE ungated (total + composants saisis à la main)

  • 03_agents/crm/AGENT.md:32 : « Total CRM : 81 tests (25 + 31 + 25) ».
  • 03_agents/erpnext_backend/AGENT.md:39 : « Total backend RBAC 60 tests (10 + 11 + 12 + 11 + 16) + e-CF 39 tests ».
  • Pourquoi non gaté : check_readme_claims recompute les cellules de table (row_re exige un lien …/README.md + dernier champ numérique) — ces phrases en prose n'en sont pas. Le total agrégé du repo (564) est gaté ailleurs mais ne couvre pas ces sous-totaux par domaine ni leurs composants. Une compensation entre deux suites CRM (l'une +1, l'autre 1) laisserait la table (gatée) juste, la somme repo (564) juste, et la prose « (25 + 31 + 25) » fausse en silence — exactement le piège per-suite corrigé la session précédente, au niveau agrégé.
  • État courant : aucun nombre périmé — 81=(25+31+25), RBAC 60=(10+16+11+11+12), e-CF 39=fiscal/ecf_dgii, tous recoupés sur plan.suites. Le défaut est la surface ungated (faux-vert latent), pas un chiffre faux (anti-invention #6 : rien à réécrire).

Fix — extension de check_readme_claims.sh (recompute total + multiset composants)

Nouveau bloc qui, pour chaque agrégat en prose, RECOMPUTE depuis auth (plan.suites[path].test_methods, déjà chargé · zéro duplication) :

  • le total == somme des suites du domaine ;
  • le multiset des composants (trié, ordre-indépendant) == multiset des comptes par suite — c'est ce qui attrape la compensation que la somme seule rate ;
  • la cohérence interne (total == somme des composants écrits).
  • un contrôle à suite unique pour e-CF (pas de parenthèses). Un claim absent échoue AUSSI. En-tête du gate + ci/README.md (§ table + § détail) mis à jour.

Preuves de morsure

Injection Résultat
Total périmé 81→82 exit 1 dit total=82 … MAIS source dit total=81 (interne_ok=False)
Compensation (25+31+25)→(24+31+26) (somme=81 inchangée) exit 1 composants=[24,26,31] MAIS source [25,25,31] — le multiset mord là où la somme est aveugle
Claim e-CF disparu exit 1 claim INTROUVABLE (attendu 39)
Restauré exit 0

Vérifs

Contrôle Résultat
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte des out/) (édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact ; aucun AGENT.md touché)
Matrice de régression 564/22 · PASS inchangée (ni tests/ ni doc-evidence touchés)
Diff 2 fichiers (ci/check_readme_claims.sh + ci/README.md) · 0 untracked

Anti-invention (#6) : rien inventé — le gate recompute total ET composants depuis plan.suites au lieu de figer un compteur ; les nombres de prose découlent de l'artefact. Hors périmètre worker (VPS · #8) : néant (gate python/stdlib en-repo). Auto-score 4Big : 96/100.


Session 20260731_070019 · Buffer S8 · « Vert trompeur » ironique : le README de l'auditeur 4Big — le module qui COMPTE les modules — figeait son PROPRE total à la main (« 17/17 modules ») alors que son artefact quality_report.json en dit 22 ; ungated

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes agrégés/par-suite des docs d'entrée (README racine, fiches QA/Backend, tables « Tests »). Recherche du même faux-vert hors de ce périmètre : audit des comptes N modules / N/N dans les README de module (pas seulement l'entrée + les fiches).

Défaut trouvé — 2 comptes vivants « 17 » périmés dans qa/audit_4big/README.md

  • 05_deliverables_mvp/qa/audit_4big/README.md:68 (« Résultat courant ») : « Verdict PASS17/17 modules à 100/100 » — compte vivant saisi à la main, périmé (17 → 22 au fil des ajouts de modules). Ironie complète : c'est le README du module qui COMPTE les modules ; son propre artefact faisant autorité qa/audit_4big/out/quality_report.json (totals.modules) dit 22 depuis longtemps.
  • …/README.md:38 (règle du verdict global) : « PASS ssi couverture 100 % et les 17 modules ≥ 95/100 » — la règle (« TOUS les modules ≥ 95 ») ne dépend pas d'un compte ; le « 17 » n'était qu'une surface de dérive de plus.
  • Pourquoi non gaté : check_readme_claims recomputait 22/22 pour le README d'entrée (racine) mais ne couvrait pas ce README de module. check_docs ne valide que les liens ; l'auditeur s'auto-exclut (SoD) donc son propre out/ ne reflète pas l'édition de son README → aucun signal.

Fix (2 volets · CLAUDE.md #4 — corriger + supprimer la surface + garder)

  1. README.md:38 : retrait du compte figé → « tous les modules gated ≥ 95/100 » (la règle, count-free ⇒ zéro surface de dérive, précédent 050007/053011).
  2. README.md:68 : 17/1722/22 (compte courant, désormais gaté).
  3. ci/check_readme_claims.sh : nouveau bloc qui RECALCULE verdict + pass/modules/ min_score depuis quality_report.json (totals, déjà chargé dans q/qv · zéro duplication) et exige l'égalité avec ce que « Résultat courant » écrit. Un claim absent échoue AUSSI. Header du gate mis à jour (doc couverte).

Preuves de morsure

  • Bite 1 (valeur périmée) : réinjecté 17/17 ⇒ gate exit 1 ✗ … dit PASS/17/17/100 MAIS quality_report dit PASS/22/22/100 → régénérer le README.
  • Bite 2 (claim disparu) : reformulé sans le motif ⇒ exit 1 ✗ … claim INTROUVABLE.
  • Restauréexit 0.

Vérifs

Contrôle Résultat
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte des out/) (auditeur auto-exclu SoD ⇒ quality_report.json byte-identique ; 0 régénération)
Matrice de régression 564/22 · PASS inchangée (README édité, aucun tests/)
Résidu 17 vivant repo-wide aucun (seul restant = daily_report 2026-07-30 point-in-time, conservé)
Diff 2 fichiers · +39/3 · 0 untracked

Anti-invention (#6) : rien inventé — le gate recompute la vérité depuis totals au lieu de figer un compteur ; le 22/22 du README découle de l'artefact. Hors périmètre worker (VPS · #8) : néant (doc + gate bash/stdlib en-repo). Auto-score 4Big : 96/100.


Session 20260731_053011 · Buffer S8 · Comptes vivants « 551 tests » périmés DANS la machinerie CI elle-même (.gitea/workflows/ci.yml + ci/check_regression.sh + ci/check_readme_claims.sh) — non couverts par aucun gate

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 558 tests · 7 gates verts). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes vivants des docs d'entrée (README, fiches QA/Backend) puis nettoyé la doc du CI (ci/README.md, session 050007). Recherche du même faux-vert hors de ce périmètre : audit git grep '551' repo-wide (pas seulement *.md) → la machinerie CI opérationnelle (workflow + headers de gates), qu'aucun gate ne couvre.

Défauts trouvés — 3 comptes vivants « 551 » périmés (→ 558), dans le code des gates

  • .gitea/workflows/ci.yml:67 : le commentaire du job check-regression décrivait l'artefact commité comme « 22 suites · 551 tests · PASS, cité dans toute la doc » — compte vivant figé périmé (matrice 534 → 551 → 558).
  • ci/check_regression.sh:8 (l'ironie, encore) : le header du gate de fraîcheur de la matrice — dont le rôle même est d'empêcher le compte agrégé de se périmer — s'illustrait par « totaux « 22 suites · 551 tests · verdict PASS » cité partout ». Le gardien anti-péremption avait lui-même péri.
  • ci/check_readme_claims.sh:152 : l'anecdote décrivant le défaut QA disait « … alors que le run dit 22 / 551 » — présent de l'indicatif ⇒ prétention vivante du compte courant, désormais fausse (558). (Le « 21 suites · 534 tests » de la même ligne est l'anecdote historique point-in-time du défaut corrigé — conservé.)
  • Pourquoi non gaté : check_readme_claims ne couvre que README + fiches QA/Backend ; ces fichiers mélangent narratifs historiques figés volontaires (progression « 534 → 551 → 558 ») et comptes vivants — exactement le motif qui a laissé ci/README.md dériver (session 050007).

Fix (2 solutions · CLAUDE.md #4 — retenu : supprimer la surface de dérive · précédent 050007)

Plutôt que réécrire 551→558 (qui re-dériverait au prochain ajout de suite), retrait du nombre vivant figé au profit d'un renvoi à l'artefact — « les totaux agrégés (suites · tests · verdict) dont la doc dérive, jamais figés en dur ici ». Zéro nombre à périmer = zéro surface de dérive, structurellement plus fort qu'un gate sur de la prose (#6).

  • ci.yml:67 & check_regression.sh:8 : compte figé → renvoi descriptif à regression_run.json.
  • check_readme_claims.sh:152 : « le run dit 22 / 551 » → « le run agrégé faisant autorité en disait déjà davantage (recompté ci-dessous, jamais figé ici) » — formulation identique à celle appliquée à ci/README.md en session 050007. Le 534 point-in-time reste comme valeur du défaut.
  • (Alt. écartée : étendre check_readme_claims à ci.yml/ci/*.sh — fragile, il faudrait distinguer chaque progression historique figée d'un compte vivant ; le retrait est plus fort.)

Preuves

Contrôle Résultat
git grep '551' hors logs/daily_reports/progression 534→551→558 aucun compte vivant résiduel
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte des out/) (édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact)
check_ci_integrity (câblage gate.needs inchangé)
Diff 3 fichiers · +9/6 · 0 untracked

Hors périmètre worker (VPS · #8) : néant (commentaires CI en-repo, aucun touch prod). Anti-invention (#6) : rien inventé — retrait de nombres figés, la vérité reste recomputée par les gates depuis regression_run.json. Auto-score 4Big : 96/100.


Session 20260731_050007 · Buffer S8 · Chiffre vivant périmé DANS la doc du CI (ci/README.md) — le gate check_regression était illustré par un compte de tests faux, et check_readme_claims sous-décrit (2 docs au lieu de 3)

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 558 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive : la classe « chiffre inventé/périmé » (README, fiche QA, fiche Backend) est gatée sur 3 docs ; recherche d'un même faux-vert hors de ce périmètre. Audit repo-wide des agrégats (git grep '<N> suites|<N> modules|551|534' sur *.md hors logs/archives) + relecture des 7 gates pour un bug de logique.

Défauts trouvés — 3 dérives réelles, toutes dans ci/README.md (doc du livrable DevOps)

  • ci/README.md:80 (le pire) : la section décrivant check_regression — le gate censé empêcher les compteurs de se périmer — s'illustrait elle-même par un compte vivant périmé : « 22 suites · 551 tests · PASS ». La matrice faisant autorité (qa/regression/out/regression_run.json) dit 558 depuis la session 043004 (534 → 551 → 558) ; l'exemple appariait le compte de suites courant (22) à un compte de tests périmé (551). Ironie complète : la doc du gate anti-dérive avait elle-même dérivé.
  • ci/README.md:130 : la ligne anecdote de la fiche QA citait « le run réel 22 / 551 » au présent — 551 ne correspond plus à rien (→ 558).
  • ci/README.md:121-131 : la section §check_readme_claims affirmait garder « les deux docs d'entrée » (README + fiche QA) et omettait la fiche ERPNext Backend — 3ᵉ doc pourtant ajouté à la couverture du gate en session 043004. Le header du script check_readme_claims.sh (source de vérité · l.23/29/32) liste bien les 3 ; seul ci/README.md a dérivé → doc qui sous-décrit son propre code.
  • Pourquoi non gaté : session 030001 a délibérément laissé ci/README.md hors de check_readme_claims car il porte des chiffres historiques figés volontaires (anecdotes « demo 18→21 », « 21 suites · 534 tests »). C'est ce mélange historique/vivant qui a laissé le compte vivant (551) se périmer en silence.

Fix (2 solutions · CLAUDE.md #4 — retenu : supprimer la surface de dérive + réaligner)

  • Correctif de fond (l.80) : le compte vivant figé « 22 suites · 551 tests » est supprimé au profit d'un renvoi — « le compte agrégé (suites · tests · passés · verdict) … recompté par check_readme_claims dans les fiches d'entrée, jamais figé en dur ici ». Plus de nombre à périmer = zéro surface de dérive (plus robuste qu'un gate sur une prose ; #6). (Alt. écartée : étendre check_readme_claims à ci/README.md — fragile, il faudrait distinguer chaque anecdote historique figée d'un compte vivant ; supprimer le nombre est structurellement plus fort.)
  • Anecdote historique (l.130) : « le run réel 22 / 551 » (chiffre vivant faux) remplacé par « alors que le run agrégé faisant autorité en disait davantage » ; le 534 est conservé comme valeur point-in-time du défaut corrigé (comme « demo 18→21 »), désormais sans nombre concurrent périmé.
  • Réalignement (l.121-134) : « les deux docs d'entrée » → « les trois » + ajout du bloc fiche Backend (N tests · M suites · verdict → regression_run.json), miroir du header du script. La doc décrit à nouveau exactement le code.

Preuves

Contrôle Résultat
git grep '551' *.md (hors logs/archives) aucun chiffre vivant résiduel
534 restant 1 seule ligne = anecdote historique explicite (l.131)
« trois docs » + fiche Backend décrite dans ci/README.md
7 gates (guard/json/docs/artifacts/regression/ci_integrity/readme_claims) exit 0
check_artifacts (rebuild + égalité byte de tous les out/) (édition hors 05_deliverables_mvp ⇒ 0 dérive d'artefact)
Arbre de travail 1 fichier modifié, 0 untracked

Anti-invention (#6) : rien inventé — la vérité (558) vient de regression_run.json (gaté par check_regression) ; le fix retire le compteur figé plutôt que d'en poser un nouveau. Matrice 558/22 · PASS inchangée.

Non-régression : édition doc pure hors 05_deliverables_mvpqa/audit_4big ne la lit pas comme evidence ⇒ quality_report.json inchangé ; aucune régénération de consommateur nécessaire.

Hors périmètre worker (VPS · #8) : néant (doc en-repo ; runner Gitea = DevOps).

Auto-score 4Big : 96/100.

Session 20260731_040004 · Sprint 7/8 · Livrable démo : ajout du prompteur Markdown (out/run_sheet.md) — le support texte lisible manquait au « scénario démo prêt à jouer »

Tâche : Sprint 7 l.68 (« CRM + Faisabilité : Scénarios démo P07 banquier · P05 client ready ») × Sprint 8 buffer. Après audit : les 7 gates CI étaient verts, 15 promesses in_repo, faisabilité 4 volets complète. Le seul livrable démo (demo/scenarios) ne produisait qu'un run-sheet JSON machine — aucun support lisible par un présentateur. Son propre README plafonnait l'auto-score à 96/100 en qualifiant le « prompteur » de hors périmètre (VPS). Or rendre un Markdown in-repo depuis le JSON est exactement le pattern déjà utilisé par faisabilite (rend des .md) — aucune écriture VPS. Gap réel, borné, à forte valeur.

Livraison

  • scenlib/render.py (nouveau) — render_markdown(run_sheet) -> str pur et déterministe. Ne lit que le run-sheet (déjà résolu, déjà anti-inventé) : aucun chiffre nouveau, aucun recalcul (anti-invention #6). _fmt : bool→oui/non, liste→jointe, placeholder survivant rendu entre back-ticks (jamais promu en nombre).
  • demo_scenario_gen.py build émet désormais out/run_sheet.md en plus des deux JSON. Le rendu est un prompteur par scénario : en-tête (audience/projet/durée/ objectif), puis chaque beat en table « À dire | Chiffre | Source (preuve) » — chaque ligne trace son pointeur RFC 6901 amont.
  • out/run_sheet.md (nouveau, commité) — 2 scénarios · 12 beats · 24 min cumulés.

Compatibilité architecture (vérifiée AVANT d'écrire)

  • HANDOFF 4Big (q4lib/criteria.py:91) ne json.load que les fichiers .json — un .md dans out/ est ignoré, aucune casse du critère.
  • ci/check_artifacts.sh diffe tout fichier produit par build → il exige désormais run_sheet.md commité + reproductible byte-for-byte (render déterministe ⇒ OK). Preuve de morsure implicite : fichier non suivi ⇒ gate exit 1 (constaté puis corrigé par git add).

Consommateurs régénérés (dérive maîtrisée)

  • +7 méthodes test (TestRenderMarkdown) → régression 551→558 tests (22 suites, PASS) : regression run + build régénérés (regression_run/plan/MANIFEST.json).
  • demo/scenarios : README 5011→5575 octets + 32→39 test_* → quality_report.json régénéré (evidence DOC+TESTS) · verdict PASS · 22/22 · min 100 inchangé.
  • 03_agents/qa/AGENT.md : verdict agrégé 551→558 (gate check_readme_claims vert).
  • README démo : Fichiers (+render.py, +run_sheet.md), chaîne de valeur, « hors périmètre » (seul l'habillage visuel reste VPS), auto-score 96→97.

Vérifs

Contrôle Résultat
7 gates CI (guard/json/docs/artifacts/regression/readme_claims/ci_integrity) exit 0
Tests démo 39/39
Tests audit_4big 34/34
Régression exhaustive 22/22 suites · 558 tests · PASS
run_sheet.md reproductible (build frais == commité) (gate artifacts)
Arbre de travail 0 fichier untracked

Hors périmètre worker (VPS · #8) : habillage visuel deck / page démo otov7.com + branchement data_room réel — inchangé. Le prompteur texte est le rendu de référence in-repo que ce visuel habillera.

Auto-score 4Big : 97/100.

Session 20260731_033001 · Buffer S8 · « Vert trompeur » : la fiche QA affichait un « verdict agrégé courant » PÉRIMÉ (21/534) en se disant « jamais compté à la main » — hors du périmètre du gate README

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close (22/22 modules 100/100, 551 tests verts, 7 gates statiques). Poursuite de la série anti-dérive : le gate check_readme_claims créé la session précédente ne garde que README.md ; recherche d'une même classe de faux-vert hors de son périmètre.

Défaut trouvé — 3 chiffres périmés en SILENCE dans 03_agents/qa/AGENT.md et ci/README.md

  • Mécanisme : la session 030001 a corrigé le README et claimé avoir corrigé « 3 commentaires CI (21 suites · 534 tests) dans ci.yml et check_regression.sh ». Mais son propre gate ne couvre que README.md. Deux autres docs d'entrée portaient encore le compte périmé — invisibles pour tous les gates (check_docs ne valide que les liens) :
    • 03_agents/qa/AGENT.md:35 (le pire) : section « Verdict agrégé courant » → « 21 suites gated · 534 tests · 534 passés · 0 échec · 0 erreur → verdict PASS » en déclarant, ligne suivante, « jamais compté à la main ». C'était pourtant un compte figé à la main qui cite qa/regression/out/regression_run.json comme source — alors que le run réel dit 22 / 551. Une fiche d'agent QA qui affirme une matrice verte avec des chiffres faux est le « vert trompeur » le plus grave.
    • ci/README.md:80 : exemple « compteurs cités partout » figé à « 21 suites · 534 tests · PASS » (→ 22 · 551).
    • ci/README.md:184 : checklist DevOps « les 6 gates statiques » (→ 7 depuis l'ajout de check-readme-claims).
  • Preuve au sol : git grep -nE '534|21 suites|6 gates' sur *.md (hors logs) ramenait les 3 lignes ; regression_run.json recalculé = 22 suites · 551 tests · 551 passés · 0 échec · 0 erreur · PASS (somme des 22 suites du run commité).

Fix (2 solutions · CLAUDE.md #4 — retenu : correction + extension du gate au-delà du README)

  • Correction des 3 chiffres pour refléter les artefacts (fiche QA 21/534→22/551 ; ci/README.md exemple 21·534→22·551 et checklist 6→7 gates). Ligne 132 « Les 6 gates commençaient… » → « Les gates… » (le compte historique contredisait la table §1 à 7).
  • Correctif de fond : extension de ci/check_readme_claims.sh (7ᵉ gate) au-delà du README. Il RECOMPUTE désormais aussi le « Verdict agrégé courant » de la fiche QA depuis regression_run.json (compte agrégé faisant autorité, commité · déterministe) : N suites · M tests · M passés · E échec · E erreur · verdict, exige l'égalité avec ce qui est écrit dans 03_agents/qa/AGENT.md. Un claim absent échoue AUSSI. (Alt. écartée : 2ᵉ gate dédié — même classe de contrôle « valeur↔artefact » ; l'ajouter au gate existant évite un doublon de câblage CI · #5.) Le nom de fichier reste inchangé (pas de ré-câblage ci.yml/INV-B) ; en-tête du script + ci/README.md §1 table + §2 détail mis à jour pour la portée élargie (README et fiche QA).

Preuves de morsure

  • Bite 1 (valeur périmée) : réintroduit « 21 suites · 534 tests » ⇒ gate exit 1 ✗ Fiche QA … dit 21/534/534/0/0/PASS MAIS regression_run dit 22/551/551/0/0/PASS.
  • Bite 2 (claim disparu) : retire toute la ligne « Verdict agrégé » ⇒ exit 1 ✗ Fiche QA · verdict agrégé — claim INTROUVABLE.
  • Restauréexit 0 (les 6 claims README + le claim QA verts).

Anti-invention (#6) : rien inventé — le gate recompute la vérité depuis regression_run.json (somme des suites du run commité) au lieu de figer un compteur ; les 3 chiffres corrigés sont tous recoupés sur cet artefact.

Non-régression : 7 gates locaux verts, dont check_artifacts (rebuild + égalité byte de tous les out/) ⇒ aucune dérive d'artefact (l'audit_4big ne lit ni 03_agents/ ni ci/ comme evidence → pas de régénération quality_report). Matrice 551/22 · PASS inchangée. Unique « 534 » résiduel = description historique volontaire du défaut corrigé (ci/README.md:130, comme l'anecdote « demo 18→21 »).

Hors périmètre worker (VPS · #8) : néant (stdlib pur en-repo ; runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.

Session 20260731_030001 · Buffer S8 · « Vert trompeur » : le README (point d'entrée) affichait des chiffres PÉRIMÉS que sa propre source contredisait — aucun gate ne les enforçait

Tâche : Sprint 8 · buffer L75 (DevOps CI/CD · QA). Roadmap fonctionnellement close (22/22 modules 100/100, 551 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un faux-vert réel (une affirmation présentée comme vérifiée mais qu'aucun gate ne vérifie), même classe que les bugs regression_run.json / out/ RBAC / cd "" des sessions précédentes.

Défaut trouvé — les chiffres du README dérivaient en SILENCE vs leurs artefacts cités

  • Mécanisme : README.md est le point d'entrée du mandat ; sa section « État courant (sourcé) » déclare mot pour mot « Chaque chiffre ci-dessous est sourcé vers un artefact commité (anti-invention #6) ; ce README n'introduit aucune donnée nouvelle. » Cette promesse n'était gardée par aucun gate : check_docs.sh ne valide que les liens (la cible existe), jamais la VALEUR des nombres. Quand un module + son job CI ont été ajoutés au fil des sprints, les chiffres du README se sont périmés en silence tout en restant « sourcés » vers un artefact qui, lui, dit autre chose — un README qui se contredit avec sa propre source est le pire « vert trompeur » (CLAUDE.md #6 : « zéro invention de chiffres »), appliqué à la doc d'entrée.
  • Preuve au sol (3 dérives réelles constatées) :
    • README : « 21/21 modules gated à 100/100 » — qa/audit_4big/out/quality_report.json totals dit 22/22 (min_score 100, verdict PASS) ;
    • README : « 21 suites gated » — qa/regression/out/regression_plan.json totals.suites dit 22 (551 tests) ;
    • README : « 15 promesses… 14 in_repo prouvées + 1 out_of_scope » — qa/acceptance/out/acceptance_matrix.json a 15 in_repo (M5 est désormais in_repo avec son hors-périmètre tracé per-promesse ; 0 promesse hors in_repo). Les liens étaient tous verts (check_docs OK) : la dérive était invisible.

Fix (2 solutions · CLAUDE.md #4 — retenu : correction README + gate qui l'enforce)

  • Correction des 3 chiffres du README (21→22 modules, 21→22 suites, « 14+1 » → « 15 in_repo ») pour refléter les artefacts.
  • Correctif de fond : nouveau gate ci/check_readme_claims.sh (7ᵉ gate statique) qui RECOMPUTE chaque chiffre depuis l'artefact cité (jamais une liste à la main · #6) et exige l'égalité avec ce qui est écrit dans le README : modules/verdict (audit_4big), suites (régression), promesses/statuts/verdict (acceptance), nombre d'AGENT.md (×2 : nav + titre). Un claim absent du README échoue AUSSI (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). (Alt. écartée : régénérer le README depuis un template — surdimensionné et fragile pour une section en prose ; un gate qui compare valeur↔source est la preuve la plus forte et la moins couplée à la mise en forme.)
  • Câblage CI : job check-readme-claims ajouté à ci.yml + inséré dans gate.needs (30 jobs bloquants désormais). check_ci_integrity (INV-A/INV-B) valide le câblage : gate.needs == 32 jobs 1 (gate) 1 manuel ; ci/check_readme_claims.sh lancé par check-readme-claims ∈ gate, et source ci/lib.sh (pas de code mort, échoue en exit 3 hors arbre git comme les 6 autres).

Preuves de morsure

  • Bite 1 (valeur périmée) : réintroduit « 21/21 modules » ⇒ gate exit 1 ✗ README dit 21/21/100 MAIS artefact dit 22/22/100 → régénérer README.
  • Bite 2 (claim disparu) : retire **22 suites gated** ⇒ gate exit 1 ✗ claim INTROUVABLE dans README (attendu suites=22).
  • Restauréexit 0 vert. Gate inerte tant que README == artefacts.

Anti-invention (#6) : rien inventé — le gate recompute la vérité depuis les artefacts au lieu de figer un compteur ; les 3 chiffres corrigés sont tous recoupés sur quality_report/regression_plan/acceptance_matrix.

Nettoyage cohérence (#5 · éliminer le vieux) : 3 commentaires CI mentionnaient encore « 21 suites · 534 tests » (désormais 22 · 551) — corrigés dans .gitea/workflows/ci.yml et ci/check_regression.sh. ci/README.md : table §1 + détail §2 + commande §3 du nouveau gate ajoutés.

Vérifs : 7 gates locaux verts (guard_constraints/validate_json/check_docs/ check_artifacts/check_regression/check_ci_integrity/check_readme_claims) ; ci.yml YAML valide ; check_ci_integrity : gate.needs = 30 jobs (32 gate e2e-baseline manuel), 7 ci/*.sh câblés + lib.sh sourcée par les 7. Artefacts out/ inchangés (aucun module 05_deliverables_mvp/ touché ⇒ pas de régénération).

Hors périmètre worker (VPS · #8) : néant (stdlib pur en-repo ; enregistrement du runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.

Session 20260731_015954 · Buffer S8 · « Vert trompeur » : les 6 gates ci/*.sh passaient VERT hors d'un arbre git en ne contrôlant RIEN

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD). Roadmap fonctionnellement close (21/21 modules 100/100, 534 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un faux-vert réel (gate qui passe sans rien vérifier), même classe que les bugs regression_run.json / out/ RBAC des sessions précédentes.

Défaut trouvé — cd "$(git rev-parse --show-toplevel)" échoue en SILENCE hors d'un checkout git

  • Mécanisme : les 6 gates commençaient par cd "$(git rev-parse --show-toplevel)". HORS d'un arbre de travail git (tarball, git archive | tar -x, git absent du PATH, working-dir erroné), git rev-parse n'écrit rien sur stdout → la commande devient cd "", un no-op qui RETOURNE SUCCÈS (le script reste dans le cwd au lieu d'échouer). Les gates poursuivaient sur un git ls-files renvoyant une liste VIDEvalidate_json (« Aucun fichier .json suivi — rien à valider »), guard_constraints et check_docs sortaient exit 0 VERT en ayant contrôlé ZÉRO fichier (≈50 JSON non validés, contraintes CLAUDE.md non vérifiées). Un gate qui passe au vert sans rien enforcer est le pire « vert trompeur » — précisément ce que la série buffer traque.
  • Preuve au sol (avant fix) : copie de l'arbre sans .gitvalidate_json exit 0 « rien à valider » ; guard_constraints exit 0 « Contraintes respectées » ; check_docs exit 0. Ce piège avait déjà faussé une vérif « 6 gates verts sur git archive » d'une session antérieure (un git archive | tar -x n'a pas de .git, alors que le runner Gitea a un vrai .git via actions/checkout@v4 — donc la méthode « archive » n'est PAS équivalente au runner pour tout check qui interroge git).

Fix (2 solutions · CLAUDE.md #4 — retenu : helper partagé DRY + durcissement INV-B)

  • Correctif de fond : nouveau helper ci/lib.sh (sourcé, jamais exécuté seul) exposant cd_repo_root — se positionne à la racine, ou échoue bruyamment (exit 3, code distinct d'un échec de contrôle 1) avec un message clair si git rev-parse ne renvoie rien. Les 6 gates remplacent leur cd "$(git rev-parse …)" par source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" || exit 3 ; cd_repo_root. (Alt. écartée : inliner le garde de 4 lignes × 6 fichiers = doublon, contraire à CLAUDE.md #5 · préférence lib partagée.)
  • Durcissement du gate de câblage (INV-B · check_ci_integrity.sh) : INV-B exigeait que chaque ci/*.sh soit lancé par un job — ci/lib.sh (jamais dans un run:) aurait été flaggé « gate mort ». INV-B accepte désormais un ci/*.sh câblé de deux façons : lancé par un job du gate, OU sourcé par ≥1 gate script (détecté par grep d'une ligne source …/<base> / . …/<base>, # exclu). C'est un renforcement réel : le gate prouve maintenant aussi que les libs partagées sont effectivement sourcées (pas du code mort).

Preuves de morsure

  • Faux-vert corrigé : arbre copié sans .git → les 6 gates sortent désormais exit 3 avec « ERREUR gate : hors d'un arbre de travail git » (avant : validate_json/guard_constraints/check_docs = exit 0 vert). Idem sur git archive HEAD | tar -x après commit.
  • INV-B reconnaît la lib : ci/lib.sh — lib sourcée par [les 6 gates] · non exécutée en propre (OK)exit 0.
  • INV-B attrape une lib morte : ci/_dead.sh (sourcé par personne) ⇒ ✗ … lancé/sourcé par AUCUN job CI (gate mort)exit 1.

Non-régression : 6 gates verts dans l'arbre de travail (= ce que voit le runner via actions/checkout@v4, avec un vrai .git) ; matrice 534/21 · PASS inchangée ; qa/audit_4big out/ inchangé (les édits ci/ sont hors 05_deliverables_mvp → aucune dérive de consommateur) ; ci.yml YAML valide.

Anti-invention (#6) : rien inventé — garde purement défensif (bash/git stdlib), INV-B relit ci.yml + grepe les gates sur disque à chaque run.

Hors périmètre worker (VPS · #8) : néant (stdlib en-repo ; enregistrement du runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.


Session 20260731_012944 · Buffer S8 · Reproductibilité en CHECKOUT PROPRE — le hand-off out/ des 4 générateurs RBAC était .gitignoreé → gate rouge en CI, vert en local seulement

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD). Roadmap fonctionnellement close (21/21 modules 100/100, 534 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un écart green-local / red-en-CI réel (même classe que le bug regression_run.json corrigé plus tôt aujourd'hui) plutôt qu'un correctif cosmétique.

Défaut trouvé — 2 gates ROUGES en checkout propre (check-artifacts + check-regression)

  • Méthode : test au sol par git archive HEAD | tar -x (= exactement ce que voit le runner Gitea), et non bash ci/*.sh dans l'arbre de travail.
  • Constat : les 4 générateurs RBAC (rbac/fixtures_gen, userperm_gen, roleprofile_gen, apply_plan) .gitignoreaient leur out/ (« re-générable à la demande ») — alors qu'ils sont audités en archétype generator (critère HANDOFF = out/MANIFEST.json + ≥1 artefact JSON). En checkout propre, leur out/ est absent → l'audit 4Big (qa/audit_4big, dont le build relit le out/ de CHAQUE module) les note 80 < 95INV7 ⇒ build refusé. Cascade : check-artifacts rouge (rebuild audit_4big + demo échouent) et check-regression rouge. Ça ne « passait » qu'en local, grâce aux out/ non suivis laissés sur disque par des build manuels — exactement la classe du bug regression_run.json (green-local/red-CI).
  • Preuve au sol : git archive de HEADcheck_artifacts exit 1 (INV7 rbac-* note 80 < 95 · demo/scenarios build a échoué) et check_regression exit 1. Les 15 autres générateurs commettent bien leur out/ → seuls les 4 RBAC étaient l'exception.

Fix (2 solutions · CLAUDE.md #4 — retenu : aligner sur les 15 autres modules)

  • Correctif de fond : committer le out/ des 4 générateurs RBAC (retrait de out/ des 4 .gitignore + git add). Faisabilité prouvée : chaque build est byte-déterministe (2 builds successifs identiques ; tri stable, zéro horodatage). apply_plan lit ses frères en process (imports), pas via leur out/ — committer le out/ sert uniquement le contrat HANDOFF audité. L'audit note désormais les 4 modules 100/100 en checkout propre. (Alt. écartée : reclasser les 4 en archétype sans HANDOFF — diverge du reste des générateurs et laisse check_artifacts les ignorer, moins cohérent · #5.)
  • Durcissement du gate (empêche la récidive) : ci/check_artifacts.sh comparait le build frais au fichier de l'arbre de travail — donc un out/ non suivi (ignoré / jamais git add) le rendait vert en local alors qu'il serait absent en CI. Ajout d'une assertion git ls-files --error-unmatch : tout fichier produit par build DOIT être suivi par git → l'écart devient une erreur locale honnête, plus une surprise en CI.
  • Régénération des consommateurs (mémoire projet) : l'édition des 4 README RBAC (en-tête out/ « non commité » → « commité · byte-déterministe », +18 o chacun) a fait dériver qa/audit_4big/out/quality_report.json (evidence DOC = taille README) — régénéré (PASS · 21/21 · min 100). demo + regression run rejoués : inchangés (ils lisaient déjà l'état correct).

Preuve de morsure du durcissement : git rm --cached role.json (fichier laissé sur disque, simulant un out/ ignoré) ⇒ check_artifacts exit 1 (✗ … NON SUIVI par git → absent en CI propre) ; re-git addexit 0. Avant le durcissement, ce même état passait vert.

Vérifs : 6 gates verts sur git archive propre (guard/json/docs/artifacts/ regression/ci_integrity) ; suites harnais OK (fixtures/userperm/roleprofile/ apply_plan/audit_4big/regression/demo · 32/32) ; matrice 534/21 inchangée ; build de TOUS les consommateurs → zéro dérive résiduelle.

Anti-invention (#6) : rien inventé — out/ RBAC = sortie déterministe des générateurs ; quality_report recalculé depuis les faits du dépôt.

Hors périmètre worker (VPS · #8) : néant (stdlib pur en-repo ; les fixtures Frappe côté VPS restent .gitignoreées, hors de ce correctif).

Auto-score 4Big : 96/100.


Session 20260731_005934 · Buffer S8 · Intégrité du câblage CI — le gate d'agrégat verrouille TOUS les jobs (gates statiques compris)

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD). Roadmap fonctionnellement close (21/21 modules à 100/100, 534 tests verts, 5 gates statiques tous verts au démarrage). Poursuite de la série anti-dérive (INV4, fraîcheur matrice, reproductibilité artefacts) : recherche d'un trou de câblage réel plutôt qu'un correctif cosmétique.

Défaut trouvé — les gates STATIQUES n'étaient gardés par aucun invariant d'appartenance au gate

  • Constat : le job gate (needs: [...]) est le seul verrou de merge — un check absent de son needs: ne bloque rien, même rouge. Les deux couvertures existantes (audit_4big/registry.coverage_report, qa/regression/discovery. coverage_report) prouvent l'appartenance au gate uniquement pour les jobs de module (ceux portant un working-directory: sous 05_deliverables_mvp). Les 5 gates statiques (constraints-guard/validate-json/check-docs/ check-artifacts/check-regression → scripts ci/*.sh) n'ont pas de working-directory : rien ne garantissait qu'ils alimentent le gate. Retirer l'un d'eux de needs: (ou une typo dans la liste, ou un ci/*.sh décâblé) rendait le gate vert en se court-circuitant — même classe de dérive silencieuse que INV4 (orphan_tests_dirs), côté câblage CI.
  • Fix : nouveau gate ci/check_ci_integrity.sh (bash/awk/git, stdlib pur, zéro réseau) + job CI check-ci-integrity, lui-même ajouté à gate.needs. Deux invariants, lus par FAITS dans ci.yml (jamais une liste à la main) :
    • INV-A : gate.needs == { tous les jobs définis } { gate } { jobs manuels if: … workflow_dispatch …, ex. e2e-baseline légitimement hors du gate push/PR }. Détecte MISSING (job oublié → ne bloque pas), DANGLING (référence fantôme : typo / job supprimé) et MANUAL-IN-GATE (job manuel dans needs → gate en attente perpétuelle sur push).
    • INV-B : chaque ci/*.sh du dépôt est lancé par exactement un job (run: bash ci/<script>), lui-même dans gate.needs — un gate statique non câblé (script mort) ou décâblé casse le check.
  • Preuve de morsure (5 modes, tous exit 1) :
    • MISSING : check-artifacts retiré de needs✗ ABSENT de gate.needs + INV-B sur check_artifacts.sh ;
    • DANGLING : seo-testsseo-testz dans needs✗ référence 'seo-testz' non définie + ✗ seo-tests ABSENT ;
    • MANUAL-IN-GATE : e2e-baseline ajouté à needs✗ job manuel … gate en attente ;
    • SCRIPT-MORT : ci/check_phantom.sh commité sans job ⇒ ✗ lancé par AUCUN job ;
    • DÉCÂBLÉ : couvert par le mode MISSING (job hors needs). État nominal restauré ⇒ exit 0 (gate.needs = 28 jobs = 30 définis 1 (gate) 1 manuel [e2e-baseline]).

Non-régression : le nouveau job est statique (aucun working-directory) → comme les 5 autres, il n'entre pas dans la matrice de régression ni le registre audit_4big (dérivés des seuls jobs de module). Totaux 534/21 inchangés. Suites harnais OK : qa/regression (21 suites · 534), qa/audit_4big (21/21 · min 100), qa/acceptance (bijectif=True). ci.yml YAML valide.

Anti-invention (#6) : rien inventé — le gate relit ci.yml et recalcule les ensembles à chaque run (aucun compteur figé).

Vérifs : 6 gates statiques verts (guard_constraints/validate_json/ check_docs/check_artifacts/check_regression/check_ci_integrity) ; doc ci/README.md mise à jour (tableau des jobs, section 2 détaillée, exécution locale, checklist « 6 gates »).

Hors périmètre worker (VPS · #8) : néant (stdlib en-repo ; enregistrement du runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.


Session 20260731_002924 · Buffer S8 · Intégrité de la matrice de régression (bug CI réel + invariant disque→CI)

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD) — roadmap fonctionnellement close (21/21 modules gated à 100/100, 534 tests verts, 5 gates statiques). Recherche d'une incohérence de reproductibilité réelle plutôt qu'un correctif doc cosmétique. Deux défauts réels trouvés et corrigés, tous deux dans qa/regression.

Défaut #1 (PRIORITAIRE · bug CI réel) — le gate check-regression était cassé en checkout propre

  • Symptôme : ci/check_regression.sh (l.52-53) exige que qa/regression/out/regression_run.json existe dans le dépôt, puis compare un run frais à ce fichier commité (baseline byte-identique).
  • Contradiction : ce même fichier était .gitignoreé (« non déterministe (temps machine) → jamais commitée ») → jamais présent dans un checkout propre. Le job Gitea check-regression ne fait que checkout + bash check_regression.sh, aucune étape ne génère le fichier. Conséquence : le gate échouait sur CHAQUE run CI propre (artefact absent du dépôt). Il ne « passait » qu'en local, par la présence d'un regression_run.json non suivi laissé par un run manuel. Le commentaire de ci.yml disait déjà « le regression_run.json commité » — intention claire du commit précédent, jamais concrétisée (fichier resté ignoré).
  • Vérif de faisabilité (#1 analyser) : l'artefact run est byte-déterministe — clés {ran,passed,failures,...} + path relatif, aucun horodatage/hôte/ durée. Deux run successifs byte-identiques (vérifié). Le committer est donc licite et complète l'intention du gate.
  • Fix : run est désormais commité (baseline du gate) →
    • .gitignore : retrait de out/regression_run.json + note expliquant qu'il est commité parce que déterministe ;
    • README.md : section run réécrite (byte-déterministe · commité · baseline check_regression · distinction d'avec check_artifacts qui ne rejoue que build) ;
    • out/regression_run.json : ajouté au suivi (21 suites · 534 tests · PASS).
  • Preuve de morsure : fichier retiré (simule checkout propre) ⇒ gate exit 1 (artefact absent) — l'état exact en CI ; restauré ⇒ exit 0 (run == commité).

Défaut #2 (invariant manquant) — dérive silencieuse disque→CI non gardée

  • Gap : discovery.coverage_report prouvait le sens CI→disque (tout module gated a un tests/) mais pas l'inverse : un module gagnant un tests/ sur disque sans job CI n'entrait jamais dans la matrice, ses tests ne tournaient jamais, et la couverture ne le voyait pas (elle n'itère que les suites dérivées du CI) — la matrice restait « PASS » en sous-comptant en silence. Même classe de dérive que « demo 18→21 », direction opposée.
  • Fix : nouvel invariant INV4
    • discovery.disk_test_modules() : marche disque pure listant tout module portant un tests/ (POSIX, trié, déterministe) ;
    • coverage_report : champ orphan_tests_dirs = disk ci_paths ; ok exige désormais not orphan_tests_dirs ;
    • regression.schema.json : orphan_tests_dirs ajouté (required · strict) ;
    • regression_gen.py : message INV4 surface les orphelins ;
    • tests/test_regression.py : +2 tests (sous-ensemble disque⊆CI sur le dépôt réel ; preuve de morsure monkeypatch d'un orphelin ⇒ ok=False).
  • Preuve de morsure end-to-end : mkdir zz_orphan/tests + buildbuild refusé INV4 … orphan_tests_dirs=['zz_orphan'] ; répertoire retiré ⇒ build OK.

Anti-invention (#6) : rien inventé — les deux gates régénèrent la vérité (run frais / couverture recomputée) au lieu de figer un compteur.

Régénération des consommateurs (mémoire projet) : l'édition des tests + du README de qa/regression a fait dériver qa/audit_4big/out/quality_report.json (evidence « README.md 3710→4236 octets » et « 24→26 méthodes test_* ») — régénéré, verdict PASS · 21/21 · min 100. Totaux matrice 534/21 inchangés : le harnais qa/regression est self-exclu (INV3) → +2 méthodes test_* n'affectent pas la matrice.

Vérifs : 5 gates locaux verts (guard_constraints/validate_json/check_docs/ check_artifacts/check_regression) ; ci.yml YAML valide ; tests harnais 26/26 OK (régression) + 34/34 OK (audit_4big) ; matrice 534/534 · 21/21 · PASS ; artefacts out/ reproductibles à l'identique.

Hors périmètre worker (VPS · #8) : néant (stdlib pur en-repo ; enregistrement du runner Gitea toujours du ressort DevOps).

Auto-score 4Big : 96/100.


Session 20260731_043004 · Buffer S8 · Chiffre périmé dans une fiche agent (vert-trompeur ungated) + extension du gate

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs ») — roadmap fonctionnellement close (22/22 modules gated · 558 tests · 7 gates statiques verts). Recherche d'un défaut réel (chiffre inventé/périmé · CLAUDE.md #6) plutôt qu'un correctif cosmétique.

Défaut trouvé — 03_agents/erpnext_backend/AGENT.md : « repo : 560 tests au total »

  • Symptôme : la ligne « source unique » situait les tests backend dans le repo par un compte agrégé saisi à la main (« repo : 560 tests au total »).
  • Contradiction : 560 ne correspond à AUCUN compte courant —
    • matrice de régression faisant autorité (qa/regression/out/regression_run.json) : 558 tests · 22 suites · PASS ;
    • repo-wide (méthodes test_* sur disque, 23 fichiers) : 584. 560 = ancien 534 (matrice d'alors) + 26 (harnais qa/regression, self-exclu INV3) — périmé en silence au fil de 534 → 551 → 558. Même classe de « vert trompeur » que la fiche QA (21/534) corrigée la session précédente, mais dans une fiche agent qu'aucun gate ne couvrait.
  • Vérif ciblée (#1 analyser) : recomputé toutes les autres bornes chiffrées des 13 AGENT.mdcrm 81=(25+31+25), RBAC 60=(10+16+11+11+12), e-CF 39, portails 19, chat 31, confotur 44, seo 36 : toutes exactes. Le 560 était le seul nombre dérivé.

Fix (2 volets · corriger + garder)

  1. 03_agents/erpnext_backend/AGENT.md : 560« matrice de régression du repo : 558 tests · 22 suites · verdict PASS, source qa/regression/out/regression_run.json — jamais compté à la main · #6 » (le nombre pointe désormais vers l'artefact gaté, pas une saisie).
  2. ci/check_readme_claims.sh : nouveau bloc qui recalcule tests+suites+ verdict depuis regression_run (même source que la fiche QA) et exige l'égalité avec ce que la fiche backend écrit ; un claim absent échoue aussi. Header du gate mis à jour (liste des docs couverts).

Preuves

  • Passe : Fiche Backend · … 558/22/PASS == regression_run (✓).
  • Morsure : 560 réinjecté ⇒ gate rouge (dit 560/22/PASS MAIS regression_run dit 558/22/PASS → régénérer la fiche) ; restauré ⇒ vert.
  • Régression nulle : édition d'un AGENT.md (doc) → aucune dérive d'artefact out/ (check_artifacts vert : quality_report.json inchangé).

Vérifs : 7 gates locaux verts (guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims) ; matrice 558/22 · PASS.

Anti-invention (#6) : rien inventé — le gate régénère la vérité (matrice re-agrégée) au lieu de figer un compteur ; le chiffre de la fiche découle de l'artefact.

Hors périmètre worker (VPS · #8) : néant (docs + gate bash/stdlib en-repo).

Auto-score 4Big : 96/100.


Session 20260731_060012 · Buffer S8 · Traçabilité NON vérifiée : les n° de ligne roadmap_line de la matrice d'acceptation pouvaient pointer à côté en silence

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs ») — roadmap fonctionnellement close (22/22 modules gated · 558 tests · 7 gates verts au démarrage). Recherche d'un défaut réel (traçabilité CLAUDE.md #6) plutôt qu'un cosmétique.

Défaut trouvé — qa/acceptance : roadmap_line déclaré, jamais ancré

  • Symptôme : chaque promesse de acceptance_spec.json (8 livrables S1..S8 + 7 métriques M1..M7) porte un roadmap_line (33, 39, … 87) censé pointer son bullet dans 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md. Le docstring promet des « CITATIONS verbatim de la roadmap » (#6), mais le seul invariant (INV10) ne vérifiait que « roadmap_line est un entier positif ».
  • Contradiction : si la roadmap est éditée (une ligne insérée/retirée, un sprint ré-ordonné), tous les roadmap_line se décalent en silence — ils pointeraient un bullet qui n'existe plus, tout en restant « vert ». Même classe de « vert trompeur » que les compteurs périmés (534→551→558) ou l'orphan_tests_dirs, appliquée cette fois à une référence de ligne (traçabilité déclarée mais non prouvée).
  • Vérif (#1 analyser) : recomputé la structure réelle → 8 bullets **DELIVERABLE** (S1@33 … S8@76) + 7 bullets métrique - ✅ (81..87). Les roadmap_line du spec sont exacts aujourd'hui — la faille est l'absence de garde, pas une valeur fausse.

Fix (2 volets · vérifier + garder)

  1. acclib/deps.py — nouveau parse_roadmap_anchors(roadmap_ref) qui DÉRIVE la structure de la roadmap (jamais une liste de n° saisie · #6) : {SX → ligne du DELIVERABLE} (SX lu dans le titre ## SPRINT n · SX (…) + lignes ordonnées des bullets métrique. OSError honnête si la roadmap est absente (référence fantôme = red).
  2. acceptance_gen.py · INV11 (ancrage roadmap) — exige que (a) le nombre de bullets DELIVERABLE == nb de livrables du spec et nb de bullets métrique == nb de métriques (le « 8 + 7 » devient dérivé du fichier, plus un compte figé), (b) chaque livrable SX pointe le DELIVERABLE de SX, (c) chaque métrique (dans l'ordre) pointe le k-ième bullet - ✅. Docstring #6 mis à jour (« n° de ligne VÉRIFIÉ, pas déclaré »).
  3. Tests — fixture roadmap synthétique (temp file, mêmes motifs en miniature : S2@L3, S3@L5, M1@L7) pour contrôler l'écart injecté ; classe TestRoadmapAnchor (+6 méthodes) : parse réel (8+7), DELIVERABLE désancré, métrique décalée, mauvais propriétaire de sprint, compte en trop, roadmap absente. 37 méthodes (31→37).

Preuves

  • Passe : build/validate réel vert ; parse_roadmap_anchors réel = {S1:33 … S8:76} + métriques [81..87] ; les 15 roadmap_line du spec matchent.
  • Morsure (spec RÉEL) : S1.roadmap_line=999roadmap: S1: … ne pointe pas le DELIVERABLE de S1 (ligne 33) ; M3=200… ≠ 3e bullet métrique (ligne 83).

Régénération des consommateurs (mémoire projet) : +6 méthodes test dans qa/acceptance → matrice 558→564 (22 suites inchangées). Régénérés : qa/regression/out/{regression_run,regression_plan,MANIFEST}.json (564/564 · PASS), qa/audit_4big/out/quality_report.json (evidence acceptance « 31→37 méthodes », score 100/100 inchangé), fiches QA et Backend (558→564, gate check_readme_claims vert), README acceptance (« 10→11 invariants »). L'artefact acceptance_matrix.json est byte-identique (INV11 ne change que la validation, pas la sortie).

Vérifs : 7 gates locaux verts (guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims) ; matrice 564/22 · PASS ; 22/22 modules 100/100.

Anti-invention (#6) : rien inventé — INV11 dérive la vérité de la roadmap et compare ; le « 8 + 7 » cesse d'être un compte à la main.

Hors périmètre worker (VPS · #8) : néant (stdlib pur en-repo).

Auto-score 4Big : 96/100.


Session 20260731_063014 · Buffer S8 · Comptes de tests PAR SUITE périmés dans la fiche QA — dérive silencieuse (aucun gate ne couvrait le grain fin)

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs ») — roadmap fonctionnellement close (22/22 modules gated · 564 tests · 7 gates verts au démarrage). Chasse d'un défaut réel de la classe « vert trompeur » (#6) plutôt qu'un cosmétique.

Défaut trouvé — fiche QA : colonne « Tests » par suite figée, DÉJÀ fausse

  • Symptôme : le tableau des livrables de 03_agents/qa/AGENT.md (col « Tests ») affichait un compte par suite saisi à la main. Trois lignes étaient déjà périmées au démarrage : audit_4big 35 (réel 34), regression 25 (réel 26), acceptance 31 (réel 37 — la session précédente avait fait 31→37 sans propager à cette table).
  • Contradiction : le gate amont check_readme_claims ne couvrait que la somme agrégée (« 564 tests »). Une compensation entre deux suites (+1 / 1) laisserait la somme juste et les deux lignes fausses — même classe de « vert trompeur » que les compteurs agrégés périmés (534→551→558→564) ou INV11, mais au grain le plus fin (par suite), et non gardée.
  • Vérif systématique (#1 analyser) : recomputé la carte faisant autorité des 22 suites gated + le self_module → diffusée contre toutes les tables de fiches (03_agents/*/AGENT.md). Seule la fiche QA dérivait (3 lignes) ; CRM (3), faisabilité (2), publiciste (1) étaient justes. READMEs de module : justes aussi.

Fix (2 volets · corriger + garder)

  1. 03_agents/qa/AGENT.md — 3 nombres corrigés vers le réel : 35→34, 25→26, 31→37.
  2. ci/check_readme_claims.sh — nouveau bloc générique : scanne toutes les fiches, extrait chaque ligne de table pointant 05_deliverables_mvp/<path>/README.md
    • sa cellule « Tests », et recompute l'attendu depuis la source faisant autorité — regression_plan.json (suites[path].test_methods) pour les 22 suites gated, et reglib.discovery.count_tests (même fonction que le plan · zéro duplication · #5) pour le self_module qa/regression exclu de la matrice par SoD. Table « Tests » absente ⇒ red (disparition = régression de traçabilité).

Preuves

  • Passe : gate vert, 10 lignes par-suite vérifiées (crm×3 · faisabilite×2 · publiciste×1 · qa×4), toutes == source.
  • Morsure : acceptance reforcé à 31 ⇒ ✗ … Tests 31 MAIS source dit 37 (plan.suites/count_tests) → régénérer la fiche + verdict ; restauration ⇒ PASS.

Régénération des consommateurs : néant — aucun compte de test n'a changé (fix = docs + gate). Artefacts out/ byte-identiques (check_artifacts vert) ; matrice 564/22 PASS inchangée.

Vérifs : 7 gates locaux verts (guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims) ; matrice 564/22 · PASS ; 22/22 modules 100/100.

Anti-invention (#6) : rien inventé — le gate recompute chaque cellule depuis plan.suites / count_tests ; les nombres de la fiche découlent de la source.

Hors périmètre worker (VPS · #8) : néant (docs + gate bash/stdlib en-repo).

Auto-score 4Big : 96/100.


Session 20260731_083034 · Buffer S8 · Comptes RBAC des 3 volets recopiés à la main — surface non gatée

Tâche : Sprint 8 · buffer L75 (« Regression tests exhaustifs ») — roadmap fonctionnellement close (22/22 modules gated · 564 tests · 7 gates verts au démarrage). Chasse d'une surface « vert trompeur » (#6) encore non gatée.

Défaut trouvé — 3 READMEs RBAC transcrivent à la main des comptes de MANIFEST byte-gatés

  • Symptôme : les trois générateurs RBAC émettent chacun un out/MANIFEST.json recalculé du contrat rbac_50_roles.json à chaque build (byte-gaté par check_artifacts ⇒ autorité). Trois READMEs de module recopiaient à la main des comptes qui en dérivent, sans AUCUN gate :
    1. rbac/roleprofile_gen/README.md — table « Nb rôles » par profil (6 lignes
      Ventes 12 · Construction 10 · Direction 9 · Compta 8 · Achat 5 · Plateforme
        • total bijectif 50.
    2. rbac/apply_plan/README.md — ligne agrégée « Génération réelle : 6 étapes · 50 rôles / 116 DocPerm / 28 UP templates / 6 Role Profile · 4 DocType custom + 5 Company + 4 rôles equipe » : 8 comptes de TOUS les volets en une phrase.
    3. rbac/userperm_gen/README.md — ventilation « 28 entite · 16 groupe · 2 own · 4 equipe » (par mécanisme d'enforcement).
  • Contradiction latente : ajouter un rôle/DocType au contrat ferait dériver les trois docs en silence pendant que les MANIFEST (byte-gatés) disent autre chose — même classe que les cellules « Tests », la prose agrégée CRM/RBAC et les triplets par workspace, mais sur le domaine RBAC (contrainte NON-NÉGOCIABLE #5 « 50 rôles »), au grain le plus fin (par profil) et au niveau consolidé.
  • État au démarrage : les 10 nombres étaient justes (aucune dérive déjà survenue) — le défaut est l'absence de garde, pas une valeur périmée.

Fix (1 volet · garder — aucun chiffre à corriger)

  • ci/check_readme_claims.sh — nouveau bloc RBAC recomputant chaque nombre depuis le out/MANIFEST.json du volet (zéro duplication des compteurs des générateurs · #6) et exigeant l'égalité avec ce que la doc ÉCRIT :
    • roleprofile : chaque nb_roles par profil (ancré sur nom+portail) + total counts.roles_couverts ;
    • apply_plan : les 8 comptes (len(apply_plan.json) · counts.{roles, custom_docperm,user_permission_templates,role_profiles} · len(confirmations_ vps.{custom_doctypes,companies,roles_scope_equipe})) en un n-uplet ordonné ;
    • userperm : by_mechanism.{user_permission_company,none_consolidated, docperm_if_owner,vps_confirm_team}. Claim absent ⇒ red (disparition = régression de traçabilité). Header + ci/README documentés.

Preuves

  • Passe : gate vert, 10 nouvelles lignes RBAC vérifiées (6 profils + total
    • agrégat apply_plan + ventilation userperm), toutes == MANIFEST.
  • Morsures (3, une par surface) : Ventes 12→13 ⇒ ✗ … dit 13 MAIS MANIFEST dit 12 ; apply_plan 116→117 DocPerm ⇒ ✗ … 6/50/117/… MAIS … 6/50/116/… ; userperm 28→29 entite ⇒ ✗ … 29/16/2/4 MAIS … 28/16/2/4. Exit 1 dans les trois cas ; restauration ⇒ PASS (exit 0).

Régénération des consommateurs : néant — aucun compte n'a changé (fix = gate seul). Artefacts out/ byte-identiques (check_artifacts vert) ; matrice 564/22 PASS inchangée.

Vérifs : 7 gates locaux verts (guard_constraints · validate_json · check_docs · check_artifacts · check_regression · check_ci_integrity · check_readme_claims) ; matrice 564/22 · PASS ; 22/22 modules 100/100.

Anti-invention (#6) : rien inventé — le gate recompute chaque nombre depuis les MANIFEST byte-gatés ; les comptes des READMEs découlent du contrat RBAC.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Sprint 8 · buffer L75 · Gate claims mobile — récap app Expo non gatée (onglets/rôles/langues/identifiants)

Diagnostic

  • Symptôme : le générateur mobile/app_config émet out/MANIFEST.json dont counts porte 4 nombres recomptés à chaque build depuis les sources faisant autorité — rbac_50_roles.json (portails métier + rôles → onglets, roles_couverts), seo/seo_spec.json (langues) et la liste a_confirmer (credentials EAS/App Store #32/Play Store jamais fabriqués · #6/#8 → identifiants_a_confirmer). Le out/ est byte-gaté par check_artifacts (build frais == commité) ⇒ artefact faisant autorité.
  • Contradiction latente : la ligne récap du README du module (05_deliverables_mvp/mobile/app_config/README.md L66-67 — « 5 onglets · 44 rôles couverts · 3 langues · 13 identifiants a_confirmer ») recopiait ce quadruplet à la main, sans AUCUN gate. Ajouter un rôle/portail au contrat RBAC, une langue au site, ou un credential store ferait dériver le doc en silence pendant que le MANIFEST (byte-gaté) dit autre chose — même classe de « vert trompeur » que les mots-clés SEO (total + fr/en/es), les triplets par workspace des portails, ou le pipeline vente CRM.
  • État au démarrage : les 4 nombres étaient justes (5/44/3/13 == MANIFEST) — le défaut est l'absence de garde, pas une valeur périmée.

Fix (1 fichier gate + 1 doc CI · aucun chiffre à corriger)

  • ci/check_readme_claims.sh — nouveau bloc Mobile recomputant le quadruplet (onglets, roles_couverts, langues, identifiants_a_confirmer) depuis mobile/app_config/out/MANIFEST.json · counts (zéro duplication du compteur du générateur · #6) et exigeant l'égalité avec ce que le README ÉCRIT. Recomputer les 4 volets (et pas le seul total de rôles) mord une dérive de N'IMPORTE lequel là où un seul nombre resterait aveugle. Un claim absent échoue AUSSI (INTROUVABLE = régression de traçabilité).
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « app mobile Expo »).

Preuve (4 morsures vérifiées + restauration verte)

Injection Résultat
onglets 5→6 ✗ dit 6/44/3/13 MAIS MANIFEST 5/44/3/13 · exit 1
rôles 44→43 ✗ dit 5/43/3/13 MAIS MANIFEST 5/44/3/13 · exit 1
langues 3→2 ✗ dit 5/44/2/13 MAIS MANIFEST 5/44/3/13 · exit 1
identifiants 13→12 ✗ dit 5/44/3/12 MAIS MANIFEST 5/44/3/13 · exit 1
claim supprimé ✗ récap app INTROUVABLE · exit 1
restauré ✓ 5/44/3/13 == MANIFEST · exit 0

Suite CI complète re-verte : check_artifacts · check_readme_claims · check_docs · check_regression · check_ci_integrity (gates statiques verrouillés) · guard_constraints.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.


Sprint 8 · buffer L75 · Gate chiffres audit 5D (contrôles/dimensions ×2 docs + ventilation verdict)

Contexte : poursuite de la fermeture des surfaces de « vert trompeur » non gatées dans check_readme_claims.sh. Après RBAC, SEO, CRM pipeline, Mobile et Legal CONFOTUR, restait la synthèse de l'audit de conformité 5D (livrable qa/audit_5d), transcrite À LA MAIN dans DEUX docs sans aucun gate.

Dérive silencieuse fermée :

  • Le README du module (05_deliverables_mvp/qa/audit_5d/README.md) ouvre par « 17 contrôles répartis en 5 dimensions (5D) » ET affiche la ventilation du verdict « 13 PASS · 0 FAIL · 4 à confirmer ».
  • La fiche agent QA (03_agents/qa/AGENT.md) recopie le même couple « 17 contrôles / 5 dimensions ».
  • Les DEUX artefacts source sont byte-gatés par check_artifacts (recomptés du catalogue de contrôles à chaque build) : out/MANIFEST.json (controls_total, dimensions) ET out/audit_report.json (totals.{pass,fail,a_confirmer}).
  • Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter un contrôle/une dimension au catalogue, ou faire basculer un contrôle en A_CONFIRMER, ferait dériver les deux docs (et la ligne verdict) en silence.

Nouveau bloc dans ci/check_readme_claims.sh : recompute le couple (contrôles + dimensions) depuis le MANIFEST et la ventilation (pass/fail/à-confirmer) depuis audit_report.totalsjamais figés en dur ici, zéro duplication du compteur du générateur (#6) — et exige l'égalité avec les deux docs. La ventilation est recomputée SÉPARÉMENT car une bascule (pass 13→12, à confirmer 4→5) est aveugle au seul total de contrôles (reste 17). Cohérence croisée en bonus : MANIFEST.controls_total == audit_report.totals.controls.

Preuve (4 morsures vérifiées + restauration verte)

Injection Résultat
README contrôles 17→18 ✗ dit 18/5 MAIS artefact 17/5 · exit 1
README verdict 13 PASS→12 / 4→5 à confirmer (bascule aveugle au total) ✗ dit 12/0/5 MAIS totals 13/0/4 · exit 1
fiche QA dimensions 5→6 ✗ dit 17/6 MAIS artefact 17/5 · exit 1
claim README verdict supprimé (reformulé) ✗ ventilation verdict INTROUVABLE · exit 1
restauré ✓ 17/5 + 13/0/4 == artefacts · exit 0
  • ci/README.md — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « audit 5D »).

Suite CI complète re-verte : check_artifacts · check_readme_claims · check_docs · check_regression · check_ci_integrity (gates statiques verrouillés) · guard_constraints · validate_json.

Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).

Auto-score 4Big : 96/100.

Sprint 8 · buffer · CRM séparation des pouvoirs (2e surface workflow_vente)

  • Constat : le README crm/workflow_vente énumérait À LA MAIN les transitions quatre-yeux (« confirmer réservation, signer contrat, approuver CONFOTUR, annuler/résilier — allow_self_approval = 0 »). Le bloc CRM existant ne gatait que le COMPTE états/transitions (9/11) — aveugle à l'IDENTITÉ des transitions à separation_of_duties.
  • Gate ajouté (ci/check_readme_claims.sh) : recompute l'ENSEMBLE {action | allow_self_approval == 0} depuis out/workflow.json (byte-gaté) et exige que la phrase README l'énumère EXACTEMENT (set-diff absent ET en trop ; casse ignorée + suffixe « (perdu) » élidé). Bonus : 0 < |quatre-yeux| < |trans|.
  • 4 morsures vérifiées : déclassement Signer contrat (self 0→1) capté là où le compte reste 9/11 · promotion Déposer CONFOTUR (self 1→0) captée · échange prose signer contratsigner bail capté · énumération supprimée = INTROUVABLE ; restauré = green ({annuler, approuver confotur, confirmer réservation, résilier, signer contrat} == workflow.json).
  • État courant : aucune action périmée (anti-invention #6) — le défaut est la surface ungated. ci/README.md (table + détail) mis à jour · 7 gates re-verts.

Sprint 8 · buffer · Portails (2e surface) · catalogue DocTypes custom à créer

  • Constat : le README frontend/portails énumère À LA MAIN, dans le §« Hand-off → agent ERPNext Backend », les DocTypes custom à créer avant import : CONFOTUR Application, Faisabilité, Publiciste Log. Ce catalogue est DATA-DERIVED == doctypes_custom_a_creer d'out/MANIFEST.json (byte-gaté par check_artifacts), lui-même l'UNION des doctypes_custom par workspace (flag custom du contrat RBAC, pour les seuls DocTypes CÂBLÉS dans un portail). C'est un ensemble PROPRE au module (sous-ensemble de celui de rbac/fixtures_gen : API Access, custom mais non câblé, en est ABSENT). Le bloc portails existant ne gatait que les COMPTES par workspace (cartes/liens/rôles) — jamais ce catalogue.
  • Piège : check_artifacts ne prouve que MANIFEST==build (byte-for-byte) et le build RECOMPUTE l'union → AJOUTER une carte visant un nouveau DocType custom · en RENOMMER un · en RETIRER un du câblage laisse la consigne de hand-off périmée pendant que l'artefact dit autre chose. L'agent Backend créerait le mauvais jeu de DocTypes → liens pointant dans le vide (le risque même que la consigne veut prévenir) — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la prose de hand-off) n'attrape.
  • Gate ajouté (ci/check_readme_claims.sh) : recompute le catalogue depuis MANIFEST.doctypes_custom_a_creer (zéro duplication du contrat du générateur #6) et exige que la prose l'énumère EXACTEMENT (absent ET en trop via set-diff · accents/casse normalisés via unicodedata). Cohérences croisées en bonus (mordent un MANIFEST INTERNEMENT incohérent) : catalogue NON VIDE, sans doublon et TRIÉ (byte-déterminisme) · égal à l'UNION des doctypes_custom par workspace (pas de DocType custom câblé mais oublié du hand-off, ni l'inverse). Un claim absent échoue AUSSI.
  • 5 morsures vérifiées : prose renomme Faisabilité → Faisabilite2 (absents=[faisabilite] en trop=[faisabilite2]) · prose retire Publiciste Log (sous-ensemble) · énumération supprimée = INTROUVABLE · MANIFEST retire du catalogue un DocType encore câblé (câblés hors catalogue=[Publiciste Log]) · catalogue MANIFEST non trié (byte-déterminisme) ; restauré = green ([CONFOTUR Application, Faisabilité, Publiciste Log] == MANIFEST == union).
  • État courant : aucun catalogue périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail) mis à jour · 7 gates re-verts (check_readme_claims · check_artifacts · check_docs · check_ci_integrity · check_regression · guard_constraints · validate_json).
  • Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo).
  • Auto-score 4Big : 96/100.