[DTP-Worker 20260801_223450] Sprint 8 · buffer · SEO : gate d'IDENTITÉ du COMPTE d'INVARIANTS CI — le compte de contrôles de validation (annoncé « ## Garanties (N invariants) » du README + toutes ses mentions + les 5 self-reports du seo_gen.py : docstrings/aides/succès CLI) recomputé du SEUL registre-source = le LEDGER numéroté « # N · » de la fonction _validate (analogue du registre # Invariant N de Chat OTOIA · source d'autorité NON-artefact, invisible à check_artifacts). Exige : (a) ledger CONTIGU 1..N (comparaison LISTE triée ⇒ mord lacune ET doublon) ; (b) toute mention « N invariants » du .py == |ledger| ; (c) compte annoncé README == |ledger| ; (d) toute mention README == |ledger|. Prose du README RÉSUME (11 puces ≠ énumération 1:1) ⇒ gate du COMPTE, pas set-diff. 5 morsures vérifiées (2 README · 3 source ; doublon ledger attrapé là où l'approche set aurait raté), 7 gates re-verts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2433,3 +2433,63 @@ Restauré = green : (a)(b)(c)(d) tous ✓ (registre contigu 1..14 · 14 marqueur
|
|||||||
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
|
- **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).
|
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
|
||||||
- **Auto-score 4Big** : 96/100.
|
- **Auto-score 4Big** : 96/100.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sprint 8 · buffer · SEO : gate d'IDENTITÉ du COMPTE d'INVARIANTS CI
|
||||||
|
|
||||||
|
**Contexte** : campagne anti-dérive documentaire (Sprint 8 · QA « regression
|
||||||
|
tests exhaustifs »). Baseline des 7 gates vert avant intervention. Recherche
|
||||||
|
(sous-agent Explore) de la prochaine surface data-derived transcrite en prose
|
||||||
|
mais NON gatée. L'agent a proposé 5 candidates « N invariants » ; chat_otoia
|
||||||
|
étant déjà fermée au commit précédent, candidat retenu = le générateur SEO
|
||||||
|
(`05_deliverables_mvp/seo/seo_gen.py` + `seo/README.md`).
|
||||||
|
|
||||||
|
**Défaut** : la source d'autorité du COMPTE D'INVARIANTS de validation SEO n'est
|
||||||
|
PAS un artefact JSON (invisible à `check_artifacts` qui byte-gate les `out/*.json`)
|
||||||
|
mais le LEDGER NUMÉROTÉ de la fonction `_validate` — les marqueurs `# N ·`
|
||||||
|
(`# 1 ·` … `# 15 ·`) qui ouvrent chaque contrôle appliqué, analogue du registre
|
||||||
|
`# Invariant N` de Chat OTOIA. Ce compte est transcrit À LA MAIN à SEPT endroits
|
||||||
|
qu'AUCUN gate ne couvrait : le titre `## Garanties (N invariants)` du README + son
|
||||||
|
bloc de commande (« schéma de sortie + N invariants »), plus CINQ chaînes du `.py`
|
||||||
|
lui-même (docstring module L19, docstring `_validate` L57, aide `build` L24, message
|
||||||
|
de succès CLI « N invariants verts. » L285, aide du sous-parseur `validate` L298).
|
||||||
|
Le bloc SEO préexistant du gate ne couvre que des COMPTES d'ARTEFACT (mots-clés
|
||||||
|
total + par langue, recomputés de MANIFEST.counts) — classe distincte, surface
|
||||||
|
aveugle. Ajouter un contrôle `# 16 ·` à `_validate` ⇒ README + self-reports du `.py`
|
||||||
|
mentent en silence pendant que la CI applique 16 contrôles (« vert trompeur » #6)
|
||||||
|
— ni les suites `tests/` (FONCTIONS, jamais la prose) ni le byte-gate d'artefact
|
||||||
|
(aveugle au `.py` source) ne l'attrapent.
|
||||||
|
|
||||||
|
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « SEO · IDENTITÉ du
|
||||||
|
COMPTE d'INVARIANTS CI », inséré juste après le bloc SEO mots-clés et avant le bloc
|
||||||
|
CRM pipeline vente) : on RECOMPUTE le ledger depuis le SEUL registre-source (les
|
||||||
|
`# N ·` de `_validate`, extraits du corps de la fonction borné au prochain `def `)
|
||||||
|
et on exige — **(a)** le ledger est CONTIGU 1..N (comparaison de la LISTE triée à
|
||||||
|
`range(1,N+1)`, ⇒ mord lacune ET doublon) ; **(b)** TOUTE mention « N invariants »
|
||||||
|
du `.py` == |ledger| (set des self-reports == {N}) ; **(c)** le compte annoncé
|
||||||
|
« ## Garanties (N invariants) » du README == |ledger| ; **(d)** TOUTE mention
|
||||||
|
« N invariants » du README == |ledger|. La prose du README RÉSUME les contrôles
|
||||||
|
(11 puces `·`-séparées ≠ énumération numérotée 1:1) ⇒ on gate le COMPTE partout où
|
||||||
|
il apparaît, PAS un set-diff de la liste. Section/compte absent = échec (#6).
|
||||||
|
|
||||||
|
**5 morsures vérifiées** (working tree byte-restauré après chacune via
|
||||||
|
`git checkout -- <fichier précis>`, JAMAIS `git checkout .` / `git clean`) :
|
||||||
|
1. README titre `15`→`16` → ✗ (c) « annonce (16) MAIS ledger 15 » + ✗ (d) « [15, 16] » ;
|
||||||
|
2. README commande `15`→`14` → ✗ (d) « mention(s) [14, 15] MAIS ledger 15 » ;
|
||||||
|
3. `.py` succès CLI `15`→`13` → ✗ (b) « self-report(s) [13, 15] MAIS ledger 15 » ;
|
||||||
|
4. ledger `# 7 ·`→`# 77 ·` (lacune) → ✗ (a) « NON contigu […6, 77, 8…] » ;
|
||||||
|
5. ledger `# 8 ·`→`# 7 ·` (doublon) → ✗ (a) « NON contigu […7, 7, 9…] »
|
||||||
|
(la comparaison sur LISTE — non set — attrape le doublon, angle que
|
||||||
|
l'approche set de chat_otoia aurait raté).
|
||||||
|
Restauré = green : (a)(b)(c)(d) tous ✓ (ledger contigu 1..15 · 15 marqueurs).
|
||||||
|
|
||||||
|
- `ci/README.md` : la clause SEO de la row `check-readme-claims` étendue du COMPTE
|
||||||
|
d'INVARIANTS CI (annonce + toutes mentions README + 5 self-reports `.py`
|
||||||
|
recomputés du ledger `# N ·` de `_validate`, contiguïté 1..N).
|
||||||
|
- **7 gates re-verts** : `guard_constraints` / `validate_json` / `check_docs` /
|
||||||
|
`check_artifacts` / `check_regression` / `check_ci_integrity` /
|
||||||
|
`check_readme_claims` (tous rc=0).
|
||||||
|
- **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.
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1550,6 +1550,101 @@ if seo_exp is not None:
|
|||||||
bad(f"SEO · {path_f} — dit {'/'.join(m.groups())} MAIS MANIFEST dit "
|
bad(f"SEO · {path_f} — dit {'/'.join(m.groups())} MAIS MANIFEST dit "
|
||||||
f"{'/'.join(seo_exp)} ({seo_src}) → régénérer le doc")
|
f"{'/'.join(seo_exp)} ({seo_src}) → régénérer le doc")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SEO · IDENTITÉ du COMPTE d'INVARIANTS CI — le README annonce « ## Garanties
|
||||||
|
# (N invariants) » et le générateur se SELF-REPORTE (« schéma + N invariants »)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# CLASSE DISTINCTE du bloc SEO ci-dessus (qui gate des COMPTES d'ARTEFACT —
|
||||||
|
# mots-clés total + par langue, recomptés de MANIFEST.counts). Ici la donnée
|
||||||
|
# gardée est le COMPTE D'INVARIANTS de validation, dont la source d'autorité
|
||||||
|
# n'est PAS un artefact JSON (invisible à check_artifacts qui byte-gate les
|
||||||
|
# out/*.json) mais le LEDGER NUMÉROTÉ de la fonction `_validate` du générateur :
|
||||||
|
# les marqueurs `# N ·` (1 · 2 · … · 15) qui ouvrent chaque contrôle appliqué —
|
||||||
|
# exactement l'analogue du registre `# Invariant N` de Chat OTOIA. Ce compte est
|
||||||
|
# transcrit À LA MAIN à SEPT endroits qu'AUCUN gate ne couvrait : le titre de
|
||||||
|
# section du README (« ## Garanties (N invariants) ») + son bloc de commande
|
||||||
|
# (« schéma de sortie + N invariants »), plus CINQ chaînes du .py lui-même
|
||||||
|
# (docstring module, docstring `_validate`, aide `build`, message de succès CLI
|
||||||
|
# « N invariants verts. », aide du sous-parseur `validate`). Ajouter un contrôle
|
||||||
|
# `# 16 ·` à `_validate` sans toucher les chaînes ⇒ le README ET les self-reports
|
||||||
|
# du .py mentent en silence pendant que la CI applique 16 contrôles — « vert
|
||||||
|
# trompeur » (#6) que ni les suites tests/ (FONCTIONS, jamais la prose) ni le
|
||||||
|
# byte-gate d'artefact (aveugle au .py source) n'attrapent. On RECOMPUTE ici le
|
||||||
|
# ledger depuis le SEUL registre-source (les `# N ·` de `_validate`) et on exige :
|
||||||
|
# (a) le ledger est CONTIGU 1..N (lacune/doublon dans les marqueurs = défaut réel
|
||||||
|
# du registre) ; (b) TOUTE mention « N invariants » du .py (self-reports) == |ledger|
|
||||||
|
# (un seul chiffre désynchronisé mordu) ; (c) le compte annoncé du README == |ledger| ;
|
||||||
|
# (d) TOUTE mention « N invariants » du README == |ledger|. La prose du README
|
||||||
|
# RÉSUME les contrôles (≠ énumération numérotée 1:1) : on gate le COMPTE partout
|
||||||
|
# où il apparaît, pas un set-diff de la liste. Section/compte absent = échec (la
|
||||||
|
# disparition d'un chiffre est une régression de traçabilité · #6).
|
||||||
|
# ============================================================================
|
||||||
|
SEO_GEN = f"{D}/seo/seo_gen.py"
|
||||||
|
try:
|
||||||
|
seo_src = open(SEO_GEN, encoding="utf-8").read()
|
||||||
|
except OSError as e:
|
||||||
|
bad(f"SEO invariants · générateur {SEO_GEN} illisible : {e}"); seo_src = None
|
||||||
|
if seo_src is not None:
|
||||||
|
# (a) registre-source = marqueurs `# N ·` du LEDGER de `_validate` (autorité).
|
||||||
|
_mfn = re.search(r"def _validate\b", seo_src)
|
||||||
|
if _mfn is None:
|
||||||
|
bad(f"SEO invariants · fonction `_validate` INTROUVABLE dans {SEO_GEN} "
|
||||||
|
"(registre-source du ledger absent → impossible de re-dériver)")
|
||||||
|
else:
|
||||||
|
_bs = _mfn.end()
|
||||||
|
_mn = re.search(r"\ndef ", seo_src[_bs:])
|
||||||
|
_body = seo_src[_bs: _bs + _mn.start()] if _mn else seo_src[_bs:]
|
||||||
|
seo_led = [int(n) for n in re.findall(r"#\s*(\d+)\s*·", _body)]
|
||||||
|
if not seo_led:
|
||||||
|
bad(f"SEO invariants · aucun marqueur `# N ·` dans `_validate` de {SEO_GEN} "
|
||||||
|
"(ledger vide → impossible de re-dériver)")
|
||||||
|
else:
|
||||||
|
seo_ninv = len(seo_led)
|
||||||
|
if sorted(seo_led) != list(range(1, seo_ninv + 1)):
|
||||||
|
bad(f"SEO invariants · ledger `_validate` NON contigu : {seo_led} "
|
||||||
|
f"(attendu 1..{seo_ninv}) — lacune/doublon dans les marqueurs `# N ·`")
|
||||||
|
else:
|
||||||
|
good(f"SEO invariants · ledger `_validate` contigu 1..{seo_ninv} "
|
||||||
|
f"({seo_ninv} marqueurs `# N ·`)")
|
||||||
|
# (b) self-reports du .py « N invariants » (docstrings + aides + succès CLI).
|
||||||
|
seo_py_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", seo_src)})
|
||||||
|
if not seo_py_rep:
|
||||||
|
bad(f"SEO invariants · aucune mention « N invariants » dans {SEO_GEN} "
|
||||||
|
f"(self-report absent → attendu {seo_ninv})")
|
||||||
|
elif seo_py_rep == [seo_ninv]:
|
||||||
|
good(f"SEO invariants · {SEO_GEN} — self-report(s) {seo_py_rep} "
|
||||||
|
f"== ledger ({seo_ninv})")
|
||||||
|
else:
|
||||||
|
bad(f"SEO invariants · {SEO_GEN} — self-report(s) « N invariants » "
|
||||||
|
f"{seo_py_rep} MAIS ledger en compte {seo_ninv} "
|
||||||
|
"(une chaîne du générateur désynchronisée du registre)")
|
||||||
|
# README : titre de section + toute mention « N invariants ».
|
||||||
|
try:
|
||||||
|
seo_rd_raw = open(SEO_RD, encoding="utf-8").read()
|
||||||
|
except OSError as e:
|
||||||
|
bad(f"SEO invariants · {SEO_RD} illisible : {e}"); seo_rd_raw = None
|
||||||
|
if seo_rd_raw is not None:
|
||||||
|
_mh = re.search(r"##\s+Garanties\s+\((\d+)\s+invariants\)", seo_rd_raw)
|
||||||
|
if _mh is None:
|
||||||
|
bad(f"SEO invariants · {SEO_RD} — section « ## Garanties (N invariants) » "
|
||||||
|
f"INTROUVABLE (attendu {seo_ninv})")
|
||||||
|
else:
|
||||||
|
# (c) compte annoncé.
|
||||||
|
if int(_mh.group(1)) == seo_ninv:
|
||||||
|
good(f"SEO invariants · {SEO_RD} — annonce ({_mh.group(1)}) "
|
||||||
|
f"== ledger ({seo_ninv})")
|
||||||
|
else:
|
||||||
|
bad(f"SEO invariants · {SEO_RD} — annonce ({_mh.group(1)}) MAIS "
|
||||||
|
f"ledger en compte {seo_ninv} → régénérer le README")
|
||||||
|
# (d) toute mention « N invariants » du README == ledger.
|
||||||
|
seo_rd_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", seo_rd_raw)})
|
||||||
|
if seo_rd_rep == [seo_ninv]:
|
||||||
|
good(f"SEO invariants · {SEO_RD} — mention(s) {seo_rd_rep} "
|
||||||
|
f"== ledger (aucune dérive)")
|
||||||
|
else:
|
||||||
|
bad(f"SEO invariants · {SEO_RD} — mention(s) « N invariants » "
|
||||||
|
f"{seo_rd_rep} MAIS ledger en compte {seo_ninv} → régénérer le README")
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# CRM · pipeline vente (états / transitions) — même classe de « vert trompeur »,
|
# CRM · pipeline vente (états / transitions) — même classe de « vert trompeur »,
|
||||||
# transcrit À LA MAIN dans DEUX README de module.
|
# transcrit À LA MAIN dans DEUX README de module.
|
||||||
|
|||||||
Reference in New Issue
Block a user