[DTP-Worker 20260802_083529] Sprint 8 · buffer · Faisabilité/bancable : élimination du taux 0.52 codé en dur dans l'oracle _check_derived_arithmetic
Le seul survivant en dur du paramètre canonique #9 (point d'équilibre 52 %) vivait au cœur de l'oracle anti-invention de bancable_gen.py — alors que le module PRÊCHE la source unique (deps.py « une seule source de vérité » ; finance._pct « jamais codé en dur »). Transitivement protégé (donc gate redondant, #5) mais BUG LATENT inverse : #9 52 %→55 % aurait fait rejeter en silence un dossier bancable VALIDE. Fix = dériver le taux de CANONICAL (finance._pct), pas un checker de plus. Vérif : validate byte-identique à 52 % (pe=21) · 22 tests verts · preuve levée du bug (source→55 % ⇒ pe=22, invariants OK) · 6 gates CI exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,62 @@
|
||||
# Activity log · 2026-08-02 · Claude Code DTP Worker
|
||||
|
||||
## Sprint 8 · buffer · Faisabilité/bancable — ÉLIMINATION du taux `0.52` codé en dur dans l'oracle anti-invention (source unique CANONICAL · #6/#5)
|
||||
|
||||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||||
chiffres » · workflow #5 « zéro duplication »). En balayant les copies MANUELLES
|
||||
du paramètre canonique #9 « point d'équilibre 52 % » restées HORS de la source
|
||||
unique, une a été trouvée au **cœur de l'oracle anti-invention** du générateur
|
||||
bancable : `faisabilite/bancable/bancable_gen.py::_check_derived_arithmetic`
|
||||
(l.131) recopiait le taux en **littéral `0.52`** (`exp_pe = math.ceil(0.52 *
|
||||
exp_total)`) pour re-calculer indépendamment le point d'équilibre et prouver
|
||||
qu'« aucune valeur n'a été posée ». Paradoxe : le module PRÊCHE l'inverse — son
|
||||
`deps.py` (« une seule source de vérité pour tout le mandat ») importe
|
||||
`CANONICAL = genlib.model.CANONICAL` (gaté sur CLAUDE.md #9/#10) précisément
|
||||
pour NE PAS dupliquer `3 % / 8.5 % / 52 %`, et `finance._pct` porte le docstring
|
||||
explicite « **jamais codé en dur ici** — évite toute dérive ». Le `0.52` de
|
||||
l'oracle était le SEUL survivant en dur (grep : aucun autre `0.03/0.085/0.52`
|
||||
littéral dans le code du module ; `finance.py`/`report.py` passent tous par
|
||||
`CANONICAL`).
|
||||
|
||||
**Pourquoi un FIX et pas un gate** — mutation-test préalable (mémoire
|
||||
`verify-uncovered-before-gating`) : AUCUNE mutation ne produit un « vert
|
||||
trompeur ». Le `0.52` est **transitivement protégé** — l'invariant du module
|
||||
compare l'oracle à la figure `finance` (issue de CANONICAL) au moment de la
|
||||
génération, et le gate bancable (`check_readme_claims.sh`) LANCE cette génération
|
||||
(`bancable_gen validate`) ; toute divergence `0.52`≠CANONICAL casse `validate`
|
||||
→ gate ROUGE. Un gate d'ancrage du littéral serait donc **redondant** (interdit
|
||||
#5). MAIS le `0.52` en dur est un **BUG LATENT** de sens inverse : un changement
|
||||
LÉGITIME du mandat (#9 52 %→55 %, propagé à CANONICAL par les gates model/
|
||||
template) laisserait l'oracle à 21 pendant que `finance` produit 22 ⇒ le
|
||||
générateur **REFUSERAIT en silence un dossier bancable VALIDE** (invariant
|
||||
« manifeste 22 != recalcul 21 »), avec un message cryptique « génération réelle
|
||||
échoue » côté CI. La bonne réponse n'est pas de gater la copie, mais de la
|
||||
**supprimer** — dériver le taux de la source unique, comme le fait déjà
|
||||
`finance.py`.
|
||||
|
||||
**Correctif** (`bancable_gen.py`, `_check_derived_arithmetic`) — `0.52` remplacé
|
||||
par `pe_pct = finance._pct(deps.CANONICAL["point_equilibre_pct"])` (RÉUTILISE le
|
||||
parseur `%→fraction` déjà livré · zéro nouvelle logique) + garde `pe_pct is not
|
||||
None`. Docstring l.117 réécrit : le taux est « lu à la SOURCE UNIQUE (CANONICAL ·
|
||||
CLAUDE.md #9, gaté), jamais recopié en dur ici ». L'indépendance de l'oracle est
|
||||
préservée là où elle compte (Σ quantités · × prix · `ceil`) ; le TAUX, lui, est
|
||||
une constante de mandat déjà ancrée en amont (CANONICAL gaté sur CLAUDE.md #9),
|
||||
donc le re-dériver de cette source n'affaiblit rien.
|
||||
|
||||
**Vérif** — `validate fixtures/brief_bancable.json` byte-identique à 52 %
|
||||
(`point_equilibre_unites = 21 = ⌈52 % × 40⌉`). **22 tests** unittest verts
|
||||
(inchangés · oracles fixture self-consistants). **Preuve du bug levé** : en
|
||||
simulant la source à 55 % (`deps.CANONICAL["point_equilibre_pct"]="55 %"`), les
|
||||
invariants restent **OK** et `point_equilibre_unites` passe à **22 = ⌈55 % × 40⌉**
|
||||
(AVANT le fix : oracle figé à 21 ⇒ invariant cassé ⇒ dossier valide rejeté).
|
||||
Suite CI complète re-verte : `check_artifacts` · `check_docs` ·
|
||||
`guard_constraints` · `check_ci_integrity` · `check_regression` ·
|
||||
`check_readme_claims` tous exit 0. **Leçon** : toute copie manuelle d'une
|
||||
constante de mandat n'appelle pas un gate N+1 — quand elle est déjà
|
||||
transitivement protégée, la SUPPRIMER (source unique) vaut mieux qu'un checker
|
||||
redondant (#5), et lève au passage le bug-miroir « le mandat change → le module
|
||||
casse ».
|
||||
|
||||
## 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
|
||||
|
||||
@@ -114,7 +114,9 @@ def _check_derived_arithmetic(brief: dict, manifest: dict) -> list[str]:
|
||||
"""Reproduit indépendamment les 4 agrégats et compare au manifeste.
|
||||
|
||||
Prouve qu'aucune valeur calculée n'a été « posée » : elle DOIT découler des
|
||||
typologies sourcées et du taux canonique 52 %.
|
||||
typologies sourcées et du taux canonique de point d'équilibre. Ce taux est lu
|
||||
à la SOURCE UNIQUE (`CANONICAL` · CLAUDE.md #9, gaté), jamais recopié en dur
|
||||
ici — sinon un changement du mandat (#9) casserait cet oracle en silence.
|
||||
"""
|
||||
motifs: list[str] = []
|
||||
rows = finance.typologies(brief)
|
||||
@@ -128,8 +130,9 @@ def _check_derived_arithmetic(brief: dict, manifest: dict) -> list[str]:
|
||||
if all_q and all(r["prix_usd"] is not None for r in rows) else None)
|
||||
exp_dop = (sum(q * r["prix_dop"] for q, r in zip(qtes, rows))
|
||||
if all_q and all(r["prix_dop"] is not None for r in rows) else None)
|
||||
exp_pe = (float(math.ceil(0.52 * exp_total))
|
||||
if exp_total is not None else None)
|
||||
pe_pct = finance._pct(deps.CANONICAL["point_equilibre_pct"])
|
||||
exp_pe = (float(math.ceil(pe_pct * exp_total))
|
||||
if exp_total is not None and pe_pct is not None else None)
|
||||
|
||||
for cle, exp in (("total_unites", exp_total),
|
||||
("valeur_catalogue_usd", exp_usd),
|
||||
|
||||
Reference in New Issue
Block a user