Nouvelle surface anti-dérive (#6) : la table §3 « Cartographie portails ↔ familles » porte deux colonnes data-derived du contrat ; « Nb rôles » était gatée, « Familles rattachées » (SET des `famille` distinctes par portail) ne l'était pas — le count-gate l'avale via `[^|]*`. Gate « 1quater-bis » : recompute le SET de familles par portail depuis rbac_50_roles.json[roles][].famille, égalité par ENSEMBLE (absent + fantôme) avec les jetons bruts de la cellule (parenthèses éditoriales exclues, ordre non gaté). Silent-green prouvé : reclasser les 2 rôles `legal` de `direction` en `finance` garde le compte 9==9 VERT mais fait mordre FANTÔME {legal}. 6 verts arbre propre · 5 morsures adversariales · 6 gates re-verts exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
30 KiB
Activity log · 2026-08-02 · Claude Code DTP Worker
Sprint 8 · buffer · RBAC SPEC §3 — ancrage de la colonne « Familles rattachées » PAR portail sur le contrat
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). La table §3 « Cartographie portails ↔ familles » de
05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md porte DEUX colonnes data-derived
du contrat rbac_50_roles.json : « Nb rôles » (déjà gatée, bloc §3 count) et —
juste à sa gauche — « Familles rattachées » : pour chaque portail, l'ENSEMBLE
des famille distinctes de ses rôles (ex. direction → {direction, finance,
faisabilite, legal}). Cette 2ᵉ colonne était NON gatée : le bloc §3 count la
capture avec [^|]* (il ne lit QUE le compte), le §5 « résumé » traite volontairement
les regroupements comme ÉDITORIAUX (pas de mapping famille↔groupe, cf. memory
rbac-spec-portail-count-gate), et check_artifacts est aveugle (la SPEC n'est
pas un out/*.json). Même classe que « colonne SET gatée seulement sur son COUNT
voisin » (roleprofile per-profil count vs quels rôles).
Le « vert trompeur » — reclasser la famille d'un rôle À L'INTÉRIEUR d'un
portail change le SET de familles sans changer le « Nb rôles » du portail → la
cellule §3 ment en silence pendant que le count-gate reste VERT. Prouvé (M4) :
reclasser les DEUX rôles legal de direction (Directeur Juridique + Agent ONAPI)
en finance laisse le compte à 9==9 VERT tandis que le nouveau gate mord
FANTÔME {legal} (la SPEC liste encore legal, le contrat ne l'a plus).
Ajouter/retirer une famille (rôle d'une famille inédite) laisse aussi la cellule
périmée. Aucune suite tests/ (FONCTIONS de validation, pas l'ancre SPEC↔contrat)
ne mord.
Gate ajouté (ci/check_readme_claims.sh, bloc « 1quater-bis » juste après le
§3 count-gate, RÉUTILISE src_roles/spec_rd déjà chargés) — RECOMPUTE le SET de
familles distinctes PAR portail depuis rbac_50_roles.json[roles][].famille et
exige, par ENSEMBLE (absent ET fantôme), l'égalité avec les jetons famille de la
cellule. On ne gate QUE les jetons famille bruts (identifiant de tête de chaque
item séparé par ,, via (?:^|,)\s*([a-z]+)), PAS les parenthèses ÉDITORIALES
(« finance (CFO) », « faisabilite (analyste/rendu/IFC/économiste) » — paraphrase,
pas contrat, #6). Ordre NON gaté (réordonner les rôles dans le contrat byte-gaté
est sémantiquement neutre pour l'appartenance). Un portail sans cellule échoue AUSSI
(INTROUVABLE = régression de traçabilité #6).
Vérif — 6 checks verts sur l'arbre propre (un par portail). 5 morsures
adversariales : M1 SPEC retire legal de direction ⇒ MANQUE {legal} · M2 SPEC
ajoute rh à achat ⇒ FANTÔME {rh} · M3 SPEC finance→financ sur compta ⇒
MANQUE {finance} · FANTÔME {financ} · M4 SILENT-GREEN : contrat reclasse les 2
rôles legal→finance dans direction, compte inchangé (count-gate 9==9 VERT) ⇒
familles-gate mord FANTÔME {legal} — preuve que l'autorité est le CONTRAT, pas la
cohérence interne de la SPEC · M5 SPEC supprime la ligne direction ⇒ cellule
INTROUVABLE. Gates re-verts : check_artifacts · check_ci_integrity ·
check_docs · check_readme_claims · check_regression · guard_constraints
tous exit 0.
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 :
- l.28 intro §0 « frais 3 % · marketing 8.5 % · point d'équilibre 52 % » (3 pourcentages #9, associés par mot-clé) ;
- l.144-146 §5.2 — 3 lignes de table « N % (fixe · contrainte #9) » ;
- l.29 intro §0 « USD + DOP · format Letter US · paiements Cardnet » ;
- l.149-151 §5.2 — 3 lignes « valeur (… contrainte #10) » (la ligne paiement citant l'exclusion « ❌ pas-X ») ;
- 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
chiffres »). Le bloc « Fiscal · e-CF DGII · CONTRAT DE FORMAT » existant ancre le
format de l'identifiant fiscal DGII (E + tipoeCF(2) + secuencia(10) = 13,
Ley 32-23) sur le SEUL bloc e_ncf byte-gaté de out/ecf_plan.json (dérivé de
ecf_spec.json) et couvre 4 surfaces de PROSE : README ×3 + fiche
erpnext_backend. Mais le composeur d'exécution lui-même — ecflib/ncf.py,
le code Frappe qui VALIDE et COMPOSE réellement le numéro e-NCF côté VPS/Compupar
— porte TROIS copies INDÉPENDANTES du format, AUCUNE dérivée de l'artefact, +
un oracle de test qui les bénit, tous NON gatés :
ENCF_RE = re.compile(r"^E([0-9]{2})([0-9]{10})$")(l.26) — la regex de VALIDATION (cœur deis_valid_encf/parse_encf) ;FORMULE = "E + tipoeCF(2) + secuencia(10)"(l.27) — la chaîne publiée parcompose_encf(renvoyée au caller VPS) ;- docstring l.8 «
"E" + tipoeCF (2 chiffres) + secuencia (10 chiffres) → 13 caracteres» + commentaire l.25 « Ley 32-23 : E + tipo(2) + secuencia(10) = 13 » (deux proses-en-code) ; tests/test_ecf_dgii.py:246— oracle hardcodéassertEqual(…["formule"], "E + tipoeCF(2) + secuencia(10)").
Le « vert trompeur » — un rango DGII à 12 chiffres (évènement ops réel, cité
dans le commentaire du gate existant) régénère ecf_spec→ecf_plan, donc les 4
proses gatées SUIVENT. Mais ENCF_RE reste {2}{10} : compose_encf zfill à 10
et is_valid_encf REJETTE un e-NCF désormais valide → bug d'exécution du
composeur fiscal, pendant que la suite tests/ reste VERTE (son oracle ==
FORMULE périmée, self-consistante). Ni check_artifacts (ncf.py n'est pas un
out/*.json) ni tests/ (teste les FONCTIONS, pas leur ANCRAGE à l'artefact) ne
mordent. Même patron que l'oracle/formateur du Publiciste déjà gaté.
Gate ajouté (ci/check_readme_claims.sh, extension du bloc e-NCF · surfaces
5→9) — RÉUTILISE prefix/tipo_len/seq_len/total déjà dérivés du bloc e_ncf
byte-gaté + le helper ecf_check (zéro re-dérivation du format #6). Ancre les 5
surfaces CODE/test au SEUL artefact : (5) ENCF_RE, (6) FORMULE, (7) docstring,
(8) commentaire, (9) oracle de test. Une surface absente échoue AUSSI (INTROUVABLE
· anti-évaporation #6).
Vérif — 5 checks verts sur l'arbre propre. 7 morsures adversariales :
M1 ENCF_RE {10}→{12} · M2 FORMULE secuencia(10)→(12) · M3 docstring 10→12 chiffres · M4 commentaire secuencia(10)→(12) · M5 oracle test (10)→(12) ·
M6 SILENT-GREEN — l'ARTEFACT passe à seq=12 pendant que ncf.py reste à 10 ⇒
les 5 surfaces mordent TOUTES (preuve que l'autorité est l'artefact byte-gaté, pas
la cohérence interne du composeur) · M7 FORMULE renommée ⇒ INTROUVABLE. Les
39 tests unittest e-CF restent verts (composeur inchangé). Gates re-verts :
check_artifacts · check_ci_integrity · check_docs · check_readme_claims ·
check_regression · guard_constraints tous exit 0.
Sprint 8 · buffer · CRM Commissions — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (7ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO, Fiscal e-CF, Legal CONFOTUR,
CRM Dossier Vente et QA Audit 5D. Le 7ᵉ générateur — dernier candidat
restant recensé au ledger (crm/commissions ·10) — était NON gaté :
crm/commissions/commissions_gen.py (barème de commissions vendeurs · roadmap l.51).
Le compte « 10 invariants » y est transcrit à la main à CINQ endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- message de succès CLI
validate« … schéma + 10 invariants verts. » (l.209) ; - aide du sous-parseur
validate« schéma + 10 invariants » (l.221) ; - README bloc de commande « schéma + 10 invariants » (l.60) ;
- README titre de section « ## Les 10 invariants » (l.64) ;
- README prose de score « 10 invariants de cross-cohérence » (l.92).
Ajouter un contrôle # 11 · à _validate sans toucher les 5 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 11
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre Commissions (≈ Dossier Vente) : le ledger est PROPRE 1..10 — le schéma est compté À PART (phrasé « schéma + N invariants »), donc pas de schéma-#1 non marqué et pas de split a/b ; on garde la détection de doublon nu comme filet. Le module porte AUSSI un SECOND nombre — « 25 tests » (la suite unittest, déjà gatée par ailleurs) présent dans le README : l'ancre sur le mot « invariants » l'exclut nativement (« 25 tests » ≠ « invariants ») ⇒ aucun faux positif. Comme Dossier Vente, ce README A un titre « ## Les N invariants » ET une énumération numérotée du ledger → on gate (a)(b)(c)(d)(e).
Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · Commissions · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (c) l'annonce du titre README « ## Les N invariants » == |ledger| ;
- (d) toute mention « N invariants » du README == |ledger| ;
- (e) l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N. Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).
Vérif — 5 checks verts sur l'arbre propre (ledger 1..10 = 10 marqueurs
# N ·, schéma compté à part). 7 morsures adversariales : M1 self-report
.py 10→11 (b) · M2 titre README 10→11 (c+d) · M3 prose README 10→11 (d) · M4
ajout marqueur # 11 · ⇒ ledger→11 pendant que la prose reste 10 (mord .py ET
README titre/mention ET l'énumération, preuve que l'autorité est le CODE) · M5
suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de
vrai doublon (a) · M7 énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Le distinct
« 25 tests » reste INVISIBLE au gate (ancre « invariants »). Gates re-verts :
check_artifacts · check_docs · guard_constraints · check_ci_integrity ·
check_regression · check_readme_claims tous exit 0. Série ledger close : les
7 générateurs portant le nombre magique d'invariants sont désormais tous gatés.
Sprint 8 · buffer · QA Audit 5D — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (6ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO, Fiscal e-CF, Legal CONFOTUR et
CRM Dossier Vente. Le 6ᵉ générateur portant le même nombre magique restait
NON gaté : qa/audit_5d/audit_5d_gen.py (candidat restant recensé : crm/commissions
·10, ledger propre non gaté).
Le compte « 15 invariants » y est transcrit à la main à SIX endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- docstring sous-commande module (l.19 « valide schéma + 15 invariants ») ;
- docstring
_validate(l.80 « 15 invariants de cohérence audit/contrôles ») ; - message de succès CLI
validate« … 15 invariants — verdict … » (l.218) ; - aide du sous-parseur
validate« valide schéma + 15 invariants » (l.229) ; - README bloc de commande « schéma + 15 invariants » (l.66) ;
- README prose d'architecture « CLI build/validate + 15 invariants » (l.81).
Ajouter un contrôle # 16 · à _validate sans toucher les 6 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 16
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre Audit 5D (≈ Dossier Vente, ≠ Fiscal) : le ledger est
PROPRE 1..15 — le schéma est compté À PART (phrasé « schéma + N invariants »),
donc pas de schéma-#1 non marqué et pas de split a/b ; on garde la détection
de doublon nu comme filet. MAIS le module porte un SECOND nombre distinct —
« 17 contrôles » (le compte de fonctions-contrôle de qalib/controls.py, déjà
gaté par ailleurs), présent dans le .py ET le README : un \d+ invariants ancré
sur le mot « invariants » l'exclut nativement (« 17 contrôles » ≠ mot
« invariants ») ⇒ aucun faux positif sur l'arbre propre. Contrairement à
Dossier Vente/Fiscal, ce README n'a NI titre « ## Les N invariants » NI
énumération numérotée du ledger → on ne gate que (a)(b)(d).
Gate ajouté (ci/check_readme_claims.sh, bloc « QA · Audit 5D · IDENTITÉ du
COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (d) toute mention « N invariants » du README == |ledger|. Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).
Vérif — 3 checks verts sur l'arbre propre (ledger 1..15 = 15 marqueurs # N ·,
schéma compté à part). 6 morsures adversariales : M1 self-report .py 15→16
(b) · M2 mention README 15→16 (d) · M3 ajout marqueur # 16 · ⇒ ledger→16 pendant
que la prose reste 15 (mord .py ET README, preuve que l'autorité est le CODE) ·
M4 suppression # 7 · ⇒ gap non-contigu (a) · M5 doublon nu # 5 · ⇒ détection de
vrai doublon (a) · M6 no-false-positive : le distinct « 17 contrôles » reste
INVISIBLE au gate (l'ancre « invariants » l'exclut, arbre propre VERT). Gates
re-verts : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_regression · check_readme_claims tous exit 0.
Sprint 8 · buffer · CRM Dossier Vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (5ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO, Fiscal e-CF et Legal CONFOTUR. Le
5ᵉ générateur portant le même nombre magique restait NON gaté :
crm/dossier_vente/doctype_dossier_vente_gen.py (candidats restants recensés :
qa/audit_5d ·15, crm/commissions ·10, tous ledgers propres non gatés).
Le compte « 12 invariants » y est transcrit à la main à SEPT endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- docstring module (l.14 « schéma + 12 invariants de ») ;
- docstring
_validate(l.77 « 12 invariants de cross-cohérence ») ; - message de succès CLI
validate« … schéma + 12 invariants verts. » (l.218) ; - aide du sous-parseur
validate(l.230) ; - README bloc de commande « schéma + 12 invariants » (l.56) ;
- README titre de section « ## Les 12 invariants » (l.60) ;
- README prose de score « 12 invariants de cross-cohérence » (l.83).
Ajouter un contrôle # 13 · à _validate sans toucher les 7 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 13
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre Dossier Vente (≈ CONFOTUR, ≠ Fiscal) : le ledger est PROPRE 1..12 — le schéma est compté À PART (phrasé « schéma + N invariants »), donc pas de schéma-#1 non marqué et pas de split a/b. On garde néanmoins la détection de doublon nu comme filet. Le README porte AUSSI une énumération numérotée sous le titre de section (item 1 = conformité au schéma, puis les contrôles), gatée en contiguïté 1..N.
Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · Dossier Vente · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (c) l'annonce du titre README « ## Les N invariants » == |ledger| ;
- (d) toute mention « N invariants » du README == |ledger| ;
- (e) l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N. Une mention ou le registre absent échoue AUSSI (traçabilité #6).
Vérif — 5 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs # N ·,
schéma compté à part). 7 morsures adversariales : M1 self-report .py 12→13
(b) · M2 titre README 12→13 (c+d) · M3 prose README 12→13 (d) · M4 ajout marqueur
# 13 · ⇒ ledger→13 pendant que la prose reste 12 (mord .py ET README ET
l'énumération, preuve que l'autorité est le CODE) · M5 suppression # 7 · ⇒ gap
non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de vrai doublon (a) · M7
énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Gates re-verts : check_artifacts
· check_docs · guard_constraints · check_ci_integrity · check_regression ·
check_readme_claims tous exit 0.
Sprint 8 · buffer · Legal CONFOTUR — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (4ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO et Fiscal e-CF. Le 4ᵉ générateur
portant le même nombre magique restait NON gaté :
legal/confotur/confotur_application_gen.py.
Le compte « 14 invariants » y est transcrit à la main à SIX endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- docstring module (l.24 « schéma + 14 invariants de ») ;
- docstring
_validate(l.87 « 14 invariants de cross-coherence ») ; - message de succès CLI
validate« … schéma + 14 invariants verts. » (l.283) ; - aide du sous-parseur
validate(l.295) ; - README bloc de commande « schéma + 14 invariants » (l.56) ;
- README commentaire d'arborescence « CLI build/validate · 14 invariants » (l.82).
Ajouter un contrôle # 15 · à _validate sans toucher les 6 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 15
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre CONFOTUR (≠ Fiscal) : le ledger est PROPRE 1..14
(pas de schéma-#1 non marqué, pas de split a/b). MAIS le README porte AUSSI un
compte de SOUS-ENSEMBLE éditorial — « 2 invariants refusent tout champ
monétaire » (l.92) — à NE PAS confondre avec le total. Un \d+ invariants naïf
mordrait donc l'arbre PROPRE (il verrait {2, 14}). Le gate exclut le
sous-ensemble par lookahead négatif (\d+)\s+invariants(?!\s+refusent),
appliqué au .py comme au README.
Gate ajouté (ci/check_readme_claims.sh, bloc « Legal · CONFOTUR · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention-total « N invariants » du
.py(hors « refusent ») == |ledger| ; - (c) toute mention-total du README (hors « refusent ») == |ledger|. Une mention ou le registre absent échoue AUSSI (disparition d'un chiffre = régression de traçabilité #6).
Vérif — 3 checks verts sur l'arbre propre (ledger 1..14 = 14 marqueurs # N ·).
6 morsures adversariales : M1 self-report .py 14→15 (b) · M2 README commande
14→15 (c) · M3 README arborescence 14→13 (c) · M4 ajout marqueur # 15 · ⇒
ledger→15 pendant que la prose reste 14 (mord .py ET README, preuve que
l'autorité est le CODE) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6
doublon nu # 5 · ⇒ détection de vrai doublon (a). M7 no-false-positive :
sous-ensemble « 2 → 3 invariants refusent » reste VERT (le lookahead exclut bien
le sous-ensemble). Gates re-verts : check_artifacts · check_docs ·
guard_constraints · check_ci_integrity · check_regression ·
check_readme_claims tous exit 0.
Sprint 8 · buffer · Publiciste — gate d'ANCRAGE des DEVISES du site public sur CLAUDE.md #10
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le bloc Publiciste/branding existant ancre les 4 tokens DESIGN
de CLAUDE.md #4 (2 couleurs + 2 typographies) sur leurs 4 copies. Mais le MÊME
module publiciste porte une 2ᵉ constante constitutionnelle — les devises
CLAUDE.md #10 (« USD + DOP ») — recopiée à 4 endroits data-derived, aucun
gaté, tous invisibles à check_artifacts (pas un out/*.json) :
lib/branding.pyDEVISE_PRIMAIRE/DEVISE_SECONDAIRE— constantes auto- étiquetées « contrainte #10 · USD + DOP », sans gate ;lib/generator.py_fmt_usd/_fmt_dop— les formateurs qui IMPRIMENT le code devise sur la grille de prix du site publicvente.otov7.com(sortie face-public : une devise hors #10 = mandat trahi en silence) ;lib/generator.pyfooter HTML « Prix … en USD + DOP (Cardnet) » ;tests/test_publiciste.py— l'oracle hardcodéassertIn("USD 150 000", …), une copie de plus jamais comparée à CLAUDE.md.
Aucune suite tests/ (qui teste des fonctions de rendu, pas l'ancre à
CLAUDE.md) n'attrape la dérive : renommer la devise #10 et aligner le module
reste vert alors que le site émettrait la mauvaise devise ; ne rien bouger reste
vert alors que branding.py se dit « #10 » en mentant.
Gate ajouté (ci/check_readme_claims.sh, bloc « Publiciste/generator+branding
· DEVISES CANONIQUES ») — RE-PARSE les 2 devises depuis la ligne #10 de CLAUDE.md
(indépendamment de exp amont · aucun skip hérité) puis exige :
- (a)
branding.pyDEVISE_PRIMAIRE/SECONDAIRE == #10 (ordre inclus) ; - (b) l'ensemble des codes émis par les formateurs
generator.py== #10 ; - (c) le footer HTML cite les 2 devises dans l'ordre +
(Cardnet); - (d) l'oracle du test == devise primaire #10. Un claim absent échoue AUSSI (traçabilité #6 · anti-évaporation).
Vérif — 5 checks verts sur l'arbre propre. 6 morsures adversariales :
M1 branding primaire USD→EUR · M2 formateur _fmt_usd→EUR (hors #10) · M3 footer
USD+DOP→USD+EUR · M4 oracle test → EUR · M5 CLAUDE.md #10 USD→EUR = cascade sur
les 4 copies simultanément (preuve que l'ancre est CLAUDE.md, pas la cohérence
interne) · M6 footer supprimé → INTROUVABLE. Gates re-verts :
check_readme_claims · check_docs · guard_constraints · check_ci_integrity
tous exit 0.
Sprint 8 · buffer · Fiscal e-CF DGII : gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » a déjà été appliqué à Chat
OTOIA et SEO (recompute le nombre d'invariants CI depuis le registre-source
# N · de _validate, jamais une liste à la main). Le 3ᵉ générateur portant
le même nombre magique restait NON couvert : fiscal/ecf_dgii/ecf_dgii_gen.py.
Le compte « 12 invariants » y est transcrit à la main à SEPT endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
ecf_dgii_gen.pydocstring module (l.24) ;- docstring
_validate(l.110) ; - message de succès CLI
validate« … schéma + 12 invariants verts. » (l.277) ; - aide du sous-parseur
validate(l.289) ; - README bloc de commande « schéma + 12 invariants » (l.65) ;
- README titre de section « ## Les 12 invariants » (l.69) ;
- README prose de score « 12 invariants de cross-cohérence » (l.105).
Ajouter un contrôle # 13 · à _validate sans toucher les 7 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 13
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre fiscal (≠ SEO/Chat OTOIA au ledger propre 1..N) :
l'invariant 1 est la validation de SCHÉMA (validate(bundle, schema), non
marquée # 1 ·), les marqueurs couvrent 2..N, et l'invariant 12 est
splitté en # 12a · (unicité évènements) / # 12b · (comptes du manifeste) —
deux volets d'un même invariant. Le gate reconstruit donc le ledger = {1:schéma}
∪ marqueurs-top-level, en collapsant 12a/12b, tout en mordant un vrai doublon
d'un numéro NON suffixé.
Gate ajouté (ci/check_readme_claims.sh, bloc « Fiscal · e-CF DGII · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (schéma #1 présent + marqueurs 2..N sans lacune ; doublon nu = défaut mordu ; 12a/12b comptés 1×) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (c) l'annonce du titre README == |ledger| ;
- (d) toute mention « N invariants » du README == |ledger| ;
- (e) l'ÉNUMÉRATION numérotée du README (« 1. … 2. … N. ») elle-même contiguë 1..N (item ni sauté ni dupliqué).
Vérif — 5 checks verts sur l'arbre propre (ledger 1..12 = schéma #1 + 11
marqueurs). 8 morsures adversariales : M1 self-report .py 12→13 (b) · M2
titre README 12→13 (c+d) · M3 mention README 12→13 (d) · M4 ajout marqueur # 13 ·
⇒ ledger→13 pendant que la prose reste 12 (b+c+d, preuve que l'autorité est le
CODE, pas la prose) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu
# 5 · ⇒ détection de vrai doublon · M7 énumération README 7.→8. ⇒ deux 8/pas de
7 (e) · M8 suppression validate(bundle, schema) ⇒ invariant 1 introuvable. Gates
re-verts : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_readme_claims · check_regression tous exit 0.