diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index 45ecce3..b1dd7cf 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,73 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## 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 `| | \`\` | C | L | R |`) **et** la cellule +de prose de la fiche (`\`\` C cartes/L liens/R rôles`, forme abrégée `C/L/R` +tolérée). + contrôle du **total** « N `Workspace` natifs » == 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 diff --git a/ci/README.md b/ci/README.md index 872cf46..456aa5b 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) == 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) == 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 @@ -154,6 +154,15 @@ Le **total ET le multiset des composants** (ordre-indépendant) sont recomputés table (gatée ci-dessus) juste et la prose fausse — c'est le même piège que les cellules, au niveau agrégé. +Même classe encore, hors du domaine « tests » : les comptes **par workspace** +(cartes / liens / rôles) des portails frontend, transcrits **à la main** dans +**deux** docs — la table du README du module (`frontend/portails/README.md`) **et** +la prose de la fiche agent (`03_agents/frontend_console/AGENT.md` : « `OTO Ventes` +4 cartes/11 liens/12 rôles · … »). Chaque triplet **et** le total « N `Workspace` +natifs » sont recomputés depuis `frontend/portails/out/MANIFEST.json` (tableau +`workspaces`, lui-même byte-gaté par `check_artifacts`) : ajouter un rôle/DocType +au contrat RBAC ferait dériver **les deux** docs en silence sinon. + 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 4d2706f..19c6ac7 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -42,6 +42,11 @@ # • « Total CRM N (a+b+c) » · « RBAC N (…) » · « e-CF N » — total ET multiset # des composants recomputés (une compensation entre suites laisserait la # table juste et la prose fausse) → plan.suites[path] +# frontend/portails/README.md + 03_agents/frontend_console/AGENT.md : +# • comptes PAR WORKSPACE (cartes / liens / rôles) transcrits À LA MAIN dans +# DEUX docs (table du README + prose « OTO Ventes 4 cartes/11 liens/12 +# rôles ») + total « N Workspace natifs » → portails MANIFEST +# (tableau `workspaces`, byte-gaté par check_artifacts) # 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é). # @@ -384,6 +389,78 @@ else: bad(f"Prose agrégée · e-CF — dit {mecf.group(1)} MAIS source dit {ecf_exp} " f"(fiscal/ecf_dgii) → régénérer la fiche") +# ============================================================================ +# Portails frontend · comptes PAR WORKSPACE (cartes / liens / rôles) — +# même classe de « vert trompeur », transcrits À LA MAIN dans DEUX docs. +# ---------------------------------------------------------------------------- +# Le générateur de portails émet out/MANIFEST.json dont le tableau `workspaces` +# porte, PAR portail, {nb_cartes, nb_liens, nb_roles} recalculés depuis le +# contrat RBAC à chaque build (byte-gaté par check_artifacts). Ces triplets sont +# recopiés À LA MAIN à DEUX endroits : la table du README du module +# (frontend/portails/README.md, colonnes Cartes|Liens|Rôles) ET la prose de la +# fiche agent (03_agents/frontend_console/AGENT.md — « OTO Ventes 4 cartes/11 +# liens/12 rôles · … »). Aucun gate ne les couvrait : ajouter un rôle/DocType au +# contrat ferait dériver LES DEUX docs en silence pendant que l'artefact dit +# autre chose — même piège que les cellules « Tests » et la prose agrégée. On +# RECOMPUTE ici chaque triplet depuis MANIFEST (déjà byte-gaté · zéro duplication +# du compteur du générateur) et on exige l'égalité avec les DEUX docs. Un +# workspace absent d'un doc échoue AUSSI (traçabilité). +# ============================================================================ +WS_RD = f"{D}/frontend/portails/README.md" +WS_FI = "03_agents/frontend_console/AGENT.md" +try: + ws_list = load("frontend/portails/out/MANIFEST.json")["workspaces"] +except (OSError, KeyError, ValueError) as e: + bad(f"Portails · MANIFEST illisible/inattendu : {e}"); ws_list = [] +try: + ws_rd = re.sub(r"\s+", " ", open(WS_RD, encoding="utf-8").read()) +except OSError as e: + bad(f"Portails · {WS_RD} illisible : {e}"); ws_rd = "" +try: + ws_fi = re.sub(r"\s+", " ", open(WS_FI, encoding="utf-8").read()) +except OSError as e: + bad(f"Portails · {WS_FI} illisible : {e}"); ws_fi = "" + +for w in ws_list: + portail, ws = w["portail"], w["workspace"] + exp = (str(w["nb_cartes"]), str(w["nb_liens"]), str(w["nb_roles"])) + src = f"cartes={exp[0]} liens={exp[1]} rôles={exp[2]}" + # 1) Table du README du module : | | `` | C | L | R | + rd_pat = (rf"\| {re.escape(portail)} \| `{re.escape(ws)}` \| " + r"(\d+) \| (\d+) \| (\d+) \|") + mrd = re.search(rd_pat, ws_rd) + if mrd is None: + bad(f"Portails README · {ws} — ligne INTROUVABLE dans {WS_RD} (attendu {src})") + elif mrd.groups() == exp: + good(f"Portails README · {ws} — {'/'.join(mrd.groups())} == MANIFEST ({src})") + else: + bad(f"Portails README · {ws} — {WS_RD} dit {'/'.join(mrd.groups())} MAIS " + f"MANIFEST dit {'/'.join(exp)} ({src}) → régénérer le README") + # 2) Prose de la fiche agent : `` C cartes/L liens/R rôles (ou C/L/R) + fi_pat = (rf"`{re.escape(ws)}` (\d+)(?: cartes)?/(\d+)(?: liens)?/" + r"(\d+)(?: rôles)?") + mfi = re.search(fi_pat, ws_fi) + if mfi is None: + bad(f"Portails fiche · {ws} — claim INTROUVABLE dans {WS_FI} (attendu {src})") + elif mfi.groups() == exp: + good(f"Portails fiche · {ws} — {'/'.join(mfi.groups())} == MANIFEST ({src})") + else: + bad(f"Portails fiche · {ws} — {WS_FI} dit {'/'.join(mfi.groups())} MAIS " + f"MANIFEST dit {'/'.join(exp)} ({src}) → régénérer la fiche") + +# Total de workspaces (« N `Workspace` natifs ») cohérent artefact ↔ fiche. +n_ws = len(ws_list) +if n_ws: + mtot = re.search(r"\*\*(\d+) `Workspace` natifs", ws_fi) + if mtot is None: + bad(f"Portails fiche · total — « N `Workspace` natifs » INTROUVABLE " + f"dans {WS_FI} (attendu {n_ws})") + elif int(mtot.group(1)) == n_ws: + good(f"Portails fiche · total — {mtot.group(1)} == MANIFEST ({n_ws} workspaces)") + else: + bad(f"Portails fiche · total — {WS_FI} dit {mtot.group(1)} MAIS " + f"MANIFEST dit {n_ws} → régénérer la fiche") + sys.exit(1 if FAIL else 0) PY rc=$?