[DTP-Worker 20260802_000501] Sprint 8 · buffer · Publiciste : gate d'ANCRAGE des DEVISES du site public sur CLAUDE.md #10

Le bloc Publiciste/branding existant ancre les 4 tokens DESIGN (#4 : couleurs +
typographies). Le MÊME module porte une 2e constante constitutionnelle NON gatée :
les devises CLAUDE.md #10 (« **USD + DOP** »), recopiées à 4 endroits data-derived,
tous invisibles à check_artifacts (pas un out/*.json) : (a) branding.py
DEVISE_PRIMAIRE/SECONDAIRE (auto-étiquetées « #10 · USD + DOP » sans gate) ; (b)
generator.py _fmt_usd/_fmt_dop — les formateurs qui IMPRIMENT le code devise sur
la grille de prix du site public vente.otov7.com (sortie face-public) ; (c) footer
HTML « Prix … en USD + DOP (Cardnet) » ; (d) l'oracle hardcodé
assertIn("USD 150 000") du test — une copie jamais comparée à CLAUDE.md. Aucune
suite tests/ (FONCTIONS de rendu, pas l'ancre à CLAUDE.md) n'attrape la dérive.

Nouveau bloc : RE-PARSE les 2 devises depuis la ligne #10 de CLAUDE.md
(indépendamment de exp amont · aucun skip hérité), exige (a) constantes branding ==
#10 ordre inclus ; (b) ensemble des codes émis par les formateurs == #10 ; (c)
footer cite les 2 devises dans l'ordre + (Cardnet) ; (d) oracle test == devise
primaire. Claim absent échoue AUSSI (#6). 6 morsures vérifiées (M5 : rename #10
CLAUDE.md-only cascade sur les 4 copies = preuve ancre live), 4 gates re-verts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-02 00:11:27 +00:00
parent 7e674f2f09
commit 1485e4d6ac
2 changed files with 159 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
# Activity log · 2026-08-02 · Claude Code DTP Worker
## 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`) :
1. `lib/branding.py` `DEVISE_PRIMAIRE`/`DEVISE_SECONDAIRE` — constantes auto-
étiquetées « contrainte #10 · USD + DOP », sans gate ;
2. `lib/generator.py` `_fmt_usd`/`_fmt_dop` — les **formateurs qui IMPRIMENT le
code devise sur la grille de prix du site public** `vente.otov7.com` (sortie
face-public : une devise hors #10 = mandat trahi en silence) ;
3. `lib/generator.py` footer HTML « Prix … en USD + DOP (Cardnet) » ;
4. `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.py` DEVISE_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.
+118
View File
@@ -6131,6 +6131,124 @@ if b4 is not None:
+ (f" · absents={miss}" if miss else "")
+ (f" · en trop={extra}" if extra else "") + " → aligner le test")
# ============================================================================
# Publiciste/generator+branding · DEVISES CANONIQUES du site public « ancrées
# sur CLAUDE.md #10 » (USD + DOP) — les libellés monétaires RÉELLEMENT ÉMIS sur
# vente.otov7.com, jamais gatés.
# ----------------------------------------------------------------------------
# Le bloc branding #4 ci-dessus ancre les 4 tokens DESIGN. Mais le MÊME module
# porte une 2ᵉ constante constitutionnelle NON gatée : les DEUX devises de
# CLAUDE.md #10 (« **USD + DOP** devises »), recopiées à QUATRE endroits data-
# derived, tous invisibles à check_artifacts (pas un out/*.json) et jamais
# confrontés à #10 :
# (a) lib/branding.py DEVISE_PRIMAIRE/DEVISE_SECONDAIRE — constantes AUTO-
# étiquetées « contrainte #10 · USD + DOP », sans aucun gate ;
# (b) lib/generator.py `_fmt_usd`/`_fmt_dop` — les DEUX formateurs qui
# IMPRIMENT le code devise sur la grille de prix du site public
# (`f"USD {value:,.0f}"` / `f"DOP {value:,.0f}"`) ; c'est la SORTIE
# FACE-PUBLIC : une devise HORS #10 ici = mandat trahi en silence (#6) ;
# (c) lib/generator.py footer (émis dans le HTML) « Prix … en USD + DOP
# (Cardnet) » — la prose du pied de page du site ;
# (d) tests/test_publiciste.py — l'oracle HARDCODÉ « assertIn("USD 150 000",
# …) » : une COPIE de plus de la devise primaire, JAMAIS comparée à #10.
# Aucune suite tests/ (qui teste des FONCTIONS de rendu, pas l'ancre à
# CLAUDE.md) n'attrape la dérive : renommer la devise #10 (USD→EUR) et aligner
# le module reste vert alors que le site émettrait la mauvaise devise ; ne RIEN
# bouger reste vert alors que branding se dit « #10 » en mentant. Même patron
# EXACT que le bloc branding/#4 et faisabilite/#9-#10. On RE-PARSE les 2 devises
# depuis la ligne #10 de CLAUDE.md (indépendamment de `exp` amont · aucun skip
# hérité) et on exige : (a) les 2 constantes == #10 (ordre inclus) ; (b)
# l'ensemble des codes émis par les formateurs == #10 ; (c) le footer cite les 2
# devises dans l'ordre ; (d) l'oracle du test == devise primaire. Un claim
# absent échoue AUSSI (traçabilité #6).
# ============================================================================
try:
cm_raw10p = open("CLAUDE.md", encoding="utf-8").read()
l10p = re.search(r"^10\.\s+(.+)$", cm_raw10p, re.M)
if not l10p:
raise ValueError("contrainte #10 INTROUVABLE")
seg10p = [s for s in re.split(r"·", l10p.group(1)) if "devise" in _ai(s)]
mdev = re.search(r"\*\*(.+?)\*\*", seg10p[0]) if seg10p else None
if not mdev:
raise ValueError("segment devises #10 (« **… devises** ») illisible")
pub_cur = re.findall(r"[A-Z]{3}", mdev.group(1))
if len(pub_cur) != 2:
raise ValueError(f"attendu 2 devises en gras, lu {pub_cur}")
except (OSError, ValueError) as e:
bad(f"Publiciste devises · CLAUDE.md #10 illisible/inattendu : {e}"); pub_cur = None
if pub_cur is not None:
good(f"Publiciste devises · CLAUDE.md #10 sain : primaire={pub_cur[0]} "
f"secondaire={pub_cur[1]}")
PUB = f"{D}/publiciste"
# (a) ANCRAGE : branding.py DEVISE_PRIMAIRE/SECONDAIRE == #10 (ordre inclus) ---
BRD = f"{PUB}/lib/branding.py"
try:
brd_src = open(BRD, encoding="utf-8").read()
except OSError as e:
bad(f"Publiciste devises · {BRD} illisible : {e}"); brd_src = None
if brd_src is not None:
dev_pat = {
"primaire": (r'DEVISE_PRIMAIRE\s*=\s*"([^"]+)"', pub_cur[0]),
"secondaire": (r'DEVISE_SECONDAIRE\s*=\s*"([^"]+)"', pub_cur[1]),
}
ddrift = []
for k, (pat, want) in dev_pat.items():
m = re.search(pat, brd_src)
got = m.group(1) if m else None
if got != want:
ddrift.append(f"{k}={got!r} (attendu {want!r})")
if ddrift:
bad(f"Publiciste devises · branding.py CONSTANTES DÉRIVENT de CLAUDE.md #10 : "
f"{' · '.join(ddrift)} → aligner branding.py (ou CLAUDE.md)")
else:
good(f"Publiciste devises · branding.py DEVISE_PRIMAIRE/SECONDAIRE == "
f"CLAUDE.md #10 ({pub_cur[0]}/{pub_cur[1]})")
# (b) SORTIE : les formateurs generator.py émettent EXACTEMENT les 2 devises #10
GEN = f"{PUB}/lib/generator.py"
try:
gen_src = open(GEN, encoding="utf-8").read()
except OSError as e:
bad(f"Publiciste devises · {GEN} illisible : {e}"); gen_src = None
if gen_src is not None:
emis = re.findall(r'return f"([A-Z]{3}) \{value:[^}]+\}"', gen_src)
if set(emis) != set(pub_cur):
miss = sorted(set(pub_cur) - set(emis)); extra = sorted(set(emis) - set(pub_cur))
bad(f"Publiciste devises · formateurs generator.py émettent {sorted(set(emis))} "
f"MAIS CLAUDE.md #10 = {sorted(set(pub_cur))}"
+ (f" · absents={miss}" if miss else "")
+ (f" · HORS #10={extra}" if extra else "")
+ " → le site public émettrait une devise non conforme (#6)")
else:
good(f"Publiciste devises · formateurs generator.py émettent {sorted(set(emis))} "
f"== CLAUDE.md #10")
# (c) FOOTER émis dans le HTML : « en <D1> + <D2> (Cardnet) » ordre inclus ---
mft = re.search(r"en ([A-Z]{3}) \+ ([A-Z]{3}) \(Cardnet\)", gen_src)
if mft is None:
bad(f"Publiciste devises · {GEN} — pied de page « en <D1> + <D2> (Cardnet) » "
f"INTROUVABLE (claim absent = régression · #6)")
elif [mft.group(1), mft.group(2)] == pub_cur:
good(f"Publiciste devises · footer generator.py cite {pub_cur[0]} + {pub_cur[1]} "
f"== CLAUDE.md #10")
else:
bad(f"Publiciste devises · footer generator.py cite {mft.group(1)} + {mft.group(2)} "
f"MAIS CLAUDE.md #10 = {pub_cur[0]} + {pub_cur[1]} → aligner le footer")
# (d) ORACLE du test : « assertIn("<D1> 150 000", self.html) » == devise primaire
TP = f"{PUB}/tests/test_publiciste.py"
try:
tp_src = open(TP, encoding="utf-8").read()
except OSError as e:
bad(f"Publiciste devises · {TP} illisible : {e}"); tp_src = None
if tp_src is not None:
mto = re.search(r'assertIn\("([A-Z]{3}) [\d ]+", self\.html\)', tp_src)
if mto is None:
bad(f"Publiciste devises · {TP} — oracle « assertIn(\"<devise> …\", self.html) » "
f"INTROUVABLE (le contrôle prix du test a disparu · régression)")
elif mto.group(1) == pub_cur[0]:
good(f"Publiciste devises · {TP} oracle prix == devise primaire #10 ({pub_cur[0]})")
else:
bad(f"Publiciste devises · {TP} oracle prix cite {mto.group(1)!r} MAIS devise "
f"primaire #10 = {pub_cur[0]!r} → aligner le test")
# ============================================================================
# Fiches agents · colonne « Job CI » — chaque nom de job cité DOIT exister dans
# .gitea/workflows/ci.yml (même classe de « vert trompeur », côté câblage CI).