[DTP-Worker 20260802_100534] Sprint 8 · buffer · Ancrage des MINIMA D'ORCHESTRATION de demo/scenarios (« ≥ 2 modules distincts · ≥ 3 beats ») sur la garde check_invariants (CODE-autorité)

Classe « seuil data-derived recopié dans une garde de validation + sa prose,
jamais ancré » (déjà couverte pour min_doc_bytes/min_test_methods de l'auditeur
4Big). Livrable Sprint 7 demo/scenarios : la garantie #3 du README énonce deux
planchers RÉELLEMENT imposés par check_invariants bloc (c) — len(sc["beats"])<3,
len(mods)<2 — mais recopiés à la main à 3 surfaces (comparaison `<`, message
d'erreur « (min N) », prose README), aucune gatée. Muter « ≥ 3 beats »→« ≥ 4 »
laissait check_readme_claims exit 0 (prouvé ungate).

Gate re-dérive les 2 minima depuis la SEULE comparaison `<` (source unique #5) et
exige message + prose == plancher effectif ; surface INTROUVABLE échoue (#6).
7 morsures adversariales (dont M3 SILENT-GREEN : code resserré `<3`→`<4` ⇒ message
ET README mordent, preuve autorité=CODE). 39 tests OK (générateur inchangé) ; suite
CI complète re-verte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-02 10:11:34 +00:00
parent 3cb1bf06e1
commit 0752e35988
2 changed files with 114 additions and 0 deletions
+47
View File
@@ -1,5 +1,52 @@
# Activity log · 2026-08-02 · Claude Code DTP Worker # Activity log · 2026-08-02 · Claude Code DTP Worker
## Sprint 8 · buffer · Démo/scenarios — ancrage des MINIMA D'ORCHESTRATION (« ≥ 2 modules distincts · ≥ 3 beats ») sur la garde `check_invariants` (CODE-autorité)
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Classe « seuil data-derived recopié dans une garde de validation +
sa prose, jamais ancré » (déjà couverte pour les planchers `min_doc_bytes` /
`min_test_methods` de l'auditeur 4Big). Un candidat restait NON gaté (grep :
absent de tous les `ci/*.sh`) au **Livrable Sprint 7** (`demo/scenarios`, run-sheet
de pitch P07/P05) : la **garantie #3** du README (« chaque scénario tisse **≥ 2
modules distincts** et **≥ 3 beats** ») énonce deux planchers structurels
RÉELLEMENT imposés par la garde `check_invariants` bloc (c) de
`demo_scenario_gen.py` — la SEULE autorité d'exécution : `len(sc["beats"]) < 3`
refuse un scénario sous 3 beats, `len(mods) < 2` sous 2 modules distincts. Les
deux nombres sont recopiés À LA MAIN à **3 surfaces**, aucune gatée : (1) la
comparaison `< N`/`< M` (le plancher EFFECTIF) · (2) le message d'erreur
« (min 3) »/« (min 2 · orchestration) » (bornes annoncées au caller) · (3) la
prose README.
**Le « vert trompeur » — PROUVÉ ungate avant ajout** (mémoire
`verify-uncovered-before-gating`) : muter « ≥ 3 beats »→« ≥ 4 »/« ≥ 2 modules »→
« ≥ 5 » dans le README laissait `check_readme_claims` **exit 0**. Resserrer la
règle (démo plus riche : `< 2``< 3` modules) sans toucher la prose ⇒ README
PÉRIMÉ en silence, documentant un plancher que le code n'applique plus — dérive
qu'aucune suite `tests/` (exerce la FONCTION `check_invariants`, jamais la prose)
n'attrape, invisible à `check_artifacts` (`.py``out/*.json`).
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Démo · run-sheet · MINIMA
D'ORCHESTRATION », juste après le bloc « modules cités ») — RE-DÉRIVE les 2 minima
depuis la SEULE comparaison `<` de la garde (source unique · zéro duplication #5)
et exige : (1) autorité = `<` de `check_invariants` ; (2) le message d'erreur
« (min N) »/« (min M · orchestration) » == plancher effectif ; (3) la prose #3 du
README == plancher effectif. Une surface INTROUVABLE échoue AUSSI (ancre morte ·
anti-évaporation #6).
**Vérif** — 3 checks verts sur l'arbre propre (≥ 2 modules · ≥ 3 beats). **7
morsures** adversariales, gate lancé SEUL, reverts par `cp` ciblé (jamais
`git checkout` large — cf. incident consigné) : **M1** README `≥3 beats``≥4` (3) ·
**M2** README `≥2 modules``≥5` (3) · **M3 SILENT-GREEN** garde `<3``<4` beats
(code resserré, prose+message figés) ⇒ **message ET README mordent** (preuve que
l'autorité est le CODE, pas la cohérence interne de la prose) · **M4** message
`(min 3)``(min 4)` désync de `<` ⇒ message mord, README reste OK · **M5** garde
`<2``<3` modules ⇒ message ET README mordent · **M6** prose #3 supprimée ⇒
INTROUVABLE · **M7** comparaison `<` de la garde retirée ⇒ autorité INTROUVABLE.
Arbre restauré (`git status` propre, seule l'addition au gate subsiste). Suite
unittest re-verte : **39 tests OK** (générateur inchangé). Suite CI complète
re-verte : `check_artifacts` · `check_docs` · `guard_constraints` ·
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
## Sprint 8 · buffer · CRM/workflow_vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` (9ᵉ instance du ledger · dialecte `# Invariant N ·`) ## Sprint 8 · buffer · CRM/workflow_vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` (9ᵉ instance du ledger · dialecte `# Invariant N ·`)
**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
+67
View File
@@ -3369,6 +3369,73 @@ if demo_n is not None:
bad(f"Démo · {DEMO_RD} — dit {m.group(1)} modules cités MAIS artefact dit " bad(f"Démo · {DEMO_RD} — dit {m.group(1)} modules cités MAIS artefact dit "
f"{demo_n} → régénérer le README") f"{demo_n} → régénérer le README")
# ============================================================================
# Démo · run-sheet · MINIMA D'ORCHESTRATION (≥ M modules distincts · ≥ N beats)
# — même classe de « vert trompeur », au grain des SEUILS de la garde d'invariant.
# ----------------------------------------------------------------------------
# La garantie #3 du README du module (« chaque scénario tisse ≥ 2 modules
# distincts et ≥ 3 beats ») ÉNONCE deux planchers structurels qui sont EN RÉALITÉ
# imposés par la garde `check_invariants` (bloc (c)) de demo_scenario_gen.py — la
# SEULE autorité d'exécution : `len(sc["beats"]) < N` refuse un scénario sous N
# beats, `len(mods) < M` sous M modules distincts. Ces deux nombres sont recopiés
# À LA MAIN à TROIS surfaces, aucune gatée : (1) la comparaison `< N`/`< M` (le
# plancher EFFECTIF), (2) le message d'erreur « (min N) »/« (min M · orchestration) »
# (les bornes annoncées au caller), (3) la prose « ≥ M modules distincts … ≥ N
# beats » du README. Resserrer la règle (p.ex. exiger ≥ 3 modules pour une démo
# plus riche : `< 2`→`< 3`) sans toucher la prose laisse le README PÉRIMÉ en
# silence, documentant un plancher que le code n'applique plus — dérive qu'aucune
# suite tests/ (qui exerce la FONCTION `check_invariants`, jamais la prose du
# README) n'attrape, et que `check_artifacts` ne voit pas (`.py` ≠ `out/*.json`).
# Même patron que les seuils min_doc_bytes/min_test_methods de l'auditeur 4Big.
# On RE-DÉRIVE les deux minima depuis la comparaison `<` de la garde (source
# unique · zéro duplication #5) et on exige que le message d'erreur ET la prose
# du README les répètent À L'IDENTIQUE. Une surface INTROUVABLE échoue AUSSI
# (ancre morte · anti-évaporation #6).
# ============================================================================
DEMO_GEN = f"{D}/demo/scenarios/demo_scenario_gen.py"
try:
demo_src = open(DEMO_GEN, encoding="utf-8").read()
except OSError as e:
bad(f"Démo minima · {DEMO_GEN} illisible : {e}"); demo_src = None
if demo_src is not None:
# (1) plancher EFFECTIF = comparaison `<` de la garde d'orchestration (autorité)
mb = re.search(r'len\(sc\["beats"\]\)\s*<\s*(\d+)', demo_src)
mm = re.search(r'len\(mods\)\s*<\s*(\d+)', demo_src)
if mb is None or mm is None:
bad(f"Démo minima · garde `check_invariants` — comparaison `len(...)<N` "
f"INTROUVABLE dans {DEMO_GEN} (beats={mb is not None} modules={mm is not None})")
else:
min_beats, min_mods = mb.group(1), mm.group(1)
good(f"Démo minima · garde d'orchestration — ≥ {min_mods} modules · "
f"≥ {min_beats} beats (source unique = comparaison `<` de check_invariants)")
# (2) message d'erreur de la garde == plancher effectif
gm_b = re.search(r"beat\(s\) \(min (\d+)\)", demo_src)
gm_m = re.search(r"\(min (\d+) · orchestration\)", demo_src)
if gm_b is None or gm_m is None:
bad(f"Démo minima · message d'erreur « (min N) » INTROUVABLE dans "
f"{DEMO_GEN} (beats={gm_b is not None} modules={gm_m is not None})")
elif gm_b.group(1) != min_beats or gm_m.group(1) != min_mods:
bad(f"Démo minima · message garde dit (min {gm_m.group(1)} modules · "
f"min {gm_b.group(1)} beats) MAIS la comparaison `<` impose "
f"(≥ {min_mods} · ≥ {min_beats}) → aligner le message sur la garde")
else:
good(f"Démo minima · message garde « (min {min_mods} · orchestration) » / "
f"« (min {min_beats}) » == comparaison `<`")
# (3) prose #3 du README == plancher effectif
demo_rd3 = read_norm(DEMO_RD)
if demo_rd3 is not None:
mr = re.search(r"≥ (\d+) modules distincts\*\* et \*\*≥ (\d+) beats", demo_rd3)
if mr is None:
bad(f"Démo minima · prose « ≥ M modules distincts … ≥ N beats » "
f"INTROUVABLE dans {DEMO_RD} (attendu ≥ {min_mods} · ≥ {min_beats})")
elif mr.groups() == (min_mods, min_beats):
good(f"Démo minima · README — ≥ {mr.group(1)} modules · ≥ {mr.group(2)} "
f"beats == garde ({min_mods} · {min_beats})")
else:
bad(f"Démo minima · README dit ≥ {mr.group(1)} modules · ≥ {mr.group(2)} "
f"beats MAIS la garde impose ≥ {min_mods} · ≥ {min_beats} → "
f"régénérer le README")
# ============================================================================ # ============================================================================
# Chat OTOIA · montage de l'assistant conversationnel par portail (nombre de # Chat OTOIA · montage de l'assistant conversationnel par portail (nombre de
# Custom Block / configs runtime / portails) — même classe de « vert trompeur », # Custom Block / configs runtime / portails) — même classe de « vert trompeur »,