[DTP-Worker] Sprint 8 · buffer · Frontend Console/AGENT.md : gate d'IDENTITÉ des configs runtime Chat OTOIA (persona · langues · endpoint:null #6/#8) ancré sur chat_mount.json ⨯ MANIFEST.json

Bloc anti-dérive #6 dans ci/check_readme_claims.sh : la cellule « Métrique
vérifiée » de 03_agents/frontend_console/AGENT.md:29 restituait 3 attributs
data-derived du montage OTOIA (persona Amélie · langues FR/EN/ES ·
`endpoint: null`) ; le bloc chat_otoia amont ne gate que le COMPTE
blocks/mounts/portails, et le gate persona/capabilities ancré sur CLAUDE.md
vise spec/mounts/MANIFEST/README/oracle, jamais cette fiche. Le nouveau bloc
recompute les 3 depuis out/chat_mount.json (5 configs) + out/MANIFEST.json
byte-gatés : persona==MANIFEST.persona.nom, langues==MANIFEST.langues majuscule,
endpoint:null == affirmation d'honnêteté exigeant all(mount.endpoint is None)
— un endpoint fabriqué mord. Cohérences croisées MANIFEST⇔chaque mount +
endpoint_statut=='a_confirmer'⇔endpoints tous null. 6 morsures vérifiées ·
7 gates verts · aucune écriture out/ (0 dérive artefact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 10:40:20 +00:00
parent 7e8bb9a0de
commit 71999e04c4
3 changed files with 157 additions and 1 deletions
+73
View File
@@ -2234,6 +2234,79 @@ if co_cb is not None:
bad(f"Chat OTOIA · {CO_RD} — dit {m.group(1)} configs runtime MAIS artefact "
f"dit {co_mo} → régénérer le README")
# ============================================================================
# Chat OTOIA · IDENTITÉ des configs runtime (persona · langues · endpoint) —
# ATTRIBUTS transcrits À LA MAIN dans la cellule « Métrique vérifiée » de la
# fiche agent, AVEUGLES au gate de COMPTE ci-dessus.
# ----------------------------------------------------------------------------
# Le bloc précédent gate le NOMBRE de blocks/mounts/portails ; il est indifférent
# à CE QUE chaque config runtime dit. Or la même cellule (03_agents/
# frontend_console/AGENT.md:29) restitue TROIS attributs data-derived du montage :
# « persona Amélie … · langues FR/EN/ES · `endpoint: null` ». Ils sortent des
# artefacts byte-gatés out/chat_mount.json (une config par portail) + out/
# MANIFEST.json (persona/langues/endpoint_statut consolidés) — jamais de la spec.
# • persona.nom : le prénom de l'assistant (le « QC » est le qualificatif de
# marque CLAUDE.md « Voix Amélie QC », gardé en littéral — non gaté ici).
# • langues : la surface trilingue FR/EN/ES lue par le générateur dans
# seo_spec.json (source unique des langues).
# • endpoint : `null` est une AFFIRMATION D'HONNÊTETÉ #6/#8 — l'endpoint OTOIA
# n'est PAS fabriqué (renseigné côté VPS). Si une config gagnait un endpoint
# réel, la fiche « endpoint: null » deviendrait un MENSONGE en silence.
# Cohérence croisée (bijection du générateur) : MANIFEST.persona.nom ET
# MANIFEST.langues doivent valoir EXACTEMENT ce que porte CHAQUE mount (un mount
# désynchronisé de son propre manifeste mord ici) ; endpoint_statut « a_confirmer »
# doit correspondre à des endpoints TOUS `null` (sinon incohérence interne de
# l'artefact). Un claim absent échoue AUSSI (INTROUVABLE = régression #6). Aucune
# suite tests/ (FONCTIONS du builder, jamais la prose de la fiche) n'attrape ce
# « vert trompeur ».
# ============================================================================
try:
ci_man = load("frontend/chat_otoia/out/MANIFEST.json")
ci_mounts = load("frontend/chat_otoia/out/chat_mount.json")
ci_persona = ci_man["persona"]["nom"]
ci_langs = list(ci_man["langues"])
ci_statut = ci_man["endpoint_statut"]
if not ci_mounts:
bad("Chat OTOIA identité · out/chat_mount.json vide"); ci_persona = None
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"Chat OTOIA identité · artefact illisible/inattendu : {e}"); ci_persona = None
if ci_persona is not None:
# Cohérence croisée MANIFEST ⇔ chaque config runtime (bijection du générateur)
bad_persona = sorted({m["persona"]["nom"] for m in ci_mounts if m["persona"]["nom"] != ci_persona})
bad_langs = sorted({"/".join(m["langues"]) for m in ci_mounts if list(m["langues"]) != ci_langs})
all_null = all(m["endpoint"] is None for m in ci_mounts)
if bad_persona:
bad(f"Chat OTOIA identité · persona MANIFEST={ci_persona!r} MAIS mounts portent "
f"aussi {bad_persona} (montage désynchronisé)")
if bad_langs:
bad(f"Chat OTOIA identité · langues MANIFEST={ci_langs} MAIS mounts portent "
f"aussi {bad_langs} (montage désynchronisé)")
if all_null and ci_statut != "a_confirmer":
bad(f"Chat OTOIA identité · endpoints TOUS null MAIS endpoint_statut={ci_statut!r} "
f"(attendu 'a_confirmer' — incohérence interne artefact)")
if (not all_null) and ci_statut == "a_confirmer":
bad("Chat OTOIA identité · endpoint_statut='a_confirmer' MAIS ≥1 endpoint fabriqué "
"(incohérence interne artefact)")
exp_langs = "/".join(l.upper() for l in ci_langs)
exp_endpoint = "null" if all_null else "NON-null(" + ",".join(
sorted({str(m["endpoint"]) for m in ci_mounts if m["endpoint"] is not None})) + ")"
co_fi2 = read_norm(CO_FI)
if co_fi2 is not None:
m = re.search(r"persona (\S+) QC · langues ([A-Z/]+) · `endpoint: ([^`]+)`", co_fi2)
if m is None:
bad(f"Chat OTOIA identité · {CO_FI} — claim « persona <nom> QC · langues "
f"<L> · `endpoint: <e>` » INTROUVABLE (attendu {ci_persona}/{exp_langs}/{exp_endpoint})")
else:
got = m.groups()
want = (ci_persona, exp_langs, exp_endpoint)
if got == want:
good(f"Chat OTOIA identité · {CO_FI} — persona {got[0]} · langues {got[1]} · "
f"endpoint {got[2]} == artefact (chat_mount ×{len(ci_mounts)} ⇔ MANIFEST)")
else:
bad(f"Chat OTOIA identité · {CO_FI} — dit persona {got[0]}/langues {got[1]}/"
f"endpoint {got[2]} MAIS artefact dit {want[0]}/{want[1]}/{want[2]} "
f"→ régénérer la fiche")
# ============================================================================
# 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 ».