diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index 7f61987..ffea241 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,68 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## 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 diff --git a/ci/README.md b/ci/README.md index 20d42ea..498c4bb 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa | `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui | | `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui | | `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui | -| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) == artefact cité (#6) | ✅ oui | +| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec @@ -258,6 +258,22 @@ dernière citation d'un module (9), ferait dériver le diagramme en silence. Coh croisée en bonus : `counts.modules_cites_uniques` == |`modules_cites`| (liste dédupliquée du manifeste) — mord un compteur désynchronisé de sa propre liste. +Même classe enfin sur le **montage Chat OTOIA par portail** (livrable +`frontend/chat_otoia`) : le nombre de **`Custom Block`**, de **configs runtime** et +de **portails** couverts, transcrits **à la main** dans DEUX docs (fiche agent +`frontend_console` — « **N `Custom Block` + M configs runtime** » · README du +module — « Portails (P, `plateforme` exclu) », « N fixtures `Custom Block` », « M +configs runtime »), pointaient l'artefact sans qu'aucun gate ne les recompute. Ces +trois nombres valent **tous 5 aujourd'hui** — un gate qui n'en recompterait qu'un +serait **aveugle** à la dérive des autres (ajouter un portail au contrat RBAC → +chat_otoia rebâtit 6 blocks ⁄ 6 mounts ⁄ 6 portails pendant que les docs restent à +5). Chacun est recomputé depuis **son propre** champ de +`frontend/chat_otoia/out/MANIFEST.json` (`counts.custom_blocks` · `counts.mounts` · +`counts.portails`, byte-gaté par `check_artifacts`) et comparé aux docs. Cohérence +croisée : `custom_blocks` == `mounts` == `portails` == |`MANIFEST.portails`| (la +bijection 1 block ⇔ 1 mount ⇔ 1 portail du générateur) — mord un compteur +désynchronisé de sa propre liste. + Un claim **absent** échoue aussi (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur (bash/git/python3), zéro réseau. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index a43a1db..c781e4f 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -72,6 +72,13 @@ # de dépôt » transcrit À LA MAIN dans DEUX docs → confotur # MANIFEST.counts (fields/data_fields/sections/roles/depot_events, recomptés # de confotur_spec.json, byte-gatés) +# frontend/chat_otoia/README.md + 03_agents/frontend_console/AGENT.md (montage +# du Chat OTOIA par portail) : +# • triplet « N `Custom Block` · M configs runtime · P portails » (tous = 5 +# aujourd'hui — piège aveugle) transcrit À LA MAIN dans DEUX docs → chat_otoia +# MANIFEST.counts (custom_blocks/mounts/portails, recomptés du contrat RBAC, +# byte-gatés) + cohérence croisée == |MANIFEST.portails| (bijection 1 block ⇔ +# 1 mount ⇔ 1 portail) # Un claim absent échoue AUSSI (la dérive de formulation qui ferait disparaître # un chiffre est elle-même une régression de traçabilité). # @@ -1028,6 +1035,96 @@ if demo_n is not None: bad(f"Démo · {DEMO_RD} — dit {m.group(1)} modules cités MAIS artefact dit " f"{demo_n} → régénérer le README") +# ============================================================================ +# Chat OTOIA · montage de l'assistant conversationnel par portail (nombre de +# Custom Block / configs runtime / portails) — même classe de « vert trompeur », +# transcrit À LA MAIN dans DEUX docs, sans AUCUN gate. +# ---------------------------------------------------------------------------- +# Le générateur chat_otoia_gen émet out/MANIFEST.json (byte-gaté par +# check_artifacts ⇒ faisant autorité) dont `counts.custom_blocks`, `counts.mounts` +# et `counts.portails` sont RECOMPTÉS à chaque build depuis le contrat RBAC (un +# `Custom Block` ⇔ une config runtime ⇔ un portail métier, `plateforme` exclu — +# invariant 2 du générateur : bijection 1 block ⇔ 1 mount ⇔ 1 portail). Ces +# nombres sont recopiés À LA MAIN : la fiche agent Frontend Console +# (03_agents/frontend_console/AGENT.md — « **N `Custom Block` + M configs +# runtime** ») ET le README du module (05_deliverables_mvp/frontend/chat_otoia/ +# README.md — « Portails (N, `plateforme` exclu) », « N fixtures `Custom Block` », +# « M configs runtime »). Piège « aveugle » caractéristique (cf. DevOps phases ⁄ +# confirmations, bascule verdict audit_5d) : 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 autres (ajouter un portail au contrat RBAC → chat_otoia +# rebâtit 6 blocks ⁄ 6 mounts ⁄ 6 portails pendant que les docs restent à 5). On +# RECOMPUTE donc CHAQUE nombre depuis SON PROPRE champ du MANIFEST (jamais figé +# ici · zéro duplication du compteur du générateur #5) et on exige l'égalité avec +# les docs. Cohérence croisée : counts.custom_blocks == counts.mounts == +# counts.portails == |MANIFEST.portails| (la bijection du générateur) — mord un +# compteur désynchronisé de sa propre liste. Un claim absent échoue AUSSI (la +# disparition d'un chiffre est une régression · traçabilité). +# ============================================================================ +CO_RD = f"{D}/frontend/chat_otoia/README.md" +CO_FI = "03_agents/frontend_console/AGENT.md" +try: + co_man = load("frontend/chat_otoia/out/MANIFEST.json") + co_cb = str(co_man["counts"]["custom_blocks"]) + co_mo = str(co_man["counts"]["mounts"]) + co_po = str(co_man["counts"]["portails"]) + co_nlist = len(co_man["portails"]) + if not (co_cb == co_mo == co_po == str(co_nlist)): + bad(f"Chat OTOIA · incohérence artefact : custom_blocks={co_cb} · " + f"mounts={co_mo} · portails={co_po} · |portails|={co_nlist} " + f"(bijection 1 block ⇔ 1 mount ⇔ 1 portail attendue)") +except (OSError, KeyError, ValueError) as e: + bad(f"Chat OTOIA · artefact chat_otoia illisible/inattendu : {e}"); co_cb = None +if co_cb is not None: + # 1) Fiche agent Frontend Console — « N `Custom Block` + M configs runtime » + co_fi = read_norm(CO_FI) + if co_fi is not None: + m = re.search(r"\*\*(\d+) `Custom Block` \+ (\d+) configs runtime\*\*", co_fi) + if m is None: + bad(f"Chat OTOIA · {CO_FI} — claim « N `Custom Block` + M configs runtime » " + f"INTROUVABLE (attendu {co_cb}/{co_mo})") + elif m.groups() == (co_cb, co_mo): + good(f"Chat OTOIA · {CO_FI} — {'/'.join(m.groups())} == artefact " + f"(custom_blocks={co_cb} · mounts={co_mo})") + else: + bad(f"Chat OTOIA · {CO_FI} — dit {'/'.join(m.groups())} MAIS artefact dit " + f"{co_cb}/{co_mo} → régénérer la fiche") + # 2) README module — « Portails (N, `plateforme` exclu) » + co_rd = read_norm(CO_RD) + if co_rd is not None: + m = re.search(r"Portails \((\d+), `plateforme` exclu\)", co_rd) + if m is None: + bad(f"Chat OTOIA · {CO_RD} — claim « Portails (N, `plateforme` exclu) » " + f"INTROUVABLE (attendu portails={co_po})") + elif m.group(1) == co_po: + good(f"Chat OTOIA · {CO_RD} — {m.group(1)} portails == artefact " + f"(portails={co_po})") + else: + bad(f"Chat OTOIA · {CO_RD} — dit {m.group(1)} portails MAIS artefact dit " + f"{co_po} → régénérer le README") + # 3) README module — « N fixtures `Custom Block` » + m = re.search(r"(\d+) fixtures `Custom Block`", co_rd) + if m is None: + bad(f"Chat OTOIA · {CO_RD} — claim « N fixtures `Custom Block` » INTROUVABLE " + f"(attendu custom_blocks={co_cb})") + elif m.group(1) == co_cb: + good(f"Chat OTOIA · {CO_RD} — {m.group(1)} fixtures == artefact " + f"(custom_blocks={co_cb})") + else: + bad(f"Chat OTOIA · {CO_RD} — dit {m.group(1)} fixtures MAIS artefact dit " + f"{co_cb} → régénérer le README") + # 4) README module — « M configs runtime » (2e transcription des mounts) + m = re.search(r"(\d+) configs runtime", co_rd) + if m is None: + bad(f"Chat OTOIA · {CO_RD} — claim « M configs runtime » INTROUVABLE " + f"(attendu mounts={co_mo})") + elif m.group(1) == co_mo: + good(f"Chat OTOIA · {CO_RD} — {m.group(1)} configs runtime == artefact " + f"(mounts={co_mo})") + else: + bad(f"Chat OTOIA · {CO_RD} — dit {m.group(1)} configs runtime MAIS artefact " + f"dit {co_mo} → régénérer le README") + sys.exit(1 if FAIL else 0) PY rc=$?