From bcfc0f9a9d4c786b6cf2210556bb8335f4b14b08 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Sun, 2 Aug 2026 04:41:54 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker=2020260802=5F043512]=20Sprint=208?= =?UTF-8?q?=20=C2=B7=20buffer=20=C2=B7=20Faisabilit=C3=A9=20:=20ancrage=20?= =?UTF-8?q?du=20DOMAINE=20DES=20CODES=20PROJET=20(garde=20=5Fvalidate=5Fbr?= =?UTF-8?q?ief=20+=202=20sch=C3=A9mas=20JSON)=20sur=20CLAUDE.md=20=C2=A7Pr?= =?UTF-8?q?ojets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-08-02.md | 55 +++++++++ .../faisabilite/generator/brief.schema.json | 2 +- .../faisabilite/version.schema.json | 2 +- ci/check_readme_claims.sh | 114 ++++++++++++++++++ 4 files changed, 171 insertions(+), 2 deletions(-) diff --git a/05_activity_log/2026-08-02.md b/05_activity_log/2026-08-02.md index 78dbee9..1b3a7b8 100644 --- a/05_activity_log/2026-08-02.md +++ b/05_activity_log/2026-08-02.md @@ -1,5 +1,60 @@ # Activity log · 2026-08-02 · Claude Code DTP Worker +## Sprint 8 · buffer · Faisabilité — ancrage du DOMAINE DES CODES PROJET (garde d'entrée `_validate_brief` + 2 schémas JSON) sur CLAUDE.md §Projets + +**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de +chiffres »). La classe « constante constitutionnelle recopiée dans une GARDE DE +VALIDATION en code, jamais ancrée » (déjà couverte pour le CONTRAT DE FORMAT +e-NCF de `ncf.py`) portait un candidat resté NON gaté et paradoxalement au **cœur +du Livrable Sprint 3** (« P07 Aqua Terra <1h ») : le **DOMAINE des codes projet +valides** du générateur 4 volets. Ce domaine EST exactement l'ensemble +`CLAUDE.md §Projets` (P01..P09), recopié À LA MAIN à **QUATRE surfaces**, aucune +ancrée, toutes invisibles à `check_artifacts` (ni `.py` ni schémas ne sont des +`out/*.json`) : +1. `faisabilite/generator/faisabilite_gen.py::_validate_brief` — la GARDE runtime + `code[:2] == "P0" and code[2] in "123456789"` (le domaine EFFECTIF) + le message + `« … invalide (attendu P01..P09) »` (les bornes) ; +2. `faisabilite/version.schema.json` — `properties.projet.pattern "^P0[1-9]$"` + (validation du `version.json` par projet) + sa `description` ; +3. `faisabilite/generator/brief.schema.json` — même `pattern "^P0[1-9]$"` + (validation du brief d'entrée) + sa `description` (qui n'en avait **aucune**). + +**Le « 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 (oracles P01/P02 self-consistants, jamais comparés à §Projets). +Symétriquement, RETIRER un projet laisse la garde trop PERMISSIVE (accepte un code +démobilisé). Ni `check_artifacts` (schémas/`.py` ≠ `out/*.json`) ni `tests/` +(teste la FONCTION `_validate_brief`, pas son ANCRAGE à §Projets) ne mordent. + +**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Faisabilité/generator · +DOMAINE DES CODES PROJET », juste après « Fiches #10 ») — RE-DÉRIVE le catalogue +de codes depuis `CLAUDE.md §Projets` (`\bP\d{2,}\b` sur la ligne `## Projets` · +source unique) puis exige que chaque surface accepte **EXACTEMENT** cet ensemble : +(a) la garde `_validate_brief` — reconstruit l'ensemble ACCEPTÉ `{préfixe+chiffre}` +depuis le code source et set-diff == §Projets ; le message cite les bornes +min..max ; (b)/(c) les 2 patterns JSON — **test COMPORTEMENTAL** (compile le +pattern, l'applique à une sonde `§Projets ∪ {P00,P10,P99,P0,PXX,P1,P010,Q01}`, +exige accepté == §Projets · robuste à la forme de la classe de caractères) + la +`description` DÉCLARE l'ancrage « CLAUDE.md §Projets » et cite les bornes. Une +surface INTROUVABLE échoue AUSSI (ancre morte · anti-évaporation #6). Deux +descriptions de schéma enrichies pour porter l'ancrage (brief.schema en gagne une). + +**Vérif** — 6 checks verts sur l'arbre propre. **6 morsures** adversariales : +M1 garde retire le chiffre `7` (validateur trop restrictif) ⇒ set ≠ · M2 message +`P01..P09→P01..P08` ⇒ bornes ≠ · M3 pattern version élargi `^P0[0-9]$` ⇒ accepte +`P00` en trop · M4 brief.schema perd l'ancrage §Projets ⇒ ne DÉCLARE plus · **M5 +SILENT-GREEN : CLAUDE.md §Projets ajoute `P10` SEUL (les 4 surfaces inchangées) ⇒ +CASCADE sur les 6 lignes (garde accepte≠, 2 patterns manquent P10, message +2 +descriptions bornes P09≠P10) — preuve que l'autorité est §Projets, pas la +cohérence interne** · M6 version.schema perd les bornes de sa description. Les +**16 tests unittest** du générateur restent verts (garde inchangée). Gates +re-verts : `check_artifacts` · `check_docs` · `guard_constraints` · +`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0. + +--- + ## 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 diff --git a/05_deliverables_mvp/faisabilite/generator/brief.schema.json b/05_deliverables_mvp/faisabilite/generator/brief.schema.json index fa63ca6..a62f556 100644 --- a/05_deliverables_mvp/faisabilite/generator/brief.schema.json +++ b/05_deliverables_mvp/faisabilite/generator/brief.schema.json @@ -7,7 +7,7 @@ "required": ["projet"], "additionalProperties": true, "properties": { - "projet": { "type": "string", "pattern": "^P0[1-9]$" }, + "projet": { "type": "string", "pattern": "^P0[1-9]$", "description": "Code projet canonique (P01..P09) · domaine ancré sur CLAUDE.md §Projets." }, "nom": { "type": "string" }, "synthetique": { "type": "boolean", "description": "true = fixture de test, jamais publiable." }, "sources": { diff --git a/05_deliverables_mvp/faisabilite/version.schema.json b/05_deliverables_mvp/faisabilite/version.schema.json index 75b7ef2..aa66b43 100644 --- a/05_deliverables_mvp/faisabilite/version.schema.json +++ b/05_deliverables_mvp/faisabilite/version.schema.json @@ -18,7 +18,7 @@ "projet": { "type": "string", "pattern": "^P0[1-9]$", - "description": "Code projet canonique (P01..P09)." + "description": "Code projet canonique (P01..P09) · domaine ancré sur CLAUDE.md §Projets." }, "template_version": { "type": "string", diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 6347dc3..6b95e2f 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -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