diff --git a/05_activity_log/2026-08-02.md b/05_activity_log/2026-08-02.md index 9ed09db..714f00c 100644 --- a/05_activity_log/2026-08-02.md +++ b/05_activity_log/2026-08-02.md @@ -1,5 +1,68 @@ # Activity log · 2026-08-02 · Claude Code DTP Worker +## Sprint 8 · buffer · Ledger d'invariants — 2ᵉ FAMILLE (`# INVn ·` / `validate_bundle` / « N familles d'invariants ») : ancrage du COMPTE de `deploy_runbook` (11) + `acceptance` (11) + +**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de +chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles CI +depuis le registre-source des marqueurs numérotés de la fonction `_validate`, jamais +une liste à la main) avait été **déclarée close à 8 générateurs** — mais sur le SEUL +dialecte `# N ·` / « N invariants » / `_validate`(`_validate_bundle`). La mémoire +`invariant-ledger-count-gate` consigne précisément le risque : « un prior *CLOSED +après 7* était FAUX — un style de marqueur différent avait glissé ; re-scanner LES +DEUX styles avant de croire la clôture ». Un **re-scan systématique** a exhumé une +**DEUXIÈME famille** entièrement HORS gate : marqueurs `# INVn ·` (préfixe `INV`) +dans `validate_bundle`, phrasé « N **familles d'**invariants ». DEUX générateurs la +portent : +- `devops/deploy_runbook/deploy_runbook_gen.py` — ledger `# INV1..INV11` dans + `validate_bundle`, compte **« 11 familles d'invariants »** recopié à **3 surfaces + vives** : générateur l.272 (message CLI de succès) · README l.73 (bloc de commande) + · suite tests l.4 (docstring) ; +- `qa/acceptance/acceptance_gen.py` — ledger `# INV1..INV11` dans `validate_bundle`, + compte à **2 surfaces vives** : README l.46 « 11 invariants » · suite tests l.4 + « 11 familles d'invariants ». (Le `.py` acceptance ne porte AUCUN compte numéroté + → surface non exigée.) + +**Le « vert trompeur »** — ajouter un contrôle `# INV12 ·` à `validate_bundle` sans +toucher ces chaînes ⇒ README + self-reports + docstrings de test mentent en silence +pendant que la CI applique 12 contrôles. Ni `check_artifacts` (le `.py` source n'est +pas un `out/*.json`) ni les suites `tests/` (FONCTIONS de validation, jamais la prose) +n'attrapent la dérive. **Prouvé (M4 SILENT-GREEN)** : injecter `# INV12 ·` dans +`validate_bundle` (ledger→12, toute la prose reste 11) ⇒ les **3 surfaces +deploy_runbook mordent** « [11] MAIS ledger en compte 12 » — preuve que l'autorité +est le **CODE-registre**, pas la cohérence interne de la prose. + +**Particularités** — Ledger PROPRE 1..N compté À PART du schéma (« schéma + N +invariants »). Les mentions NON numérotées de « invariants » (docstrings « invariants +de déploiement/recette », README « Un invariant refuse … ») ne sont pas ancrées par +un chiffre → **aucun faux positif**. Le compte-regex accepte les DEUX formes +(« N invariants » ET « N familles d'invariants ») via un préfixe optionnel +`(?:familles d')?`. Les `daily_reports`/`activity_log` (snapshots datés — ex. +acceptance « 10 » d'avant la croissance 10→11) ne sont PAS des surfaces vives et ne +sont volontairement pas ancrés (aucune dérive à surveiller sur un log figé). + +**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « DevOps deploy_runbook + QA +acceptance · IDENTITÉ du COMPTE d'INVARIANTS de `validate_bundle` », juste après le +bloc Mobile) — **paramétré sur les 2 modules** (zéro duplication). RECOMPUTE le +ledger depuis le SEUL registre-source (`# INVn ·` de `validate_bundle`, corps extrait +jusqu'au `def` suivant) et exige : **(a)** ledger CONTIGU 1..N (lacune OU doublon nu += vrai défaut de registre) ; **(b)** CHAQUE surface déclarée porte ≥1 mention +« N [familles d']invariants » toutes == |ledger|. Une surface sans mention = self-report +ÉVAPORÉ (échec · anti-évaporation #6). + +**Vérif** — 7 checks verts sur l'arbre propre (2 ledgers 1..11 + 5 surfaces). +**8 morsures** adversariales, gate lancé SEUL, reverts par `cp` ciblé (jamais +`git checkout` large — cf. incident consigné) : **M1** deploy `.py` 11→12 (b générateur) +· **M2** deploy README 11→12 (b) · **M3** deploy test 11→12 (b) · **M4 SILENT-GREEN** +`# INV12 ·` injecté ⇒ 3 surfaces deploy mordent (autorité = CODE) · **M5** `# INV7 ·` +supprimé ⇒ ledger non contigu (a) + 3 surfaces désync · **M6** acceptance README 11→12 +(b) · **M7** acceptance test 11→12 (b) · **M8** compte retiré du README acceptance ⇒ +self-report ÉVAPORÉ. Arbre restauré (`git status` propre). Suites unittest re-vertes : +deploy_runbook **29 tests OK** · acceptance **37 tests OK** (générateurs inchangés). +Suite complète re-verte : `check_artifacts` · `check_docs` · `guard_constraints` · +`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0. +**Les DEUX familles de ledger sont désormais gatées ; re-scanner tout NOUVEAU +dialecte de marqueur (préfixe/phrasé) avant de re-déclarer la classe close.** + ## Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'encadré « Défense en profondeur » (NOM du rôle détenteur de `set_user_permissions` · scope · famille) sur l'artefact byte-gaté **Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le §2 diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 777c367..093891d 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -8646,6 +8646,94 @@ if mbi_src is not None: bad(f"Mobile invariants · {MBI_RD} — mention(s) « N invariants » " f"{mbi_rd_rep} MAIS ledger en compte {mbi_ninv} → régénérer le README") +# ============================================================================ +# DevOps deploy_runbook + QA acceptance · IDENTITÉ du COMPTE d'INVARIANTS de +# `validate_bundle` — DEUXIÈME famille de « ledger d'invariants », distincte des +# 8 générateurs déjà gatés (Chat OTOIA · SEO · Fiscal · CONFOTUR · Dossier Vente · +# Audit 5D · Commissions · Mobile). +# ---------------------------------------------------------------------------- +# La série « ledger » précédente a été déclarée close à 8 générateurs — MAIS sur +# le SEUL patron `# N ·` / « N invariants » / `_validate`(`_validate_bundle`). +# DEUX générateurs portent le MÊME nombre magique dans un DIALECTE différent, resté +# HORS de tout gate : marqueurs `# INVn ·` (préfixe `INV`) dans `validate_bundle`, +# et phrasé « N **familles d'**invariants ». C'est exactement le piège consigné +# (« CLOSED après N était FAUX — un style de marqueur différent avait glissé ») : +# • devops/deploy_runbook — ledger `# INV1..INV11` dans `validate_bundle`, compte +# « 11 familles d'invariants » recopié à TROIS surfaces (générateur l.272 · README +# l.73 · suite tests l.4) ; +# • qa/acceptance — ledger `# INV1..INV11` dans `validate_bundle`, compte à DEUX +# surfaces (README l.46 « 11 invariants » · suite tests l.4 « 11 familles +# d'invariants »). Le .py acceptance ne porte PAS de compte numéroté → non exigé. +# Ajouter un contrôle `# INV12 ·` à `validate_bundle` sans toucher ces chaînes ⇒ +# README + self-reports mentent en silence pendant que la CI applique 12 contrôles — +# « vert trompeur » (#6) que ni les suites tests/ (FONCTIONS de validation, jamais +# la prose) ni le byte-gate d'artefact (aveugle au .py source) n'attrapent. Ledger +# PROPRE 1..N compté À PART du schéma (phrasé « schéma + N invariants »). Les +# mentions NON numérotées de « invariants » (docstrings « invariants de … », « Un +# invariant refuse … ») ne sont pas ancrées par un chiffre → aucun faux positif. On +# RECOMPUTE le ledger depuis le SEUL registre-source (les `# INVn ·` de +# `validate_bundle`) et on exige : (a) ledger CONTIGU 1..N (lacune/doublon = défaut +# de registre) ; (b) CHAQUE surface déclarée porte ≥1 mention « N [familles d'] +# invariants » toutes == |ledger|. Surface sans mention = self-report évaporé (échec +# · régression de traçabilité #6). Les daily_reports/activity_log (snapshots datés, +# ex. acceptance « 10 » d'avant la croissance 10→11) NE sont PAS des surfaces vives +# et ne sont volontairement pas ancrés. +# ============================================================================ +DRA_INV_RE = re.compile(r"(\d+)\s+(?:familles\s+d['’ʼ]\s*)?invariants") +DRA_MODS = [ + ("DevOps · deploy_runbook", f"{D}/devops/deploy_runbook/deploy_runbook_gen.py", + [("générateur", f"{D}/devops/deploy_runbook/deploy_runbook_gen.py"), + ("README", f"{D}/devops/deploy_runbook/README.md"), + ("suite tests", f"{D}/devops/deploy_runbook/tests/test_deploy_runbook.py")]), + ("QA · acceptance", f"{D}/qa/acceptance/acceptance_gen.py", + [("README", f"{D}/qa/acceptance/README.md"), + ("suite tests", f"{D}/qa/acceptance/tests/test_acceptance.py")]), +] +for _lbl, _gen, _surfaces in DRA_MODS: + try: + _src = open(_gen, encoding="utf-8").read() + except OSError as e: + bad(f"{_lbl} invariants · générateur {_gen} illisible : {e}"); continue + _mfn = re.search(r"def validate_bundle\b", _src) + if _mfn is None: + bad(f"{_lbl} invariants · fonction `validate_bundle` INTROUVABLE dans {_gen} " + "(registre-source du ledger absent → impossible de re-dériver)"); continue + _bs = _mfn.end() + _mn = re.search(r"\ndef ", _src[_bs:]) + _body = _src[_bs: _bs + _mn.start()] if _mn else _src[_bs:] + _pairs = re.findall(r"#\s*INV\s*(\d+)([a-z]?)\s*·", _body) + _dup_bare = sorted({n for n in {p[0] for p in _pairs} + if [s for num, s in _pairs if num == n and s == ""] + and sum(1 for num, _ in _pairs if num == n) > 1}) + _top = sorted({int(n) for n, _ in _pairs}) + if not _top: + bad(f"{_lbl} invariants · aucun marqueur `# INVn ·` dans `validate_bundle` de " + f"{_gen} (ledger vide → impossible de re-dériver)"); continue + if _dup_bare: + bad(f"{_lbl} invariants · marqueur(s) `# INVn ·` dupliqué(s) dans " + f"`validate_bundle` : {_dup_bare} — vrai doublon de registre"); continue + _ninv = len(_top) + if _top != list(range(1, _ninv + 1)): + bad(f"{_lbl} invariants · ledger `validate_bundle` NON contigu : {_top} " + f"(attendu 1..{_ninv}) — lacune/doublon dans les marqueurs `# INVn ·`") + else: + good(f"{_lbl} invariants · ledger `validate_bundle` contigu 1..{_ninv} " + f"({_ninv} marqueurs `# INVn ·` · schéma compté à part)") + for _sname, _spath in _surfaces: + try: + _sraw = open(_spath, encoding="utf-8").read() + except OSError as e: + bad(f"{_lbl} invariants · {_sname} {_spath} illisible : {e}"); continue + _rep = sorted({int(x) for x in DRA_INV_RE.findall(_sraw)}) + if not _rep: + bad(f"{_lbl} invariants · aucune mention « N (familles d')invariants » dans " + f"{_spath} ({_sname}) — self-report évaporé (attendu {_ninv} · régression #6)") + elif _rep == [_ninv]: + good(f"{_lbl} invariants · {_sname} — mention(s) {_rep} == ledger ({_ninv})") + else: + bad(f"{_lbl} invariants · {_sname} ({_spath}) — mention(s) « N invariants » " + f"{_rep} MAIS ledger `validate_bundle` en compte {_ninv} → resynchroniser") + # ============================================================================ # Traçabilité roadmap · le `roadmap_ref` VERBATIM des artefacts — la CITATION # (n° de ligne + « libellé bullet ») ANCRÉE sur la ligne RÉELLE de la roadmap,