[DTP-Worker] Sprint 8 · buffer L75 · Domaine SEO : le total ET la ventilation par langue des mots-clés (« 258 (fr=87·en=87·es=84) ») étaient transcrits À LA MAIN dans DEUX docs (table du README module seo + ligne de la fiche agent seo) sans AUCUN gate — ajouter un projet/vocabulaire ferait dériver les deux docs en silence pendant que seo/out/MANIFEST.json (byte-gaté par check_artifacts, counts recomptés de projets_master.json) dit autre chose → nouveau bloc dans check_readme_claims recomputant le quadruplet (total + fr/en/es) depuis MANIFEST.counts et exigeant l'égalité avec les deux docs (3 morsures : dérive total · compensation entre langues captée là où le total seul est aveugle · claim disparu)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,65 @@
|
|||||||
# Activity Log · 2026-07-31 · Claude Code DTP
|
# Activity Log · 2026-07-31 · Claude Code DTP
|
||||||
|
|
||||||
|
## 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.json` — **258** 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
|
## 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
|
**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap
|
||||||
|
|||||||
+13
-1
@@ -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-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-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-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) == 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) == artefact cité (#6) | ✅ oui |
|
||||||
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
|
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
|
||||||
|
|
||||||
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
|
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
|
||||||
@@ -174,6 +174,18 @@ du volet (recalculé du contrat `rbac_50_roles.json` à chaque build, byte-gaté
|
|||||||
`check_artifacts`) : ajouter un rôle/DocType ferait dériver **les trois** READMEs
|
`check_artifacts`) : ajouter un rôle/DocType ferait dériver **les trois** READMEs
|
||||||
en silence sinon.
|
en silence sinon.
|
||||||
|
|
||||||
|
Même classe enfin côté **SEO** : le total et la ventilation par langue des
|
||||||
|
mots-clés (« **258** mots-clés (fr=87 · en=87 · es=84) ») étaient transcrits **à
|
||||||
|
la main** dans **deux** docs — la table « Livrables » du README de module
|
||||||
|
(`05_deliverables_mvp/seo/README.md`) **et** la même ligne de la fiche agent
|
||||||
|
(`03_agents/seo/AGENT.md`) — sans aucun gate. Le quadruplet (total + fr/en/es) est
|
||||||
|
recomputé depuis `seo/out/MANIFEST.json` (`counts.keywords_total` +
|
||||||
|
`counts.keywords_per_lang`, recomptés de `projets_master.json` à chaque build,
|
||||||
|
byte-gaté par `check_artifacts`). 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 — ajouter un projet ferait sinon dériver
|
||||||
|
**les deux** docs en silence.
|
||||||
|
|
||||||
Un claim **absent** échoue aussi (la dérive de formulation qui ferait
|
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
|
disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur
|
||||||
(bash/git/python3), zéro réseau.
|
(bash/git/python3), zéro réseau.
|
||||||
|
|||||||
@@ -47,6 +47,10 @@
|
|||||||
# DEUX docs (table du README + prose « OTO Ventes 4 cartes/11 liens/12
|
# DEUX docs (table du README + prose « OTO Ventes 4 cartes/11 liens/12
|
||||||
# rôles ») + total « N Workspace natifs » → portails MANIFEST
|
# rôles ») + total « N Workspace natifs » → portails MANIFEST
|
||||||
# (tableau `workspaces`, byte-gaté par check_artifacts)
|
# (tableau `workspaces`, byte-gaté par check_artifacts)
|
||||||
|
# seo/README.md + 03_agents/seo/AGENT.md (comptes de mots-clés) :
|
||||||
|
# • total + ventilation « 258 mots-clés (fr=87 · en=87 · es=84) » transcrits À
|
||||||
|
# LA MAIN dans DEUX docs → seo MANIFEST.counts
|
||||||
|
# (keywords_total + keywords_per_lang, recomptés de projets_master, byte-gatés)
|
||||||
# rbac/{roleprofile_gen,apply_plan,userperm_gen}/README.md (comptes 3 volets) :
|
# rbac/{roleprofile_gen,apply_plan,userperm_gen}/README.md (comptes 3 volets) :
|
||||||
# • table « Nb rôles » PAR profil + total bijectif → roleprofile MANIFEST
|
# • table « Nb rôles » PAR profil + total bijectif → roleprofile MANIFEST
|
||||||
# • ligne agrégée « 6 étapes · 50 rôles / 116 DocPerm / 28 UP / 6 Role
|
# • ligne agrégée « 6 étapes · 50 rôles / 116 DocPerm / 28 UP / 6 Role
|
||||||
@@ -584,6 +588,49 @@ if up_bm is not None and up_rd is not None:
|
|||||||
bad(f"RBAC userperm · {UP_RD} dit {'/'.join(mu.groups())} MAIS MANIFEST "
|
bad(f"RBAC userperm · {UP_RD} dit {'/'.join(mu.groups())} MAIS MANIFEST "
|
||||||
f"dit {'/'.join(up_exp)} ({up_src}) → régénérer le README")
|
f"dit {'/'.join(up_exp)} ({up_src}) → régénérer le README")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SEO · comptes de mots-clés (total + ventilation par langue) —
|
||||||
|
# même classe de « vert trompeur », transcrits À LA MAIN dans DEUX docs.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Le générateur SEO émet out/MANIFEST.json dont le bloc `counts` porte
|
||||||
|
# keywords_total et keywords_per_lang{fr,en,es}, RECOMPTÉS à chaque build depuis
|
||||||
|
# `projets_master.json` (byte-gaté par check_artifacts ⇒ source faisant autorité).
|
||||||
|
# Ce quadruplet est recopié À LA MAIN à DEUX endroits : la table « Livrables » du
|
||||||
|
# README du module (05_deliverables_mvp/seo/README.md — « **258** mots-clés
|
||||||
|
# (fr=87 · en=87 · es=84) ») ET la même ligne de la fiche agent
|
||||||
|
# (03_agents/seo/AGENT.md). Aucun gate ne les couvrait : ajouter un projet ou du
|
||||||
|
# vocabulaire éditorial ferait dériver LES DEUX docs en silence pendant que
|
||||||
|
# l'artefact dit autre chose — même piège que les triplets par workspace des
|
||||||
|
# portails. On RECOMPUTE ici (total + 3 langues) depuis MANIFEST.counts (déjà
|
||||||
|
# byte-gaté · zéro duplication du compteur du générateur) et on exige l'égalité
|
||||||
|
# avec les DEUX docs. Un claim absent échoue AUSSI (traçabilité).
|
||||||
|
# ============================================================================
|
||||||
|
SEO_RD = f"{D}/seo/README.md"
|
||||||
|
SEO_FI = "03_agents/seo/AGENT.md"
|
||||||
|
try:
|
||||||
|
seo_c = load("seo/out/MANIFEST.json")["counts"]
|
||||||
|
seo_exp = (str(seo_c["keywords_total"]),
|
||||||
|
str(seo_c["keywords_per_lang"]["fr"]),
|
||||||
|
str(seo_c["keywords_per_lang"]["en"]),
|
||||||
|
str(seo_c["keywords_per_lang"]["es"]))
|
||||||
|
except (OSError, KeyError, ValueError) as e:
|
||||||
|
bad(f"SEO · MANIFEST illisible/inattendu : {e}"); seo_exp = None
|
||||||
|
if seo_exp is not None:
|
||||||
|
seo_src = (f"total={seo_exp[0]} fr={seo_exp[1]} en={seo_exp[2]} es={seo_exp[3]}")
|
||||||
|
seo_pat = r"\*\*(\d+)\*\* mots-clés \(fr=(\d+) · en=(\d+) · es=(\d+)\)"
|
||||||
|
for path_f in (SEO_RD, SEO_FI):
|
||||||
|
txt = read_norm(path_f)
|
||||||
|
if txt is None:
|
||||||
|
continue
|
||||||
|
m = re.search(seo_pat, txt)
|
||||||
|
if m is None:
|
||||||
|
bad(f"SEO · {path_f} — claim mots-clés INTROUVABLE (attendu {seo_src})")
|
||||||
|
elif m.groups() == seo_exp:
|
||||||
|
good(f"SEO · {path_f} — {'/'.join(m.groups())} == MANIFEST ({seo_src})")
|
||||||
|
else:
|
||||||
|
bad(f"SEO · {path_f} — dit {'/'.join(m.groups())} MAIS MANIFEST dit "
|
||||||
|
f"{'/'.join(seo_exp)} ({seo_src}) → régénérer le doc")
|
||||||
|
|
||||||
sys.exit(1 if FAIL else 0)
|
sys.exit(1 if FAIL else 0)
|
||||||
PY
|
PY
|
||||||
rc=$?
|
rc=$?
|
||||||
|
|||||||
Reference in New Issue
Block a user