[DTP-Worker 20260802_043512] Sprint 8 · buffer · Faisabilité : ancrage du DOMAINE DES CODES PROJET (garde _validate_brief + 2 schémas JSON) sur CLAUDE.md §Projets

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-02 04:41:54 +00:00
parent 03b481c6ee
commit bcfc0f9a9d
4 changed files with 171 additions and 2 deletions
+114
View File
@@ -5343,6 +5343,120 @@ 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")
# ============================================================================
# Faisabilité/generator · DOMAINE DES CODES PROJET (P01..P09) ancré sur §Projets
# — même classe d'ANCRAGE que le CONTRAT DE FORMAT e-NCF (`ncf.py`) : une GARDE
# DE VALIDATION en CODE qui encode un domaine constitutionnel SANS l'ancrer.
# ----------------------------------------------------------------------------
# Le générateur 4 volets (Livrable Sprint 3 · « P07 Aqua Terra <1h ») REFUSE tout
# brief dont le `projet` n'est pas un code canonique. Le DOMAINE des codes valides
# EST exactement l'ensemble des projets de CLAUDE.md §Projets (P01..P09). Il est
# recopié À LA MAIN à QUATRE surfaces, AUCUNE ancrée sur §Projets, toutes
# invisibles à `check_artifacts` (aucune n'est un `out/*.json`) :
# (a) `faisabilite/generator/faisabilite_gen.py::_validate_brief` — la GARDE
# runtime `code[:2] == "P0" and code[2] in "123456789"` (le domaine effectif)
# + le message `attendu P01..P09` (les bornes) ;
# (b) `faisabilite/version.schema.json` — `properties.projet.pattern "^P0[1-9]$"`
# (validation JSON du `version.json` par projet) + sa `description` ;
# (c) `faisabilite/generator/brief.schema.json` — même `pattern "^P0[1-9]$"`
# (validation JSON du brief d'entrée) + sa `description`.
# « Vert trompeur » : si CLAUDE.md §Projets AJOUTE un 10ᵉ projet (`P10 …`, évènement
# de portefeuille réel), la GARDE et les DEUX patterns REJETTENT `P10` comme
# « invalide (attendu P01..P09) » → le générateur REFUSE de scaffolder un projet
# LÉGITIMEMENT mandaté (bug d'exécution) pendant que la suite `tests/` reste VERTE
# (ses oracles P01/P02 sont self-consistants, jamais comparés à §Projets). Ni
# `check_artifacts` (schémas/`.py` ne sont pas des `out/*.json`) ni `tests/` (teste
# la FONCTION `_validate_brief`, pas son ANCRAGE) ne mordent. Symétriquement,
# RETIRER un projet de §Projets laisse la garde trop PERMISSIVE (accepte un code
# démobilisé). On RE-DÉRIVE le domaine de §Projets et on exige que chaque surface
# accepte EXACTEMENT cet ensemble (test COMPORTEMENTAL du pattern, robuste à la
# forme de la classe de caractères), + la déclaration de l'ancrage « §Projets ».
# ----------------------------------------------------------------------------
try:
_fcm = open("CLAUDE.md", encoding="utf-8").read()
_fmp = re.search(r"^## Projets\s*\n(.+)$", _fcm, re.M)
if _fmp is None:
raise ValueError("section « ## Projets » INTROUVABLE")
fpj_codes = sorted(set(re.findall(r"\bP\d{2,}\b", _fmp.group(1))))
if not fpj_codes:
raise ValueError("aucun code projet PNN parsé")
except (OSError, ValueError) as e:
bad(f"Faisabilité domaine · CLAUDE.md §Projets illisible/inattendu : {e}")
fpj_codes = None
if fpj_codes is not None:
fpj_set = set(fpj_codes); fpj_mn, fpj_mx = fpj_codes[0], fpj_codes[-1]
# (a) la garde runtime _validate_brief : préfixe + ensemble de chiffres + message
FG = f"{D}/faisabilite/generator/faisabilite_gen.py"
try:
_fg = open(FG, encoding="utf-8").read()
except OSError as e:
_fg = None; bad(f"Faisabilité domaine · {FG} illisible : {e}")
if _fg is not None:
_mpre = re.search(r'code\[:2\]\s*==\s*"([^"]+)"', _fg)
_mdig = re.search(r'code\[2\]\s*in\s*"([^"]+)"', _fg)
if not (_mpre and _mdig):
bad("Faisabilité domaine · _validate_brief : préfixe/ensemble-chiffres du "
"code projet INTROUVABLE (ancre morte) → la garde a changé de forme")
else:
_accept = {f"{_mpre.group(1)}{d}" for d in _mdig.group(1)}
_miss = sorted(fpj_set - _accept); _extra = sorted(_accept - fpj_set)
if _miss or _extra:
bad(f"Faisabilité domaine · _validate_brief accepte {sorted(_accept)} ≠ "
f"CLAUDE.md §Projets{' · manquants=' + str(_miss) if _miss else ''}"
f"{' · en trop=' + str(_extra) if _extra else ''} → aligner la garde "
f"(ou CLAUDE.md §Projets)")
else:
good(f"Faisabilité domaine · _validate_brief accepte exactement les "
f"{len(_accept)} codes {fpj_mn}..{fpj_mx} de CLAUDE.md §Projets")
_mmsg = re.search(r'brief\.projet invalide \(attendu ([^)]+)\)', _fg)
if not _mmsg:
bad("Faisabilité domaine · message « brief.projet invalide (attendu …) » "
"INTROUVABLE (ancre morte)")
elif fpj_mn not in _mmsg.group(1) or fpj_mx not in _mmsg.group(1):
bad(f"Faisabilité domaine · message « attendu {_mmsg.group(1)} » ne cite pas "
f"les bornes {fpj_mn}..{fpj_mx} de CLAUDE.md §Projets")
else:
good(f"Faisabilité domaine · message d'erreur cite les bornes "
f"{fpj_mn}..{fpj_mx} == CLAUDE.md §Projets")
# (b)+(c) les 2 schémas JSON : le pattern accepte EXACTEMENT §Projets (comportemental)
_probe = fpj_set | {"P00", "P10", "P99", "P0", "PXX", "P1", "P010", "Q01"}
for _rel in ("version.schema.json", "generator/brief.schema.json"):
SP = f"{D}/faisabilite/{_rel}"
try:
_sj = json.load(open(SP, encoding="utf-8"))
except (OSError, ValueError) as e:
bad(f"Faisabilité domaine · {_rel} illisible/inattendu : {e}"); continue
_prop = (_sj.get("properties") or {}).get("projet") or {}
_pat = _prop.get("pattern")
if not _pat:
bad(f"Faisabilité domaine · {_rel} : properties.projet.pattern INTROUVABLE")
else:
try:
_rx = re.compile(_pat)
except re.error as e:
bad(f"Faisabilité domaine · {_rel} pattern {_pat!r} illégal : {e}"); _rx = None
if _rx is not None:
_acc = {c for c in _probe if _rx.fullmatch(c)}
_miss = sorted(fpj_set - _acc); _extra = sorted(_acc - fpj_set)
if _miss or _extra:
bad(f"Faisabilité domaine · {_rel} pattern {_pat!r} accepte {sorted(_acc)}"
f" ≠ CLAUDE.md §Projets{' · manquants=' + str(_miss) if _miss else ''}"
f"{' · en trop=' + str(_extra) if _extra else ''}")
else:
good(f"Faisabilité domaine · {_rel} pattern accepte exactement les "
f"{len(fpj_set)} codes de §Projets")
_desc = _prop.get("description", "") or ""
if not re.search(r"CLAUDE\.md\s*§?\s*Projets", _desc):
bad(f"Faisabilité domaine · {_rel} : description projet ne DÉCLARE pas "
f"l'ancrage « CLAUDE.md §Projets » : {_desc!r}")
elif fpj_mn not in _desc or fpj_mx not in _desc:
bad(f"Faisabilité domaine · {_rel} : description ne cite pas les bornes "
f"{fpj_mn}..{fpj_mx} : {_desc!r}")
else:
good(f"Faisabilité domaine · {_rel} description ancre {fpj_mn}..{fpj_mx} "
f"sur CLAUDE.md §Projets")
# ============================================================================
# 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