[DTP-Worker 20260801_220449] Sprint 8 · buffer · Chat OTOIA : gate d'IDENTITÉ de la LISTE D'INVARIANTS CI — la section « ## Invariants (N) — garantis en CI » du README (compte annoncé + énumération numérotée en prose + self-report CLI « N invariants verts » du générateur, 3 transcriptions) recomputée du REGISTRE-source = marqueurs # Invariant N de chat_otoia_gen.py : (a) registre code contigu 1..N, (b) self-report CLI == |registre|, (c) compte annoncé == |registre|, (d) énumération prose == registre EXACT (set-diff absent/fantôme) ; surface aveugle aux blocs Chat OTOIA préexistants (comptes d'artefact + identité persona/langues) ; 5 morsures vérifiées, 7 gates re-verts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 22:13:26 +00:00
parent d20d37607d
commit 64b635073a
3 changed files with 145 additions and 1 deletions
+90
View File
@@ -2928,6 +2928,96 @@ if ci_persona is not None:
f"endpoint {got[2]} MAIS artefact dit {want[0]}/{want[1]}/{want[2]} "
f"→ régénérer la fiche")
# ============================================================================
# Chat OTOIA · IDENTITÉ de la LISTE D'INVARIANTS CI — le README du module porte
# une section « ## Invariants (N) — garantis en CI » qui ANNONCE un compte (N)
# PUIS énumère chaque invariant par son numéro entre parenthèses ((1) · (2) ·
# … · (11, 13) · (12) · (14)). Cette prose transcrit À LA MAIN le REGISTRE réel
# des invariants du générateur — les marqueurs `# Invariant N ·` de
# chat_otoia_gen.py, qui sont la source d'autorité de « ce que la CI garantit »
# (chaque marqueur = un contrôle appliqué par validate()). Classe DISTINCTE de
# tout ce qui précède : les blocs Chat OTOIA plus haut gatent des COMPTES
# d'artefact (blocks/mounts/portails) et l'IDENTITÉ persona/langues/endpoint ;
# AUCUN ne touche la liste d'invariants — surface ungatée, piège aveugle. Le
# générateur se SELF-REPORTE lui-même (« schéma + N invariants verts. » dans son
# message CLI), 3ᵉ transcription du même compte. On RECOMPUTE ici le registre
# depuis le SEUL registre-source (les marqueurs `# Invariant N` du .py) et on
# exige : (a) le registre code est un ensemble CONTIGU 1..max (une lacune/un
# doublon dans le ledger code = défaut réel) ; (b) le message CLI du générateur
# « N invariants verts » == |registre| (self-report du .py cohérent avec son
# propre ledger) ; (c) le compte annoncé « ## Invariants (N) » du README ==
# |registre| ; (d) l'ÉNUMÉRATION en prose du README == registre EXACT (numéro
# absent ET numéro fantôme mordus par set-diff). Ajouter un `# Invariant 15`
# (nouveau contrôle) sans toucher la prose ⇒ (c)(d) rouges ; retirer le contrôle
# 7 du code ⇒ (7) devient fantôme dans la prose. Aucune suite tests/ ne teste la
# PROSE ; le byte-gate d'artefact ignore le .py source ⇒ dérive silencieuse
# sinon. Section/compte absent = échec (la disparition d'un chiffre est une
# régression de traçabilité · #6).
# ============================================================================
CO_GEN = f"{D}/frontend/chat_otoia/chat_otoia_gen.py"
try:
co_src = open(CO_GEN, encoding="utf-8").read()
except OSError as e:
bad(f"Chat OTOIA invariants · générateur {CO_GEN} illisible : {e}"); co_src = None
if co_src is not None:
# (a) registre-source = marqueurs `# Invariant N` du générateur (autorité).
inv_reg = sorted({int(n) for n in re.findall(r"#\s*Invariant\s+(\d+)\b", co_src)})
if not inv_reg:
bad(f"Chat OTOIA invariants · aucun marqueur `# Invariant N` dans {CO_GEN} "
"(registre-source vide → impossible de re-dériver)")
else:
n_inv = len(inv_reg)
contigu = inv_reg == list(range(1, inv_reg[-1] + 1))
if not contigu:
bad(f"Chat OTOIA invariants · registre code NON contigu : {inv_reg} "
f"(attendu 1..{n_inv}) — lacune/doublon dans le ledger `# Invariant N`")
else:
good(f"Chat OTOIA invariants · registre code contigu 1..{n_inv} "
f"({n_inv} marqueurs `# Invariant N`)")
# (b) self-report CLI du générateur « N invariants verts ».
mg = re.search(r"(\d+)\s+invariants verts", co_src)
if mg is None:
bad(f"Chat OTOIA invariants · message CLI « N invariants verts » "
f"INTROUVABLE dans {CO_GEN} (attendu {n_inv})")
elif int(mg.group(1)) == n_inv:
good(f"Chat OTOIA invariants · générateur self-report {mg.group(1)} "
f"== registre ({n_inv})")
else:
bad(f"Chat OTOIA invariants · générateur dit « {mg.group(1)} invariants "
f"verts » MAIS registre `# Invariant N` en compte {n_inv} "
"(self-report désynchronisé du ledger)")
# README : section « ## Invariants (N) — garantis en CI » + énumération.
try:
co_rd_raw = open(CO_RD, encoding="utf-8").read()
except OSError as e:
bad(f"Chat OTOIA invariants · {CO_RD} illisible : {e}"); co_rd_raw = None
if co_rd_raw is not None:
mh = re.search(r"##\s+Invariants\s+\((\d+)\)\s+—\s+garantis en CI", co_rd_raw)
if mh is None:
bad(f"Chat OTOIA invariants · {CO_RD} — section « ## Invariants (N) — "
f"garantis en CI » INTROUVABLE (attendu {n_inv})")
else:
# (c) compte annoncé.
if int(mh.group(1)) == n_inv:
good(f"Chat OTOIA invariants · {CO_RD} — annonce ({mh.group(1)}) "
f"== registre ({n_inv})")
else:
bad(f"Chat OTOIA invariants · {CO_RD} — annonce ({mh.group(1)}) MAIS "
f"registre en compte {n_inv} → régénérer le README")
# (d) énumération en prose == registre EXACT (set-diff).
nxt = re.search(r"\n##\s", co_rd_raw[mh.end():])
prose = co_rd_raw[mh.end(): mh.end() + nxt.start()] if nxt else co_rd_raw[mh.end():]
enum = sorted({int(x) for grp in re.findall(r"\(([\d,\s]+)\)", prose)
for x in re.findall(r"\d+", grp)})
if enum == inv_reg:
good(f"Chat OTOIA invariants · {CO_RD} — énumération prose {enum} "
"== registre code (aucun absent/fantôme)")
else:
absents = [x for x in inv_reg if x not in enum]
fantomes = [x for x in enum if x not in inv_reg]
bad(f"Chat OTOIA invariants · {CO_RD} — énumération prose ≠ registre : "
f"absent(s)={absents} · fantôme(s)={fantomes} → régénérer le README")
# ============================================================================
# Recette (module) · couple « N livrables de sprint + M métriques succès MVP »
# transcrit À LA MAIN dans DEUX docs — même classe de « vert trompeur ».