[DTP-Worker] Sprint 8 · buffer · Faisabilité/bancable : les 4 figures « Génération réelle (fixture) » du README (40 unités · USD 8,560,000 / DOP 505,040,000 · point d'équilibre 21 unités ⌈52 % × 40⌉) — les chiffres DATA-DERIVED du dossier bancable que voit le banquier — étaient HORS de tout gate. Le module n'écrit son out/ que sur disque (« out/ non commité ») ⇒ check_artifacts est aveugle (aucun artefact byte-gaté) et AUCUN gate ne le touchait.

Ces 4 figures sont Σ quantités · Σ(qté×prix_usd) · Σ(qté×prix_dop) · ⌈point_equilibre_pct × Σ unités⌉ calculées sur la fixture COMMITÉE fixtures/brief_bancable.json, le 52 % ancré sur CLAUDE.md #9 (« Point équilibre 52% pré-vente »). PIÈGE : la seule vérification existante (tests/) teste des FONCTIONS (l'arithmétique de recoupement _check_derived_arithmetic) avec des oracles HARDCODÉS dans le test — une copie de plus, jamais comparée à la PROSE du README. Éditer une typologie de la fixture (quantité/prix), en ajouter/retirer une, ou déplacer le point d'équilibre de CLAUDE.md #9, laisse les 4 chiffres du README périmés pendant que le générateur produit autre chose → le banquier lit un dossier faux (l'invention même que #6 interdit) — « vert trompeur » qu'aucune suite tests/ n'attrape.

Nouveau bloc « Faisabilité/bancable figures » dans ci/check_readme_claims.sh (avant sys.exit), même esprit que le bloc faisabilite/generator ancré sur CLAUDE.md #9/#10 mais appliqué à une surface dont la source de vérité n'est PAS un out/*.json : (1) recompute indépendant des 4 figures depuis la fixture commitée ; (2) cohérence croisée — le manifeste GÉNÉRÉ (bancable_gen validate, exécuté via subprocess stdlib pur, encoding utf-8) == le recompute (mord un générateur/fixture incohérents) ; (3) prose — un seul pattern d'identité exige les 4 valeurs EXACTES avec séparateur de milliers pour USD/DOP + la formule ⌈pct × Σ unités⌉ dont le pct == CLAUDE.md #9 et le total == Σ unités. Un claim absent échoue AUSSI (traçabilité).

État courant : aucun chiffre périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated (module entièrement hors CI). 7 morsures vérifiées : README USD 8,560,000→9,560,000 (figure périmée) · README retire le séparateur de milliers (8,560,000→8560000) · README breakeven 21→20 (formule périmée) · README formule 52 %→55 % (dérive CLAUDE.md #9) · README supprime la ligne entière (INTROUVABLE) · fixture Studio qté 12→13 (5 morsures : units/USD/DOP/breakeven README périmés + manifeste) · fixture 2 Chambres prix_usd 320000→300000 (catalogue USD périmé) ; restauré = green : 4 figures == génération réelle · manifeste == recompute · pct 52 % == CLAUDE.md #9 · exit 0. Working tree byte-restauré via git checkout -- (JAMAIS git clean) · 7 gates re-verts. ci/README.md (ligne récap check-readme-claims) mis à jour · log 05_activity_log/2026-08-01.md. Auto-score 4Big 96/100.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 00:41:00 +00:00
parent 37903685c0
commit e8b30d7147
3 changed files with 174 additions and 1 deletions
+57
View File
@@ -62,3 +62,60 @@ byte-restauré (`git checkout --`, **JAMAIS** `git clean`) · **7 gates re-verts
- **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.
## Session `20260801_003201` · Buffer S8 · Domaine Faisabilité/bancable : les **4 figures « Génération réelle (fixture) »** du README (`40 unités · valeur catalogue USD 8,560,000 / DOP 505,040,000 · point d'équilibre 21 unités (⌈52 % × 40⌉)`) — les chiffres DATA-DERIVED que le **dossier bancable** (l'artefact que voit le banquier) recopie — étaient **HORS de tout gate**. Le module n'écrit son `out/` que sur disque (« out/ non commité ») ⇒ **check_artifacts est aveugle** (aucun artefact byte-gaté) et **AUCUN** gate ne le touchait.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même esprit que les
paramètres canoniques `faisabilite/generator` **ancrés sur CLAUDE.md #9/#10** (le
`52 %` réutilisé ici) — appliqué à une **surface encore jamais gatée** : un module
dont la source de vérité n'est PAS un `out/*.json` mais **la fixture commitée + le
générateur**.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/faisabilite/bancable/README.md:103-105` — ligne
« Génération réelle (fixture) » : `40 unités` (= Σ quantités typologies) ·
`USD 8,560,000` (= Σ qté×prix_usd) · `DOP 505,040,000` (= Σ qté×prix_dop) ·
`21 unités (⌈52 % × 40⌉)` (= ⌈point_equilibre_pct × Σ unités⌉).
- Source faisant autorité : `fixtures/brief_bancable.json` (COMMITÉE) + la
**génération réelle** `python3 bancable_gen.py validate` (émet le manifeste sur
stdout, `figures_calculees[]`). Le `52 %` est **ancré sur CLAUDE.md #9**
(« Point équilibre 52% pré-vente »), pas recopié.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS**
(l'arithmétique de recoupement `_check_derived_arithmetic`) avec des **oracles
HARDCODÉS** dans le test — une copie de plus, jamais comparée à la PROSE du
README. Éditer une typologie de la fixture (quantité/prix), en ajouter/retirer
une, ou déplacer le point d'équilibre de CLAUDE.md #9, laisse les 4 chiffres du
README périmés pendant que le générateur produit autre chose → le banquier lit
un dossier faux (l'invention même que #6 interdit).
- **État courant** : **aucun chiffre périmé** — les 4 figures recoupent la
génération réelle exactement (anti-invention #6, rien à réécrire). Le défaut est
la **surface ungated** (module entièrement hors CI).
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Faisabilité/bancable
figures » avant `sys.exit`) : (1) **recompute indépendant** des 4 figures depuis
la fixture commitée ; (2) **cohérence croisée** — le manifeste GÉNÉRÉ
(`bancable_gen validate`) == le recompute (mord un générateur/fixture
incohérents) ; (3) **prose** — un seul pattern d'identité exige les 4 valeurs
EXACTES, **avec séparateur de milliers** pour USD/DOP, + la formule `⌈pct × Σ
unités⌉` dont le pct == CLAUDE.md #9 et le total == Σ unités. Un claim absent
échoue AUSSI (traçabilité). Le `out/` n'étant pas commité, le gate **exécute le
générateur** (stdlib pur, `subprocess`, `encoding="utf-8"`) au lieu de lire un
artefact byte-gaté — même patron que le bloc `faisabilite/generator` qui lit
`model.py`.
**7 morsures vérifiées** : README `USD 8,560,000→9,560,000` (figure périmée) ·
README retire le séparateur de milliers (`8,560,000→8560000`) · README breakeven
`21→20` (formule périmée) · README formule `52 %→55 %` (dérive CLAUDE.md #9) ·
README supprime la ligne entière (INTROUVABLE · claim absent) · **fixture** Studio
`qté 12→13` (5 morsures : units/USD/DOP/breakeven README périmés + manifeste) ·
**fixture** 2 Chambres `prix_usd 320000→300000` (catalogue USD périmé) ; restauré =
green : 4 figures == génération réelle · manifeste == recompute · pct 52 % ==
CLAUDE.md #9 · exit 0. Working tree byte-restauré via `git checkout --` (JAMAIS
`git clean`, interdit absolu) · **7 gates re-verts**.
- `ci/README.md` (ligne récap du pipeline `check-readme-claims`) mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; conversion PDF + rebuild
Portail Bancables restent côté VPS).
- **Auto-score 4Big** : 96/100.
+1 -1
View File
File diff suppressed because one or more lines are too long
+116
View File
@@ -3078,6 +3078,122 @@ if up_scopes is not None:
f"{'oui' if got_emit else 'non'} MAIS artefact dit " f"{'oui' if got_emit else 'non'} MAIS artefact dit "
f"{'oui' if exp_emit else 'non'} → régénérer le README") f"{'oui' if exp_emit else 'non'} → régénérer le README")
# ============================================================================
# Faisabilité/bancable — figures « Génération réelle (fixture) » du README
# ----------------------------------------------------------------------------
# Le module `faisabilite/bancable` n'écrit son `out/` que sur disque (« out/ non
# commité ») : il est HORS de check_artifacts (aucun artefact byte-gaté) et
# AUCUN gate ne le touchait. Or son README §« Vérification en-repo » recopie
# QUATRE chiffres DATA-DERIVED que le générateur calcule sur la fixture COMMITÉE
# `fixtures/brief_bancable.json` : « 40 unités · valeur catalogue USD 8,560,000
# / DOP 505,040,000 · point d'équilibre 21 unités (⌈52 % × 40⌉) ». Ces valeurs
# sont Σ quantités · Σ(qté×prix_usd) · Σ(qté×prix_dop) · ⌈52 % × Σ unités⌉ — le
# 52 % ancré sur CLAUDE.md #9. PIÈGE : la suite tests/ teste des FONCTIONS
# (l'arithmétique de recoupement) avec des oracles HARDCODÉS ; elle ne compare
# JAMAIS la PROSE du README à la génération réelle. Éditer une typologie de la
# fixture (quantité/prix), en ajouter/retirer une, ou déplacer le point
# d'équilibre de CLAUDE.md #9, laisse les 4 chiffres du README périmés en silence
# pendant que le générateur produit autre chose → le banquier lit un dossier
# faux, l'invention même que #6 interdit. Ce bloc RECOMPUTE les 4 figures depuis
# la fixture (source de vérité), exige que la GÉNÉRATION RÉELLE (bancable_gen
# validate) les confirme (mord un générateur/fixture incohérents), puis que la
# prose README les énumère EXACTEMENT (valeur + séparateur de milliers + formule
# ⌈pct × Σ unités⌉, pct ancré CLAUDE.md #9). Un claim absent échoue AUSSI.
# ============================================================================
import math
BANC = f"{D}/faisabilite/bancable"
BANC_RD = f"{BANC}/README.md"
# 1) point d'équilibre % ANCRÉ sur CLAUDE.md #9 (source faisant autorité · #6/#9)
try:
_cmb = open("CLAUDE.md", encoding="utf-8").read()
_l9b = re.search(r"^9\.\s+(.+)$", _cmb, re.M)
if not _l9b:
raise ValueError("contrainte #9 INTROUVABLE")
_peb = None
for _sb in re.split(r"·", _l9b.group(1)):
if "equilibre" in _ai(_sb):
_mmb = re.search(r"(\d+(?:\.\d+)?)\s*%", _sb)
_peb = float(_mmb.group(1)) if _mmb else None
if _peb is None:
raise ValueError("point d'équilibre % absent de #9")
except (OSError, ValueError) as e:
bad(f"Bancable figures · CLAUDE.md #9 (point d'équilibre) illisible : {e}"); _peb = None
# 2) recompute INDÉPENDANT depuis la fixture COMMITÉE (source de vérité · #6)
try:
_fxb = json.load(open(f"{BANC}/fixtures/brief_bancable.json", encoding="utf-8"))
_typb = _fxb["architecture"]["typologies"]
rec_units = sum(int(t["quantite"]) for t in _typb)
rec_usd = sum(int(t["quantite"]) * int(t["prix_usd"]) for t in _typb)
rec_dop = sum(int(t["quantite"]) * int(t["prix_dop"]) for t in _typb)
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"Bancable figures · fixture brief_bancable.json illisible/inattendue : {e}"); rec_units = None
# 3) génération RÉELLE : le manifeste que le README prétend recopier
man_fig = None
try:
_procb = subprocess.run(
[sys.executable, "bancable_gen.py", "validate", "fixtures/brief_bancable.json"],
cwd=BANC, capture_output=True, text=True, encoding="utf-8")
if _procb.returncode != 0:
raise ValueError(f"validate exit {_procb.returncode} : {_procb.stderr.strip()[:160]}")
man_fig = {f["cle"]: f["valeur"] for f in json.loads(_procb.stdout)["figures_calculees"]}
except (ValueError, KeyError, OSError) as e:
bad(f"Bancable figures · génération réelle (bancable_gen validate) échoue : {e}")
if _peb is not None and rec_units is not None and man_fig is not None:
rec_be = math.ceil(_peb / 100.0 * rec_units)
# cohérence croisée : le manifeste GÉNÉRÉ == le recompute indépendant fixture
for cle, expv in (("total_unites", rec_units), ("valeur_catalogue_usd", rec_usd),
("valeur_catalogue_dop", rec_dop), ("point_equilibre_unites", rec_be)):
gotv = man_fig.get(cle)
if gotv is None:
bad(f"Bancable figures · manifeste sans figure « {cle} » (attendu {expv})")
elif int(gotv) != int(expv):
bad(f"Bancable figures · manifeste « {cle} »={gotv} MAIS recompute fixture "
f"{expv} → générateur/fixture incohérents (formule cassée)")
else:
good(f"Bancable figures · manifeste « {cle} »={int(gotv)} == recompute fixture")
# 4) PROSE README §« Vérification en-repo » — un seul pattern d'identité
try:
banc_txt = re.sub(r"\s+", " ", open(BANC_RD, encoding="utf-8").read())
except OSError as e:
bad(f"Bancable figures · {BANC_RD} illisible : {e}"); banc_txt = None
if banc_txt is not None:
pat = (r"(\d[\d,]*)\s*unités\s*·\s*valeur catalogue\s*USD\s*([\d,]+)\s*/\s*"
r"DOP\s*([\d,]+)\s*·\s*point d'équilibre\s*(\d+)\s*unités\s*"
r"\(⌈\s*(\d+)\s*%\s*×\s*(\d+)\s*⌉\)")
m = re.search(pat, banc_txt)
if m is None:
bad(f"Bancable figures · {BANC_RD} — ligne « Génération réelle (fixture) » "
f"INTROUVABLE (attendu {rec_units} unités · USD {rec_usd:,} / DOP "
f"{rec_dop:,} · équilibre {rec_be} unités (⌈{_peb:g} % × {rec_units}⌉)) — "
f"un claim absent est une régression (#6)")
else:
g_pct, g_tot = float(m.group(5)), int(m.group(6))
for lbl, raw, expv in (("Σ unités", m.group(1), rec_units),
("valeur catalogue USD", m.group(2), rec_usd),
("valeur catalogue DOP", m.group(3), rec_dop),
("point d'équilibre", m.group(4), rec_be)):
got = int(raw.replace(",", ""))
if got != expv:
bad(f"Bancable figures · README {lbl}={raw} MAIS génération réelle "
f"={expv} → figure périmée, régénérer la fixture/le README (#6)")
elif expv >= 1000 and "," not in raw:
bad(f"Bancable figures · README {lbl}={raw} sans séparateur de milliers "
f"(attendu « {expv:,} »)")
else:
good(f"Bancable figures · README {lbl}={raw} == génération réelle")
# formule ⌈52 % × 40⌉ : pct ancré CLAUDE.md #9 · total == Σ unités
if g_pct != _peb:
bad(f"Bancable figures · README formule ⌈{g_pct:g} %⌉ MAIS CLAUDE.md #9 "
f"point d'équilibre = {_peb:g} % → aligner (ou CLAUDE.md)")
else:
good(f"Bancable figures · README formule pct {g_pct:g} % == CLAUDE.md #9")
if g_tot != rec_units:
bad(f"Bancable figures · README formule « × {g_tot} » MAIS Σ unités "
f"= {rec_units} → total de la formule désaligné")
else:
good(f"Bancable figures · README formule « × {g_tot} » == Σ unités")
sys.exit(1 if FAIL else 0) sys.exit(1 if FAIL else 0)
PY PY
rc=$? rc=$?