diff --git a/05_activity_log/2026-08-02.md b/05_activity_log/2026-08-02.md index 57deed6..187c206 100644 --- a/05_activity_log/2026-08-02.md +++ b/05_activity_log/2026-08-02.md @@ -1,5 +1,61 @@ # Activity log · 2026-08-02 · Claude Code DTP Worker +## Sprint 8 · buffer · Faisabilité — ancrage du TEMPLATE CANONIQUE v1.0 (5 surfaces #9/#10) sur CLAUDE.md + +**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de +chiffres »). Le pattern « constante canonique CLAUDE.md recopiée à la main dans +une surface data-derived » (re-dérive la valeur depuis la contrainte, jamais la +cohérence interne) couvre déjà le générateur (`model.py`/README/renderer/scorer/ +oracle-test), les fiches ERPNext/CRM (« Fiches #10 »), le module fiscal (`moneda`), +la marque SEO §Entités, la persona Chat OTOIA, les tokens branding #4, etc. Un +fichier restait NON gaté (grep : absent de tous les `ci/*.sh`) et c'est +paradoxalement **le plus normatif** : `05_deliverables_mvp/faisabilite/ +TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md`, le DOCUMENT-MODÈLE « CANONIQUE » (Livrable +Sprint 1) que **toute faisabilité en production DOIT reproduire**, et que le +générateur 4 volets + le Publiciste + la régénération batch S4 sont censés suivre. + +Ce template recopie **à la main les 6 valeurs de CLAUDE.md #9/#10 à CINQ surfaces +structurées**, toutes se réclamant de « contrainte #9 »/« #10 », **aucune gatée** : +1. **l.28** intro §0 « frais **3 %** · marketing **8.5 %** · point d'équilibre + **52 %** » (3 pourcentages #9, associés par mot-clé) ; +2. **l.144-146** §5.2 — 3 lignes de table « **N %** (fixe · contrainte #9) » ; +3. **l.29** intro §0 « **USD + DOP** · format **Letter US** · paiements **Cardnet** » ; +4. **l.149-151** §5.2 — 3 lignes « **valeur** (… contrainte #10) » (la ligne + paiement citant l'exclusion « ❌ pas-X ») ; +5. **l.196** §7 checklist « 3 % / 8.5 % / 52 % · USD+DOP · Letter US · Cardnet + présents » (énumération des 6 marqueurs). + +**Le « vert trompeur »** — si CLAUDE.md #9 passe **52 %→55 %** (ou #10 renomme le +prestataire) et que les modules s'alignent, ce TEMPLATE — la RÉFÉRENCE de toutes +les faisabilités — reste PÉRIMÉ en silence, imposant à chaque projet un paramètre +qui CONTREDIT le mandat pendant qu'il se réclame « contrainte #9/#10 ». Ni +`check_artifacts` (ce n'est pas un `out/*.json`) ni `check_docs`/`guard_constraints` +ni aucune suite `tests/` (FONCTIONS, jamais l'ancre à CLAUDE.md) ne mordent. + +**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « TEMPLATE FAISABILITÉ CANONIQUE +v1.0 · PARAMÈTRES #9/#10 », juste après « Fiches #10 ») — RÉUTILISE `exp` (les 6 +valeurs déjà recomputées des lignes #9/#10 de CLAUDE.md · zéro re-dérivation) + +`exp_excl` (prestataire exclu du `(pas X)`). Exige que chacune des 5 surfaces == +CLAUDE.md : (1) 3 pcts associés par mot-clé ; (2) SET des 3 lignes « (fixe · +contrainte #9) » == {3 %,8.5 %,52 %} (mord aussi ligne ajoutée/retirée) ; (3)/(4) +chaque valeur #10 présente en gras dans sa région ancrée + l'exclusion ❌ ; (5) +énumération des 6 marqueurs == ensemble EXACT (set-diff absent ET en trop). Une +surface INTROUVABLE échoue AUSSI (recensement évaporé = régression #6). + +**Vérif** — 5 checks verts sur l'arbre propre. **11 morsures** adversariales : +M1 intro#9 52→55 · M2 ligne#9 8.5→9 · M3 intro#10 Cardnet→Azul · M4 ligne#10 +Letter US→A4 · M5 exclusion ❌ Stripe→PayPal · M6 checklist perd Cardnet · M7 +checklist 52→60 · M8 intro#9 supprimée (INTROUVABLE) · M9 checklist « présents »→ +« présent » (INTROUVABLE) · **M10 CLAUDE.md #9 52→55 SEUL ⇒ cascade sur 3 régions +(1)(2)(5)** · **M11 CLAUDE.md #10 Cardnet→Azul SEUL ⇒ cascade sur 3 régions +(3)(4)(5)** — les deux preuves que l'AUTORITÉ est CLAUDE.md, pas la cohérence +interne du template. Un piège corrigé en route : le groupe capturé est le NOMBRE nu +(sans `%`), donc `_pct()` (qui exige un `%`) renvoyait `None` → `float()` direct. +Gates re-verts : `check_artifacts` · `check_docs` · `guard_constraints` · +`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0. + +--- + ## Sprint 8 · buffer · Fiscal e-CF — ancrage du CONTRAT DE FORMAT e-NCF dans le COMPOSEUR `ncf.py` (CODE) + son ORACLE de test **Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 5c2e38d..4f0ff74 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -5295,6 +5295,140 @@ if exp is not None: (f" · (pas {exp_excl})" if cites_excl and exp_excl else "") good(f"Fiches #10 · {label} — cite {_cited} == CLAUDE.md #10") +# ============================================================================ +# TEMPLATE FAISABILITÉ CANONIQUE v1.0 · PARAMÈTRES #9/#10 — même classe d'ANCRAGE +# que « paramètres canoniques » (generator) et « Fiches #10 » ci-dessus, sur une +# SURFACE encore distincte : le DOCUMENT-MODÈLE « CANONIQUE » que TOUTE faisabilité +# en production DOIT reproduire — `05_deliverables_mvp/faisabilite/ +# TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md` (Livrable Sprint 1 · statut « CANONIQUE : +# toute faisabilité en production DOIT s'y conformer »). +# ---------------------------------------------------------------------------- +# Ce template recopie À LA MAIN les 6 valeurs de CLAUDE.md #9/#10 à CINQ surfaces +# structurées, TOUTES se réclamant de « contrainte #9 »/« #10 » : +# (1) l.28 intro §0 « frais édition **3 %** · marketing **8.5 %** · point +# d'équilibre **52 %** pré-vente » (les 3 pourcentages #9, associés par mot-clé) ; +# (2) l.144-146 §5.2 — 3 lignes de table « **N %** (fixe · contrainte #9) » +# (frais/marketing/équilibre) ; +# (3) l.29 intro §0 « **USD + DOP** · format **Letter US** · paiements **Cardnet** » +# (les 3 valeurs #10) ; +# (4) l.149-151 §5.2 — 3 lignes de table « **valeur** (… contrainte #10) » (devises · +# format · paiement, la ligne paiement citant l'exclusion « ❌ pas-X ») ; +# (5) l.196 §7 checklist « 3 % / 8.5 % / 52 % · USD+DOP · Letter US · Cardnet présents » +# (l'énumération des 6 marqueurs, comme la prose README:28 du generator). +# AUCUN gate ne liait ce fichier à CLAUDE.md (grep : absent de tous les `ci/*.sh`) : +# check_artifacts est aveugle (ce n'est pas un `out/*.json`), check_docs/guard aussi. +# Piège #6 « vert trompeur » : si CLAUDE.md #9 passe 52 %→55 % (ou #10 renomme le +# prestataire) et que les modules s'alignent, ce TEMPLATE — la référence que le +# générateur 4 volets, le Publiciste et la régénération batch S4 (§8) sont censés +# suivre — reste PÉRIMÉ en silence, imposant à TOUTES les faisabilités un paramètre +# qui CONTREDIT le mandat, pendant qu'il se réclame « contrainte #9/#10 ». Aucune +# suite tests/ (FONCTIONS, jamais l'ancre à CLAUDE.md) ne l'attrape. On RÉUTILISE +# `exp` (6 valeurs recomputées de #9/#10 · zéro duplication) + `exp_excl` (prestataire +# exclu) et on exige que CHACUNE des 5 surfaces == CLAUDE.md. Une surface INTROUVABLE +# échoue AUSSI (le recensement qui s'évapore est lui-même une régression #6). +# ============================================================================ +if exp is not None: + TPL = f"{D}/faisabilite/TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md" + tpl = read_norm(TPL) + if tpl is None: + bad(f"Template canonique · {TPL} illisible") + else: + _pct_kw_map = (("frais_edition_pct", r"frais", "frais édition"), + ("marketing_pct", r"marketing", "marketing"), + ("point_equilibre_pct", r"[ée]quilibre", "point d'équilibre")) + # (1) intro §0 (l.28) · les 3 pourcentages #9 associés par mot-clé + m1 = re.search(r"#9 Param[^→]*→(.+?)pr[ée]-vente", tpl) + if m1 is None: + bad("Template canonique · (1) intro #9 (l.28) INTROUVABLE " + "(attendu frais/marketing/équilibre en gras)") + else: + seg1 = m1.group(1); drift = [] + for key, kw, lab in _pct_kw_map: + mm = re.search(kw + r"[^*]*?\*\*\s*(\d+(?:\.\d+)?)\s*%", seg1) + got = float(mm.group(1)) if mm else None + if got != exp[key]: + drift.append(f"{lab}={_pnorm(got) if got is not None else '∅'} " + f"(attendu {_pnorm(exp[key])})") + if drift: + bad("Template canonique · (1) intro #9 (l.28) DÉRIVE de CLAUDE.md #9 : " + f"{' · '.join(drift)} → aligner le template (ou CLAUDE.md)") + else: + good("Template canonique · (1) intro #9 (l.28) — 3 pourcentages == " + "CLAUDE.md #9") + # (2) §5.2 (l.144-146) · les 3 lignes de table « **N %** (fixe · contrainte #9) » + rows9 = re.findall(r"\*\*\s*(\d+(?:\.\d+)?)\s*%\s*\*\*\s*\(fixe · contrainte #9\)", + tpl) + want9 = {exp[k] for k in _num_keys} + got9 = {float(x) for x in rows9} + if len(rows9) != 3: + bad(f"Template canonique · (2) §5.2 — attendu 3 lignes « **N %** (fixe · " + f"contrainte #9) », trouvé {len(rows9)} ({rows9}) → régénérer le template") + elif got9 == want9: + good("Template canonique · (2) §5.2 — 3 lignes « (fixe · contrainte #9) » == " + "CLAUDE.md #9 (3 %/8.5 %/52 %)") + else: + miss = sorted(_pnorm(v) for v in want9 - got9) + extra = sorted(_pnorm(v) for v in got9 - want9) + bad(f"Template canonique · (2) §5.2 — lignes #9 {sorted(map(_pnorm, got9))} " + f"MAIS CLAUDE.md {sorted(map(_pnorm, want9))}" + + (f" · absents={miss}" if miss else "") + + (f" · en trop={extra}" if extra else "") + " → régénérer le template") + # (3) intro §0 (l.29) · les 3 valeurs #10 en gras + m3 = re.search(r"#10 Devises\*\* → (.+?)\.", tpl) + if m3 is None: + bad("Template canonique · (3) intro #10 (l.29) INTROUVABLE " + "(attendu devises/format/paiement en gras)") + else: + seg3 = m3.group(1); drift = [] + for k in _txt_keys: + if not re.search(r"\*\*\s*" + re.escape(exp[k]) + r"\s*\*\*", seg3): + drift.append(f"{_lab10[k]} attendu **{exp[k]}**") + if drift: + bad("Template canonique · (3) intro #10 (l.29) DÉRIVE de CLAUDE.md #10 : " + f"{' · '.join(drift)} → aligner le template (ou CLAUDE.md)") + else: + good("Template canonique · (3) intro #10 (l.29) — devises/format/paiement " + "== CLAUDE.md #10") + # (4) §5.2 (l.149-151) · les 3 lignes de table « **valeur** (… contrainte #10) » + drift = [] + # devises + format : « **valeur** (contrainte #10) » + for k in ("devises", "format_doc"): + if not re.search(r"\*\*\s*" + re.escape(exp[k]) + r"\s*\*\*\s*\(contrainte #10\)", + tpl): + drift.append(f"ligne {_lab10[k]} attendue « **{exp[k]}** (contrainte #10) »") + # paiement : « **valeur** (❌ · contrainte #10) » — cite l'exclusion #10 + pay_re = (r"\*\*\s*" + re.escape(exp["paiement"]) + r"\s*\*\*\s*\(❌\s*" + + (re.escape(exp_excl) if exp_excl else r"\S+") + r"\s*·\s*contrainte #10\)") + if not re.search(pay_re, tpl): + drift.append(f"ligne paiement attendue « **{exp['paiement']}** (❌ " + f"{exp_excl or '?'} · contrainte #10) »") + if drift: + bad("Template canonique · (4) §5.2 lignes #10 DÉRIVENT de CLAUDE.md #10 : " + f"{' · '.join(drift)} → aligner le template (ou CLAUDE.md)") + else: + good("Template canonique · (4) §5.2 — 3 lignes « (… contrainte #10) » (devises · " + f"format · paiement ❌ {exp_excl}) == CLAUDE.md #10") + # (5) §7 checklist (l.196) · énumération des 6 marqueurs canoniques + _want6_tpl = {_pnorm(exp[k]) for k in _num_keys} | {_ai(exp[k]) for k in _txt_keys} + m5 = re.search(r"Param[èe]tres canoniques\*\* \| (.+?) pr[ée]sents", tpl) + if m5 is None: + bad("Template canonique · (5) §7 checklist — énumération « 3 %/…/Cardnet " + "présents » INTROUVABLE") + else: + got5 = set() + for tok in re.split(r"[/·]", m5.group(1)): + p = _pct(tok) + got5.add(_pnorm(p) if p is not None else _ai(tok)) + if got5 == _want6_tpl: + good("Template canonique · (5) §7 checklist — énumère les 6 marqueurs == " + "CLAUDE.md #9/#10") + else: + miss = sorted(_want6_tpl - got5); extra = sorted(got5 - _want6_tpl) + bad(f"Template canonique · (5) §7 checklist — énumère {sorted(got5)} MAIS " + f"CLAUDE.md {sorted(_want6_tpl)}" + + (f" · absents={miss}" if miss else "") + + (f" · en trop={extra}" if extra else "") + " → régénérer le template") + # ============================================================================ # Fiscal · ecf_dgii — le JEU DE DEVISES `moneda` (`USD`/`DOP`) ANCRÉ sur CLAUDE.md # #10 — même classe d'ANCRAGE que la CITATION #10 des fiches ci-dessus, mais sur