[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
+55
View File
@@ -1,5 +1,60 @@
# Activity log · 2026-08-02 · Claude Code DTP Worker # 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 ## 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 **Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
@@ -7,7 +7,7 @@
"required": ["projet"], "required": ["projet"],
"additionalProperties": true, "additionalProperties": true,
"properties": { "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" }, "nom": { "type": "string" },
"synthetique": { "type": "boolean", "description": "true = fixture de test, jamais publiable." }, "synthetique": { "type": "boolean", "description": "true = fixture de test, jamais publiable." },
"sources": { "sources": {
@@ -18,7 +18,7 @@
"projet": { "projet": {
"type": "string", "type": "string",
"pattern": "^P0[1-9]$", "pattern": "^P0[1-9]$",
"description": "Code projet canonique (P01..P09)." "description": "Code projet canonique (P01..P09) · domaine ancré sur CLAUDE.md §Projets."
}, },
"template_version": { "template_version": {
"type": "string", "type": "string",
+114
View File
@@ -5343,6 +5343,120 @@ if exp is not None:
(f" · (pas {exp_excl})" if cites_excl and exp_excl else "") (f" · (pas {exp_excl})" if cites_excl and exp_excl else "")
good(f"Fiches #10 · {label} — cite {_cited} == CLAUDE.md #10") 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 # 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 # que « paramètres canoniques » (generator) et « Fiches #10 » ci-dessus, sur une