95 KiB
Activity log · 2026-08-02 · Claude Code DTP Worker
Sprint 8 · buffer · Dérive doc-vs-réalité : 11 fiches AGENT.md pointaient un canal handoffs/ INEXISTANT — correction vers le canal de hand-off RÉEL (livrables out/ commités)
Nature — dérive réelle, pas un gate de plus (CLAUDE.md #5 · interdit « documenter sans vérifier existence courante »). Audit de complétude plutôt qu'empilage : la série récente a saturé les surfaces driftables numériques ; j'ai cherché une dérive doc-vs-réalité échappant aux gates.
Constat. Le bloc boilerplate « ## Communication inter-agents » (identique dans
11 des 13 fiches 03_agents/*/AGENT.md) affichait :
- Handoffs formalisés dans \05_deliverables_mvp/handoffs/`. Or ce répertoire **n'existe pas** — find -iname 'handoff' = 0 fichier, 0 consommateur (code/gate), 0 test. Fausse promesse de canal. Les 2 autres lignes du bloc sont, elles, vraies (daily_reports/` existe · WhatsApp conforme CLAUDE.md).
Pourquoi les gates l'ont manqué (angle mort documenté, pas à re-gater).
check_docs.sh §1 valide les cibles de liens [texte](cible) mais neutralise
délibérément les code-spans backtick (l.28 sed 's/\[^`]*`//g') : un chemin entre backticks est traité comme *exemple de syntaxe* (ex. gen.py build|validaten'est pas un chemin réel).`…/handoffs/`étant un code-span, il échappe. **Gater tous les backticks créerait des faux positifs massifs** (les fiches en regorgent : entrées CLI, extensions) → j'ai **choisi de NE PAS élargir le gate** (anti-sur-gating, mémoireverify-uncovered-before-gating`). Le correctif est un correctif de vérité,
pas un nouveau gate.
2 solutions (workflow #4) — la bonne = corriger la doc, pas créer le dossier.
- ❌ Créer
handoffs/+ README index : inventerait du contenu (#6) et dupliquerait le mécanisme de hand-off déjà réel (lesout/commités), violant #5. - ✅ Corriger les 11 lignes vers le canal qui existe déjà, décrit richement dans
chaque fiche (« Chaque générateur transforme un spec en fixtures
out/commitées — le hand-off direct »). Nouvelle ligne, universellement vraie pour les 11 agents (chacun produit au moins un doc/spec/fixture commité, y compris bim/ifc_speckle/ rendu/erpnext_backend qui n'ont pas deout/mais ont une fiche/SPEC) :- Hand-off inter-agents = livrables déterministes commités in-repo (fixtures/specs \out/`, SPEC, README), consommés directement par l'agent destinataire`.
Exécution. Remplacement littéral exact (script Python, assertion count==11,
1 occurrence/fichier) sur les 11 fiches : bim · crm · devops · erpnext_backend ·
frontend_console · ifc_speckle · mobile · onapi_legal · qa · rendu · seo.
Vérif. (1) grep -rn handoffs sur 03_agents/ 05_deliverables_mvp/ ci/ README.md
→ 0 résidu. (2) Nouvelle ligne présente (échantillon crm l.68, bim l.74). (3)
7 gates verts : check_ci_integrity · check_artifacts · check_docs ·
check_readme_claims · check_regression · guard_constraints · validate_json (tous exit 0).
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 ·)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source des marqueurs numérotés de la fonction de validation,
jamais une liste à la main) était consignée « close à 8 générateurs » (Chat OTOIA ·
SEO · Fiscal e-CF · Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM
Commissions · Mobile) + 2 (deploy_runbook/acceptance, dialecte # INVn). La mémoire
invariant-ledger-count-gate avertit précisément : « CLOSED a été prouvé FAUX DEUX
FOIS ; re-scanner TOUS les dialectes de marqueur AVANT de croire la clôture ». Un
3ᵉ oubli a été exhumé : crm/workflow_vente/workflow_vente_gen.py (Livrable
Sprint 4 l.50 · workflow de vente Frappe lead→…→CONFOTUR) porte un ledger de 9
invariants métier de graphe dans _validate_bundle, marqueurs # Invariant N ·
(style Mobile), phrasé « schéma + 9 invariants ». Le nombre « 9 » est recopié À LA
MAIN à 4 surfaces vives, aucune gatée : générateur l.223 (message CLI de succès)
· README l.78 (bloc de commande) · README l.114 (prose de vérification) · README
l.125 (prose d'auto-score).
Le « vert trompeur » — PROUVÉ ungate avant ajout (mémoire
verify-uncovered-before-gating) : muter « 9 invariants »→« 10 » dans le README
laissait check_readme_claims exit 0. Ni check_artifacts (le .py source
n'est pas un out/*.json) ni les suites tests/ (FONCTIONS de validation, jamais
la prose) n'attrapent la dérive. Ajouter un contrôle # Invariant 10 · à
_validate_bundle sans toucher les 4 chaînes ⇒ README + self-report CLI mentent en
silence pendant que la CI applique 10 contrôles.
Particularités du registre (vs les 8 blocs # N ·) — style # Invariant N · +
fonction _validate_bundle (comme Mobile ; les blocs ancrés # N ·/def _validate\b
ne le voient pas). Ledger PROPRE 1..9 compté À PART du schéma (« schéma + N
invariants » · pas de schéma-#1, pas de split a/b ; filet doublon nu conservé). Un
SECOND nombre distinct — « 25 tests » (suite unittest, déjà gatée) — présent au
README : l'ancre sur le mot « invariants » l'exclut nativement ⇒ aucun faux
positif. Le README N'A NI titre « ## Les N invariants » (son titre « ## Invariants
vérifiés par le CLI » ne porte aucun chiffre) NI énumération 1:1 avec le ledger — sa
liste compte le SCHÉMA en item 1 puis FUSIONNE les invariants 8 & 9 dans son item
9 (« Maîtres … ; comptes du manifeste cohérents ») : elle N'EST PAS le ledger, la
gater comme contiguë==ledger serait un faux positif fragile (présentation éditoriale).
On gate donc UNIQUEMENT (a)(b)(d), comme Audit 5D.
Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · workflow_vente · IDENTITÉ
du COMPTE D'INVARIANTS », juste après le bloc Mobile) — RECOMPUTE le ledger depuis
le SEUL registre-source (les # Invariant N · de _validate_bundle, corps extrait
jusqu'au def suivant) et exige : (a) ledger CONTIGU 1..N (lacune OU doublon nu
= vrai défaut de registre) ; (b) toute mention « N invariants » du .py ==
|ledger| ; (d) toute mention « N invariants » du README == |ledger|. Mention ou
registre absent = échec (anti-évaporation #6).
Vérif — 3 checks verts sur l'arbre propre (ledger 1..9 = 9 marqueurs # Invariant N ·, schéma compté à part). 7 morsures adversariales, gate lancé SEUL, reverts
par cp ciblé (jamais git checkout large — cf. incident consigné) : M1 .py
9→10 (b) · M2 README 9→10 métier (d) · M3 SILENT-GREEN # Invariant 10 ·
injecté ⇒ ledger→10 pendant que la prose reste 9 (mord .py ET README, preuve que
l'autorité est le CODE) · M4 # Invariant 5 · supprimé ⇒ ledger non contigu (a) +
self-reports désync · M5 doublon nu # Invariant 3 · ⇒ vrai doublon (a) · M6
toutes les mentions « 9 invariants » retirées du README ⇒ self-report ÉVAPORÉ · M7
no-false-positive : muter « 25 tests »→« 26 » ⇒ le gate ledger reste SILENCIEUX
(l'ancre « invariants » l'exclut ; le rouge vient du gate test-count préexistant, pas
du mien). Arbre restauré (git status propre, seule l'addition +113 subsiste). Suite
unittest re-verte : 25 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. La classe ledger passe à 9
générateurs (+2 dialecte # INVn) ; re-scanner tout NOUVEAU dialecte AVANT de
re-déclarer close — ce 3ᵉ oubli confirme l'avertissement de la mémoire.
Sprint 8 · buffer · Faisabilité/bancable — ÉLIMINATION du taux 0.52 codé en dur dans l'oracle anti-invention (source unique CANONICAL · #6/#5)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres » · workflow #5 « zéro duplication »). En balayant les copies MANUELLES
du paramètre canonique #9 « point d'équilibre 52 % » restées HORS de la source
unique, une a été trouvée au cœur de l'oracle anti-invention du générateur
bancable : faisabilite/bancable/bancable_gen.py::_check_derived_arithmetic
(l.131) recopiait le taux en littéral 0.52 (exp_pe = math.ceil(0.52 * exp_total)) pour re-calculer indépendamment le point d'équilibre et prouver
qu'« aucune valeur n'a été posée ». Paradoxe : le module PRÊCHE l'inverse — son
deps.py (« une seule source de vérité pour tout le mandat ») importe
CANONICAL = genlib.model.CANONICAL (gaté sur CLAUDE.md #9/#10) précisément
pour NE PAS dupliquer 3 % / 8.5 % / 52 %, et finance._pct porte le docstring
explicite « jamais codé en dur ici — évite toute dérive ». Le 0.52 de
l'oracle était le SEUL survivant en dur (grep : aucun autre 0.03/0.085/0.52
littéral dans le code du module ; finance.py/report.py passent tous par
CANONICAL).
Pourquoi un FIX et pas un gate — mutation-test préalable (mémoire
verify-uncovered-before-gating) : AUCUNE mutation ne produit un « vert
trompeur ». Le 0.52 est transitivement protégé — l'invariant du module
compare l'oracle à la figure finance (issue de CANONICAL) au moment de la
génération, et le gate bancable (check_readme_claims.sh) LANCE cette génération
(bancable_gen validate) ; toute divergence 0.52≠CANONICAL casse validate
→ gate ROUGE. Un gate d'ancrage du littéral serait donc redondant (interdit
#5). MAIS le 0.52 en dur est un BUG LATENT de sens inverse : un changement
LÉGITIME du mandat (#9 52 %→55 %, propagé à CANONICAL par les gates model/
template) laisserait l'oracle à 21 pendant que finance produit 22 ⇒ le
générateur REFUSERAIT en silence un dossier bancable VALIDE (invariant
« manifeste 22 != recalcul 21 »), avec un message cryptique « génération réelle
échoue » côté CI. La bonne réponse n'est pas de gater la copie, mais de la
supprimer — dériver le taux de la source unique, comme le fait déjà
finance.py.
Correctif (bancable_gen.py, _check_derived_arithmetic) — 0.52 remplacé
par pe_pct = finance._pct(deps.CANONICAL["point_equilibre_pct"]) (RÉUTILISE le
parseur %→fraction déjà livré · zéro nouvelle logique) + garde pe_pct is not None. Docstring l.117 réécrit : le taux est « lu à la SOURCE UNIQUE (CANONICAL ·
CLAUDE.md #9, gaté), jamais recopié en dur ici ». L'indépendance de l'oracle est
préservée là où elle compte (Σ quantités · × prix · ceil) ; le TAUX, lui, est
une constante de mandat déjà ancrée en amont (CANONICAL gaté sur CLAUDE.md #9),
donc le re-dériver de cette source n'affaiblit rien.
Vérif — validate fixtures/brief_bancable.json byte-identique à 52 %
(point_equilibre_unites = 21 = ⌈52 % × 40⌉). 22 tests unittest verts
(inchangés · oracles fixture self-consistants). Preuve du bug levé : en
simulant la source à 55 % (deps.CANONICAL["point_equilibre_pct"]="55 %"), les
invariants restent OK et point_equilibre_unites passe à 22 = ⌈55 % × 40⌉
(AVANT le fix : oracle figé à 21 ⇒ invariant cassé ⇒ dossier valide rejeté).
Suite CI complète re-verte : check_artifacts · check_docs ·
guard_constraints · check_ci_integrity · check_regression ·
check_readme_claims tous exit 0. Leçon : toute copie manuelle d'une
constante de mandat n'appelle pas un gate N+1 — quand elle est déjà
transitivement protégée, la SUPPRIMER (source unique) vaut mieux qu'un checker
redondant (#5), et lève au passage le bug-miroir « le mandat change → le module
casse ».
Sprint 8 · buffer · Ledger d'invariants — 2ᵉ FAMILLE (# INVn · / validate_bundle / « N familles d'invariants ») : ancrage du COMPTE de deploy_runbook (11) + acceptance (11)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles CI
depuis le registre-source des marqueurs numérotés de la fonction _validate, jamais
une liste à la main) avait été déclarée close à 8 générateurs — mais sur le SEUL
dialecte # N · / « N invariants » / _validate(_validate_bundle). La mémoire
invariant-ledger-count-gate consigne précisément le risque : « un prior CLOSED
après 7 était FAUX — un style de marqueur différent avait glissé ; re-scanner LES
DEUX styles avant de croire la clôture ». Un re-scan systématique a exhumé une
DEUXIÈME famille entièrement HORS gate : marqueurs # INVn · (préfixe INV)
dans validate_bundle, phrasé « N **familles d'**invariants ». DEUX générateurs la
portent :
devops/deploy_runbook/deploy_runbook_gen.py— ledger# INV1..INV11dansvalidate_bundle, compte « 11 familles d'invariants » recopié à 3 surfaces vives : générateur l.272 (message CLI de succès) · README l.73 (bloc de commande) · suite tests l.4 (docstring) ;qa/acceptance/acceptance_gen.py— ledger# INV1..INV11dansvalidate_bundle, compte à 2 surfaces vives : README l.46 « 11 invariants » · suite tests l.4 « 11 familles d'invariants ». (Le.pyacceptance ne porte AUCUN compte numéroté → surface non exigée.)
Le « vert trompeur » — ajouter un contrôle # INV12 · à validate_bundle sans
toucher ces chaînes ⇒ README + self-reports + docstrings de test mentent en silence
pendant que la CI applique 12 contrôles. Ni check_artifacts (le .py source n'est
pas un out/*.json) ni les suites tests/ (FONCTIONS de validation, jamais la prose)
n'attrapent la dérive. Prouvé (M4 SILENT-GREEN) : injecter # INV12 · dans
validate_bundle (ledger→12, toute la prose reste 11) ⇒ les 3 surfaces
deploy_runbook mordent « [11] MAIS ledger en compte 12 » — preuve que l'autorité
est le CODE-registre, pas la cohérence interne de la prose.
Particularités — Ledger PROPRE 1..N compté À PART du schéma (« schéma + N
invariants »). Les mentions NON numérotées de « invariants » (docstrings « invariants
de déploiement/recette », README « Un invariant refuse … ») ne sont pas ancrées par
un chiffre → aucun faux positif. Le compte-regex accepte les DEUX formes
(« N invariants » ET « N familles d'invariants ») via un préfixe optionnel
(?:familles d')?. Les daily_reports/activity_log (snapshots datés — ex.
acceptance « 10 » d'avant la croissance 10→11) ne sont PAS des surfaces vives et ne
sont volontairement pas ancrés (aucune dérive à surveiller sur un log figé).
Gate ajouté (ci/check_readme_claims.sh, bloc « DevOps deploy_runbook + QA
acceptance · IDENTITÉ du COMPTE d'INVARIANTS de validate_bundle », juste après le
bloc Mobile) — paramétré sur les 2 modules (zéro duplication). RECOMPUTE le
ledger depuis le SEUL registre-source (# INVn · de validate_bundle, corps extrait
jusqu'au def suivant) et exige : (a) ledger CONTIGU 1..N (lacune OU doublon nu
= vrai défaut de registre) ; (b) CHAQUE surface déclarée porte ≥1 mention
« N [familles d']invariants » toutes == |ledger|. Une surface sans mention = self-report
ÉVAPORÉ (échec · anti-évaporation #6).
Vérif — 7 checks verts sur l'arbre propre (2 ledgers 1..11 + 5 surfaces).
8 morsures adversariales, gate lancé SEUL, reverts par cp ciblé (jamais
git checkout large — cf. incident consigné) : M1 deploy .py 11→12 (b générateur)
· M2 deploy README 11→12 (b) · M3 deploy test 11→12 (b) · M4 SILENT-GREEN
# INV12 · injecté ⇒ 3 surfaces deploy mordent (autorité = CODE) · M5 # INV7 ·
supprimé ⇒ ledger non contigu (a) + 3 surfaces désync · M6 acceptance README 11→12
(b) · M7 acceptance test 11→12 (b) · M8 compte retiré du README acceptance ⇒
self-report ÉVAPORÉ. Arbre restauré (git status propre). Suites unittest re-vertes :
deploy_runbook 29 tests OK · acceptance 37 tests OK (générateurs inchangés).
Suite complète re-verte : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_regression · check_readme_claims tous exit 0.
Les DEUX familles de ledger sont désormais gatées ; re-scanner tout NOUVEAU
dialecte de marqueur (préfixe/phrasé) avant de re-déclarer la classe close.
Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'encadré « Défense en profondeur » (NOM du rôle détenteur de set_user_permissions · scope · famille) sur l'artefact byte-gaté
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le §2
« Modèle RBAC » de 05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md ferme sur un
encadré de sécurité « Défense en profondeur » : le pouvoir set_user_permissions
(élévation de privilège) est « réservé au SEUL rôle OTO Plateforme RBAC Admin
(scope_donnees = groupe, famille = plateforme) — séparation des pouvoirs vérifiée
par test automatisé ». La SPEC se réclame donc d'un fait de sécurité tiré du
contrat rbac_50_roles.json, sur trois attributs : NOM du détenteur · scope ·
famille. Or le test cité (test_rbac::test_set_user_permissions_reserve_admin) impose
bien scope==groupe ET famille==plateforme à TOUT détenteur — MAIS jamais le NOM du
rôle (OTO Plateforme RBAC Admin) NI cette prose §2. L'UNICITÉ (« au SEUL rôle »)
et le NOM étaient DÉJÀ gatés côté artefact (bloc « RBAC fixtures », singleton
len(fx_sup)==1 depuis custom_docperm.json byte-gaté + prose du README fixtures
« émis que pour le rôle NAME ») — mais PAS la 3ᵉ surface prose, le §2 de la SPEC.
Le « vert trompeur » — RENOMMER le détenteur dans le contrat (+régénérer
fixtures/out + audit 4Big) laisse le §2 citer un rôle DISPARU en silence :
check_artifacts ne voit qu'un HASH de TAILLE du doc (agnostique au nom · la SPEC
n'est pas un out/*.json), les 10 tests restent verts (scope/famille inchangés).
Prouvé (M6 SILENT-GREEN) : renommer …RBAC Admin→…Securite Admin dans le
contrat + régénérer les 4 générateurs RBAC & l'audit ⇒ check_artifacts VERT
pendant que le nouveau gate mord « §2 nomme ['OTO Plateforme RBAC Admin'] MAIS
set_user_permissions réservé à 'OTO Plateforme Securite Admin' » — preuve que
l'autorité est l'ARTEFACT byte-gaté, pas la cohérence interne de la SPEC.
Gate ajouté (ci/check_readme_claims.sh, extension du bloc « RBAC fixtures »,
points 4 & 5 — pas un bloc neuf) — RÉUTILISE fx_sup (le singleton déjà byte-gaté
ci-dessus · zéro re-dérivation de l'unicité, interdit #5 « jamais de doublon »)
comme NOM-autorité et src_roles (contrat byte-gaté) pour scope/famille. Capte
l'encadré **Défense en profondeur** … vérifiée par test automatisé (spec_rd
whitespace-normalisé) et exige : le pouvoir set_user_permissions y est nommé ;
(a) le NOM du détenteur cité (accents/casse neutralisés) == le seul porteur du
flag ; (b)/(c) les scope_donnees = … / famille = … cités == attributs du
détenteur dans rbac_50_roles.json ; (5) §6 (row « Séparation des pouvoirs »)
cite bien set_user_permissions réservé (anti-évaporation). Encadré/row INTROUVABLE =
échec (#6). La glose « (gestion des habilitations) » N'est PAS gatée (éditoriale).
Note process (interdit #5) — un premier jet ajoutait un bloc §2 autonome
RECALCULANT le singleton depuis src_roles : doublon de la vérification d'unicité
déjà portée par le bloc fixtures (via custom_docperm.json byte-gaté, équivalent par
check_artifacts). Écarté ; les 4 checks réécrits en extension du bloc existant,
sans ré-affirmer l'invariant d'unicité.
Vérif — 4 checks verts sur l'arbre propre. 6 morsures adversariales, gate
lancé seul, reverts par cp/tar ciblés (jamais git checkout large — cf. incident
consigné) : M1 §2 renomme le détenteur (prose) ⇒ NOM ≠ artefact · M2 §2
scope groupe→entite ⇒ (b) ≠ détenteur · M3 §2 famille plateforme→ventes
⇒ (c) ≠ détenteur · M4 encadré supprimé ⇒ INTROUVABLE · M5 §6 retire
set_user_permissions ⇒ ancre évaporée · M6 SILENT-GREEN (ci-dessus) contrat
renommé + artefacts régénérés, check_artifacts VERT ⇒ §2 (a) mord. Suite complète
re-verte : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_regression · check_readme_claims tous exit 0.
Sprint 8 · buffer · Traçabilité roadmap — ancrage de la CITATION roadmap_ref VERBATIM (n° ligne + libellé) des artefacts sur la ligne RÉELLE de la roadmap
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention · toujours
vérifier sources archives »). Le pattern « citation vérifiée, pas déclarée » (INV11
de qa/acceptance ancre les roadmap_line NUMÉRIQUES de son artefact au bullet
DELIVERABLE réel) portait un angle mort : quatre modules — demo/scenarios,
frontend/chat_otoia, frontend/portails, mobile/app_config — publient dans leur
out/MANIFEST.json (byte-gaté) un champ roadmap_ref de forme
« …ROADMAP… · Sprint N l.A[-B] « <libellé bullet> » », où le libellé est une
transcription VERBATIM du bullet de la roadmap à la ligne citée. Or aucun gate
ne vérifiait que la ligne A[-B] porte réellement ce texte : check_artifacts ne
prouve QUE spec==MANIFEST byte-for-byte (la MÊME citation recopiée des deux côtés,
jamais contredite) ; INV11 ne touche QUE les roadmap_line numériques de SON
artefact ; l'ancrage Expo mobile ne teste que « roadmap » + « Expo N » dans
expo_sdk_source, pas la citation complète.
Le « vert trompeur » — RÉORDONNER la roadmap (les n° de ligne glissent) ou
REFORMULER un bullet laisse ces 4 citations pointer une ligne qui dit AUTRE CHOSE :
un agent lisant le MANIFEST pour retrouver la promesse d'origine tomberait sur le
mauvais bullet. Prouvé par mutation : reformuler le bullet l.63 (Chat OTOIA embedded…) laissait les 6 gates VERTS pendant que chat_otoia/out/MANIFEST.json
citait encore « … Chat OTOIA embedded … » à l.63. Aucune suite tests/ (FONCTIONS
de résolution, jamais l'ancre à la roadmap) ne mord.
Gate ajouté (ci/check_readme_claims.sh, bloc « Traçabilité roadmap », juste
avant sys.exit) — DÉCOUVRE tous les out/MANIFEST.json (glob 1 & 2 niveaux)
portant un roadmap_ref citant (l.A[-B] «…») et exige, par module : (1) le
fichier cité == la roadmap canonique ; (2) le libellé, NORMALISÉ (markdown
**/- retirés · espaces compressés · NFC), est SOUS-CHAÎNE des lignes A..B
réelles (robuste à la troncature du bullet et à la plage multi-lignes l.56-57) ;
(3) le spec du module porte le MÊME roadmap_ref (identité spec↔artefact publié).
Bornes de ligne hors-plage attrapées (renumérotation). Les 4 modules connus sont
EXIGÉS présents (anti-évaporation #6 : retirer un roadmap_ref échoue) ; tout AUTRE
MANIFEST à citation est ancré en bonus (future-proof).
Vérif — 5 checks verts sur l'arbre propre (4 citations ancrées + recensement
« 4 modules attendus présents »). 7 morsures adversariales, gate lancé SEUL,
reverts par cp ciblé (jamais git checkout large — cf. incident consigné) :
M1 SILENT-GREEN — reformuler le bullet l.63 (AUTORITÉ), MANIFEST inchangé ⇒
libellé ABSENT (preuve que l'autorité est la ROADMAP, pas la cohérence interne de
l'artefact) · M2 renuméroter le MANIFEST l.63→l.62 (ligne existante mais autre
bullet) ⇒ ABSENT · M3 dériver le libellé du MANIFEST lui-même ⇒ ABSENT · M4 citer
un autre fichier roadmap ⇒ fichier ≠ canonique · M5 dériver la citation du SEUL spec
⇒ spec ≠ MANIFEST (artefact incohérent) · M6 retirer roadmap_ref du MANIFEST ⇒
ÉVAPORÉE ['frontend/chat_otoia'] · M7 citer l.9999 ⇒ plage hors roadmap (1..92).
Suite complète re-verte : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_regression · check_readme_claims tous exit 0.
Sprint 8 · buffer · RBAC SPEC §6 — ancrage de l'ÉNUMÉRATION des DocTypes custom sur le catalogue du contrat (2ᵉ surface prose)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention »). Le
générateur rbac_fixtures_gen émet MANIFEST.custom_doctypes_a_confirmer =
l'ensemble des DocTypes custom: true du contrat rbac_50_roles.json (les DocTypes
DTP à créer côté VPS avant import — Faisabilité, Publiciste Log, CONFOTUR Application, API Access). Cet ensemble data-derived est transcrit À LA MAIN à
deux surfaces prose : (1) rbac/fixtures_gen/README.md — déjà gatée (bloc
« RBAC fixtures · catalogue custom ») ; (2) RBAC_50_ROLES_SPEC.md §6 l.117
(« DocTypes custom: true (Faisabilité, Publiciste Log, CONFOTUR Application, API Access) ») — NON gatée. Le bloc apply_plan ne gate que le
COMPTE (len(custom_doctypes)), aveugle aux NOMS ; check_artifacts est aveugle
(la SPEC n'est pas un out/*.json).
Le « vert trompeur » — AJOUTER un DocType custom au contrat (le MANIFEST
rebâtit l'entrée) ou RENOMMER/ÉCHANGER l'un des 4 laisse §6 à ses 4 noms périmés en
silence : la cible d'application VPS énumérée par la SPEC ne correspond plus aux
DocTypes que l'agent ERPNext doit réellement créer. Vérifié par mutation : dropper
Publiciste Log de §6 laissait le gate à exit 0. Même patron que la 2ᵉ surface
CONFOTUR/entités (README gaté, fiche non).
Gate ajouté (ci/check_readme_claims.sh, extension du bloc « RBAC fixtures »,
point 3) — RÉUTILISE fx_cdt (= MANIFEST.custom_doctypes_a_confirmer, byte-gaté,
déjà dérivé par le point 1) et le normaliseur d'accents _fx_acc (zéro duplication
du contrat) : capte l'énumération §6 (DocTypes custom: true** (…) sur le texte
whitespace-normalisé, robuste au retour-ligne 117↔118), extrait les tokens
backtickés, et exige set-diff EXACT == catalogue MANIFEST (absent ET en trop, casse/
accents neutralisés). Énumération INTROUVABLE = échec (anti-évaporation #6).
Vérif — 1 check vert sur l'arbre propre (les 4 == MANIFEST). 5 morsures
adversariales : A dropper Publiciste Log ⇒ absent · B renommer API Access→API Acces ⇒ absent+en-trop · C ajouter Ghost DocType fantôme ⇒ en-trop · D
SILENT-GREEN : le MANIFEST GAGNE un 5ᵉ DocType custom (§6 inchangé) ⇒ mord « absents=
[new custom dt] » — preuve que l'autorité est l'ARTEFACT, pas la cohérence interne
de la SPEC · E énumération §6 supprimée ⇒ INTROUVABLE. Reverts par chemin ciblé
(jamais git checkout large — cf. incident consigné). Suite complète re-verte :
check_artifacts · check_docs · guard_constraints · check_ci_integrity ·
check_regression · check_readme_claims tous exit 0.
Note process — un candidat initial (ancrage des attributs de rôle des fiches
mobile/rendu/ifc_speckle) s'est révélé DÉJÀ couvert par les blocs existants
« Fiche Mobile » / « Fiches Faisabilité » : la ligne d'index MEMORY.md était
périmée vs le corps détaillé de la mémoire (classe close le 2026-08-01). Doublon
écarté (interdit #5 « jamais accumuler doublons »), index mémoire corrigé.
Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'ÉNUMÉRATION des scope_donnees (ligne Row-level) sur l'ENUM du schéma
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). La table §2 « Modèle RBAC » de
05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md ouvre par « trois niveaux, tous
couverts par le schéma » puis sa 3ᵉ ligne (« Row-level (User Permission) |
scope_donnees | … ») ÉNUMÈRE à la main l'ensemble EXHAUSTIF des portées
row-level : own · equipe · entite (Company) · groupe (consolidé). Par sa
phrase d'intro, cet ensemble EST l'enum scope_donnees de rbac.schema.json
(la DÉFINITION du modèle). Or aucun gate ne le touchait : §3 compte les rôles
par portail, §4 les entités, §5 le résumé, et le bloc userperm gate la table
scope → mécanisme d'un autre artefact (userperm_gen/out) — tous aveugles à
l'énumération du §2. Même classe que §4 (colonne ancrée sur une source-de-vérité
structurée), côté schéma cette fois.
Le « vert trompeur » — si le schéma AJOUTE une portée (region pour un besoin
ops réel) ou en RETIRE une (own supprimé, schéma resserré), le §2 reste PÉRIMÉ en
silence : il documente un modèle qui CONTREDIT le schéma tout en se réclamant
« couvert par le schéma ». Ni check_artifacts (la SPEC n'est pas un out/*.json)
ni tests/ (FONCTIONS de validation, jamais l'ancre SPEC↔schéma) ne mordent.
Gate ajouté (ci/check_readme_claims.sh, bloc « 1octies », juste après le §5)
— charge l'enum scope_donnees depuis rbac.schema.json (source de vérité du
modèle) puis exige : (a) ENSEMBLE des jetons scope backtickés de la ligne §2
== enum (set-diff absent ET fantôme) ; (b) ANCRAGE — la colonne du milieu NOMME
bien scope_donnees (sinon on gate la mauvaise ligne) ; (c) sanity — toute
portée RÉELLEMENT exercée par le contrat ∈ enum (une portée en usage jamais dans
l'enum = schéma/contrat incohérents). Les gloses parenthétiques (Company)/
(consolidé) NE sont PAS gatées (éditoriales · #6). Ligne/section/enum absents =
échec (traçabilité #6).
Vérif — 3 checks verts sur l'arbre propre (4 portées own/equipe/entite/groupe
== enum). 6 morsures adversariales : M1 §2 retire own ⇒ manquant (a) · M2 §2
ajoute fantôme region ⇒ fantôme (a) · M3 SILENT-GREEN : le schéma GAGNE region,
§2 inchangé ⇒ (a) mord manquant region — preuve que l'autorité est le SCHÉMA, pas
la cohérence interne de la SPEC · M4 colonne du milieu renommée scope_donnees→
scope ⇒ ancre perdue (b) · M5 ligne Row-level supprimée ⇒ INTROUVABLE · M6 le
schéma RETIRE own (encore exercé par 2 rôles) ⇒ (a) fantôme + (c) portée hors
enum. Gates re-verts : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_regression · check_readme_claims tous exit 0.
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) :
faisabilite/generator/faisabilite_gen.py::_validate_brief— la GARDE runtimecode[:2] == "P0" and code[2] in "123456789"(le domaine EFFECTIF) + le message« … invalide (attendu P01..P09) »(les bornes) ;faisabilite/version.schema.json—properties.projet.pattern "^P0[1-9]$"(validation duversion.jsonpar projet) + sadescription;faisabilite/generator/brief.schema.json— mêmepattern "^P0[1-9]$"(validation du brief d'entrée) + sadescription(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
chiffres »). La table §3 « Cartographie portails ↔ familles » de
05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md porte DEUX colonnes data-derived
du contrat rbac_50_roles.json : « Nb rôles » (déjà gatée, bloc §3 count) et —
juste à sa gauche — « Familles rattachées » : pour chaque portail, l'ENSEMBLE
des famille distinctes de ses rôles (ex. direction → {direction, finance,
faisabilite, legal}). Cette 2ᵉ colonne était NON gatée : le bloc §3 count la
capture avec [^|]* (il ne lit QUE le compte), le §5 « résumé » traite volontairement
les regroupements comme ÉDITORIAUX (pas de mapping famille↔groupe, cf. memory
rbac-spec-portail-count-gate), et check_artifacts est aveugle (la SPEC n'est
pas un out/*.json). Même classe que « colonne SET gatée seulement sur son COUNT
voisin » (roleprofile per-profil count vs quels rôles).
Le « vert trompeur » — reclasser la famille d'un rôle À L'INTÉRIEUR d'un
portail change le SET de familles sans changer le « Nb rôles » du portail → la
cellule §3 ment en silence pendant que le count-gate reste VERT. Prouvé (M4) :
reclasser les DEUX rôles legal de direction (Directeur Juridique + Agent ONAPI)
en finance laisse le compte à 9==9 VERT tandis que le nouveau gate mord
FANTÔME {legal} (la SPEC liste encore legal, le contrat ne l'a plus).
Ajouter/retirer une famille (rôle d'une famille inédite) laisse aussi la cellule
périmée. Aucune suite tests/ (FONCTIONS de validation, pas l'ancre SPEC↔contrat)
ne mord.
Gate ajouté (ci/check_readme_claims.sh, bloc « 1quater-bis » juste après le
§3 count-gate, RÉUTILISE src_roles/spec_rd déjà chargés) — RECOMPUTE le SET de
familles distinctes PAR portail depuis rbac_50_roles.json[roles][].famille et
exige, par ENSEMBLE (absent ET fantôme), l'égalité avec les jetons famille de la
cellule. On ne gate QUE les jetons famille bruts (identifiant de tête de chaque
item séparé par ,, via (?:^|,)\s*([a-z]+)), PAS les parenthèses ÉDITORIALES
(« finance (CFO) », « faisabilite (analyste/rendu/IFC/économiste) » — paraphrase,
pas contrat, #6). Ordre NON gaté (réordonner les rôles dans le contrat byte-gaté
est sémantiquement neutre pour l'appartenance). Un portail sans cellule échoue AUSSI
(INTROUVABLE = régression de traçabilité #6).
Vérif — 6 checks verts sur l'arbre propre (un par portail). 5 morsures
adversariales : M1 SPEC retire legal de direction ⇒ MANQUE {legal} · M2 SPEC
ajoute rh à achat ⇒ FANTÔME {rh} · M3 SPEC finance→financ sur compta ⇒
MANQUE {finance} · FANTÔME {financ} · M4 SILENT-GREEN : contrat reclasse les 2
rôles legal→finance dans direction, compte inchangé (count-gate 9==9 VERT) ⇒
familles-gate mord FANTÔME {legal} — preuve que l'autorité est le CONTRAT, pas la
cohérence interne de la SPEC · M5 SPEC supprime la ligne direction ⇒ cellule
INTROUVABLE. Gates re-verts : check_artifacts · check_ci_integrity ·
check_docs · check_readme_claims · check_regression · guard_constraints
tous exit 0.
Sprint 8 · buffer · Faisabilité — ancrage du TEMPLATE CANONIQUE v1.0 (5 surfaces #9/#10) sur CLAUDE.md
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « constante canonique CLAUDE.md recopiée à la main dans
une surface data-derived » (re-dérive la valeur depuis la contrainte, jamais la
cohérence interne) couvre déjà le générateur (model.py/README/renderer/scorer/
oracle-test), les fiches ERPNext/CRM (« Fiches #10 »), le module fiscal (moneda),
la marque SEO §Entités, la persona Chat OTOIA, les tokens branding #4, etc. Un
fichier restait NON gaté (grep : absent de tous les ci/*.sh) et c'est
paradoxalement le plus normatif : 05_deliverables_mvp/faisabilite/ TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md, le DOCUMENT-MODÈLE « CANONIQUE » (Livrable
Sprint 1) que toute faisabilité en production DOIT reproduire, et que le
générateur 4 volets + le Publiciste + la régénération batch S4 sont censés suivre.
Ce template recopie à la main les 6 valeurs de CLAUDE.md #9/#10 à CINQ surfaces structurées, toutes se réclamant de « contrainte #9 »/« #10 », aucune gatée :
- l.28 intro §0 « frais 3 % · marketing 8.5 % · point d'équilibre 52 % » (3 pourcentages #9, associés par mot-clé) ;
- l.144-146 §5.2 — 3 lignes de table « N % (fixe · contrainte #9) » ;
- l.29 intro §0 « USD + DOP · format Letter US · paiements Cardnet » ;
- l.149-151 §5.2 — 3 lignes « valeur (… contrainte #10) » (la ligne paiement citant l'exclusion « ❌ pas-X ») ;
- l.196 §7 checklist « 3 % / 8.5 % / 52 % · USD+DOP · Letter US · Cardnet présents » (énumération des 6 marqueurs).
Le « vert trompeur » — si CLAUDE.md #9 passe 52 %→55 % (ou #10 renomme le
prestataire) et que les modules s'alignent, ce TEMPLATE — la RÉFÉRENCE de toutes
les faisabilités — reste PÉRIMÉ en silence, imposant à chaque projet un paramètre
qui CONTREDIT le mandat pendant qu'il se réclame « contrainte #9/#10 ». Ni
check_artifacts (ce n'est pas un out/*.json) ni check_docs/guard_constraints
ni aucune suite tests/ (FONCTIONS, jamais l'ancre à CLAUDE.md) ne mordent.
Gate ajouté (ci/check_readme_claims.sh, bloc « TEMPLATE FAISABILITÉ CANONIQUE
v1.0 · PARAMÈTRES #9/#10 », juste après « Fiches #10 ») — RÉUTILISE exp (les 6
valeurs déjà recomputées des lignes #9/#10 de CLAUDE.md · zéro re-dérivation) +
exp_excl (prestataire exclu du (pas X)). Exige que chacune des 5 surfaces ==
CLAUDE.md : (1) 3 pcts associés par mot-clé ; (2) SET des 3 lignes « (fixe ·
contrainte #9) » == {3 %,8.5 %,52 %} (mord aussi ligne ajoutée/retirée) ; (3)/(4)
chaque valeur #10 présente en gras dans sa région ancrée + l'exclusion ❌ ; (5)
énumération des 6 marqueurs == ensemble EXACT (set-diff absent ET en trop). Une
surface INTROUVABLE échoue AUSSI (recensement évaporé = régression #6).
Vérif — 5 checks verts sur l'arbre propre. 11 morsures adversariales :
M1 intro#9 52→55 · M2 ligne#9 8.5→9 · M3 intro#10 Cardnet→Azul · M4 ligne#10
Letter US→A4 · M5 exclusion ❌ Stripe→PayPal · M6 checklist perd Cardnet · M7
checklist 52→60 · M8 intro#9 supprimée (INTROUVABLE) · M9 checklist « présents »→
« présent » (INTROUVABLE) · M10 CLAUDE.md #9 52→55 SEUL ⇒ cascade sur 3 régions
(1)(2)(5) · M11 CLAUDE.md #10 Cardnet→Azul SEUL ⇒ cascade sur 3 régions
(3)(4)(5) — les deux preuves que l'AUTORITÉ est CLAUDE.md, pas la cohérence
interne du template. Un piège corrigé en route : le groupe capturé est le NOMBRE nu
(sans %), donc _pct() (qui exige un %) renvoyait None → float() direct.
Gates re-verts : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_regression · check_readme_claims tous exit 0.
Sprint 8 · buffer · Fiscal e-CF — ancrage du CONTRAT DE FORMAT e-NCF dans le COMPOSEUR ncf.py (CODE) + son ORACLE de test
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le bloc « Fiscal · e-CF DGII · CONTRAT DE FORMAT » existant ancre le
format de l'identifiant fiscal DGII (E + tipoeCF(2) + secuencia(10) = 13,
Ley 32-23) sur le SEUL bloc e_ncf byte-gaté de out/ecf_plan.json (dérivé de
ecf_spec.json) et couvre 4 surfaces de PROSE : README ×3 + fiche
erpnext_backend. Mais le composeur d'exécution lui-même — ecflib/ncf.py,
le code Frappe qui VALIDE et COMPOSE réellement le numéro e-NCF côté VPS/Compupar
— porte TROIS copies INDÉPENDANTES du format, AUCUNE dérivée de l'artefact, +
un oracle de test qui les bénit, tous NON gatés :
ENCF_RE = re.compile(r"^E([0-9]{2})([0-9]{10})$")(l.26) — la regex de VALIDATION (cœur deis_valid_encf/parse_encf) ;FORMULE = "E + tipoeCF(2) + secuencia(10)"(l.27) — la chaîne publiée parcompose_encf(renvoyée au caller VPS) ;- docstring l.8 «
"E" + tipoeCF (2 chiffres) + secuencia (10 chiffres) → 13 caracteres» + commentaire l.25 « Ley 32-23 : E + tipo(2) + secuencia(10) = 13 » (deux proses-en-code) ; tests/test_ecf_dgii.py:246— oracle hardcodéassertEqual(…["formule"], "E + tipoeCF(2) + secuencia(10)").
Le « vert trompeur » — un rango DGII à 12 chiffres (évènement ops réel, cité
dans le commentaire du gate existant) régénère ecf_spec→ecf_plan, donc les 4
proses gatées SUIVENT. Mais ENCF_RE reste {2}{10} : compose_encf zfill à 10
et is_valid_encf REJETTE un e-NCF désormais valide → bug d'exécution du
composeur fiscal, pendant que la suite tests/ reste VERTE (son oracle ==
FORMULE périmée, self-consistante). Ni check_artifacts (ncf.py n'est pas un
out/*.json) ni tests/ (teste les FONCTIONS, pas leur ANCRAGE à l'artefact) ne
mordent. Même patron que l'oracle/formateur du Publiciste déjà gaté.
Gate ajouté (ci/check_readme_claims.sh, extension du bloc e-NCF · surfaces
5→9) — RÉUTILISE prefix/tipo_len/seq_len/total déjà dérivés du bloc e_ncf
byte-gaté + le helper ecf_check (zéro re-dérivation du format #6). Ancre les 5
surfaces CODE/test au SEUL artefact : (5) ENCF_RE, (6) FORMULE, (7) docstring,
(8) commentaire, (9) oracle de test. Une surface absente échoue AUSSI (INTROUVABLE
· anti-évaporation #6).
Vérif — 5 checks verts sur l'arbre propre. 7 morsures adversariales :
M1 ENCF_RE {10}→{12} · M2 FORMULE secuencia(10)→(12) · M3 docstring 10→12 chiffres · M4 commentaire secuencia(10)→(12) · M5 oracle test (10)→(12) ·
M6 SILENT-GREEN — l'ARTEFACT passe à seq=12 pendant que ncf.py reste à 10 ⇒
les 5 surfaces mordent TOUTES (preuve que l'autorité est l'artefact byte-gaté, pas
la cohérence interne du composeur) · M7 FORMULE renommée ⇒ INTROUVABLE. Les
39 tests unittest e-CF restent verts (composeur inchangé). Gates re-verts :
check_artifacts · check_ci_integrity · check_docs · check_readme_claims ·
check_regression · guard_constraints tous exit 0.
Sprint 8 · buffer · CRM Commissions — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (7ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO, Fiscal e-CF, Legal CONFOTUR,
CRM Dossier Vente et QA Audit 5D. Le 7ᵉ générateur — dernier candidat
restant recensé au ledger (crm/commissions ·10) — était NON gaté :
crm/commissions/commissions_gen.py (barème de commissions vendeurs · roadmap l.51).
Le compte « 10 invariants » y est transcrit à la main à CINQ endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- message de succès CLI
validate« … schéma + 10 invariants verts. » (l.209) ; - aide du sous-parseur
validate« schéma + 10 invariants » (l.221) ; - README bloc de commande « schéma + 10 invariants » (l.60) ;
- README titre de section « ## Les 10 invariants » (l.64) ;
- README prose de score « 10 invariants de cross-cohérence » (l.92).
Ajouter un contrôle # 11 · à _validate sans toucher les 5 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 11
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre Commissions (≈ Dossier Vente) : le ledger est PROPRE 1..10 — le schéma est compté À PART (phrasé « schéma + N invariants »), donc pas de schéma-#1 non marqué et pas de split a/b ; on garde la détection de doublon nu comme filet. Le module porte AUSSI un SECOND nombre — « 25 tests » (la suite unittest, déjà gatée par ailleurs) présent dans le README : l'ancre sur le mot « invariants » l'exclut nativement (« 25 tests » ≠ « invariants ») ⇒ aucun faux positif. Comme Dossier Vente, ce README A un titre « ## Les N invariants » ET une énumération numérotée du ledger → on gate (a)(b)(c)(d)(e).
Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · Commissions · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (c) l'annonce du titre README « ## Les N invariants » == |ledger| ;
- (d) toute mention « N invariants » du README == |ledger| ;
- (e) l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N. Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).
Vérif — 5 checks verts sur l'arbre propre (ledger 1..10 = 10 marqueurs
# N ·, schéma compté à part). 7 morsures adversariales : M1 self-report
.py 10→11 (b) · M2 titre README 10→11 (c+d) · M3 prose README 10→11 (d) · M4
ajout marqueur # 11 · ⇒ ledger→11 pendant que la prose reste 10 (mord .py ET
README titre/mention ET l'énumération, preuve que l'autorité est le CODE) · M5
suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de
vrai doublon (a) · M7 énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Le distinct
« 25 tests » reste INVISIBLE au gate (ancre « invariants »). Gates re-verts :
check_artifacts · check_docs · guard_constraints · check_ci_integrity ·
check_regression · check_readme_claims tous exit 0. Série ledger close : les
7 générateurs portant le nombre magique d'invariants sont désormais tous gatés.
Sprint 8 · buffer · QA Audit 5D — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (6ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO, Fiscal e-CF, Legal CONFOTUR et
CRM Dossier Vente. Le 6ᵉ générateur portant le même nombre magique restait
NON gaté : qa/audit_5d/audit_5d_gen.py (candidat restant recensé : crm/commissions
·10, ledger propre non gaté).
Le compte « 15 invariants » y est transcrit à la main à SIX endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- docstring sous-commande module (l.19 « valide schéma + 15 invariants ») ;
- docstring
_validate(l.80 « 15 invariants de cohérence audit/contrôles ») ; - message de succès CLI
validate« … 15 invariants — verdict … » (l.218) ; - aide du sous-parseur
validate« valide schéma + 15 invariants » (l.229) ; - README bloc de commande « schéma + 15 invariants » (l.66) ;
- README prose d'architecture « CLI build/validate + 15 invariants » (l.81).
Ajouter un contrôle # 16 · à _validate sans toucher les 6 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 16
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre Audit 5D (≈ Dossier Vente, ≠ Fiscal) : le ledger est
PROPRE 1..15 — le schéma est compté À PART (phrasé « schéma + N invariants »),
donc pas de schéma-#1 non marqué et pas de split a/b ; on garde la détection
de doublon nu comme filet. MAIS le module porte un SECOND nombre distinct —
« 17 contrôles » (le compte de fonctions-contrôle de qalib/controls.py, déjà
gaté par ailleurs), présent dans le .py ET le README : un \d+ invariants ancré
sur le mot « invariants » l'exclut nativement (« 17 contrôles » ≠ mot
« invariants ») ⇒ aucun faux positif sur l'arbre propre. Contrairement à
Dossier Vente/Fiscal, ce README n'a NI titre « ## Les N invariants » NI
énumération numérotée du ledger → on ne gate que (a)(b)(d).
Gate ajouté (ci/check_readme_claims.sh, bloc « QA · Audit 5D · IDENTITÉ du
COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (d) toute mention « N invariants » du README == |ledger|. Une mention ou le registre absent échoue AUSSI (traçabilité #6 · anti-évaporation).
Vérif — 3 checks verts sur l'arbre propre (ledger 1..15 = 15 marqueurs # N ·,
schéma compté à part). 6 morsures adversariales : M1 self-report .py 15→16
(b) · M2 mention README 15→16 (d) · M3 ajout marqueur # 16 · ⇒ ledger→16 pendant
que la prose reste 15 (mord .py ET README, preuve que l'autorité est le CODE) ·
M4 suppression # 7 · ⇒ gap non-contigu (a) · M5 doublon nu # 5 · ⇒ détection de
vrai doublon (a) · M6 no-false-positive : le distinct « 17 contrôles » reste
INVISIBLE au gate (l'ancre « invariants » l'exclut, arbre propre VERT). Gates
re-verts : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_regression · check_readme_claims tous exit 0.
Sprint 8 · buffer · CRM Dossier Vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (5ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO, Fiscal e-CF et Legal CONFOTUR. Le
5ᵉ générateur portant le même nombre magique restait NON gaté :
crm/dossier_vente/doctype_dossier_vente_gen.py (candidats restants recensés :
qa/audit_5d ·15, crm/commissions ·10, tous ledgers propres non gatés).
Le compte « 12 invariants » y est transcrit à la main à SEPT endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- docstring module (l.14 « schéma + 12 invariants de ») ;
- docstring
_validate(l.77 « 12 invariants de cross-cohérence ») ; - message de succès CLI
validate« … schéma + 12 invariants verts. » (l.218) ; - aide du sous-parseur
validate(l.230) ; - README bloc de commande « schéma + 12 invariants » (l.56) ;
- README titre de section « ## Les 12 invariants » (l.60) ;
- README prose de score « 12 invariants de cross-cohérence » (l.83).
Ajouter un contrôle # 13 · à _validate sans toucher les 7 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 13
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre Dossier Vente (≈ CONFOTUR, ≠ Fiscal) : le ledger est PROPRE 1..12 — le schéma est compté À PART (phrasé « schéma + N invariants »), donc pas de schéma-#1 non marqué et pas de split a/b. On garde néanmoins la détection de doublon nu comme filet. Le README porte AUSSI une énumération numérotée sous le titre de section (item 1 = conformité au schéma, puis les contrôles), gatée en contiguïté 1..N.
Gate ajouté (ci/check_readme_claims.sh, bloc « CRM · Dossier Vente · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (c) l'annonce du titre README « ## Les N invariants » == |ledger| ;
- (d) toute mention « N invariants » du README == |ledger| ;
- (e) l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N. Une mention ou le registre absent échoue AUSSI (traçabilité #6).
Vérif — 5 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs # N ·,
schéma compté à part). 7 morsures adversariales : M1 self-report .py 12→13
(b) · M2 titre README 12→13 (c+d) · M3 prose README 12→13 (d) · M4 ajout marqueur
# 13 · ⇒ ledger→13 pendant que la prose reste 12 (mord .py ET README ET
l'énumération, preuve que l'autorité est le CODE) · M5 suppression # 7 · ⇒ gap
non-contigu (a) · M6 doublon nu # 5 · ⇒ détection de vrai doublon (a) · M7
énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Gates re-verts : check_artifacts
· check_docs · guard_constraints · check_ci_integrity · check_regression ·
check_readme_claims tous exit 0.
Sprint 8 · buffer · Legal CONFOTUR — gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate (4ᵉ instance du ledger)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles
CI depuis le registre-source # N · de _validate, jamais une liste à la main)
a déjà couvert Chat OTOIA, SEO et Fiscal e-CF. Le 4ᵉ générateur
portant le même nombre magique restait NON gaté :
legal/confotur/confotur_application_gen.py.
Le compte « 14 invariants » y est transcrit à la main à SIX endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
- docstring module (l.24 « schéma + 14 invariants de ») ;
- docstring
_validate(l.87 « 14 invariants de cross-coherence ») ; - message de succès CLI
validate« … schéma + 14 invariants verts. » (l.283) ; - aide du sous-parseur
validate(l.295) ; - README bloc de commande « schéma + 14 invariants » (l.56) ;
- README commentaire d'arborescence « CLI build/validate · 14 invariants » (l.82).
Ajouter un contrôle # 15 · à _validate sans toucher les 6 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 15
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre CONFOTUR (≠ Fiscal) : le ledger est PROPRE 1..14
(pas de schéma-#1 non marqué, pas de split a/b). MAIS le README porte AUSSI un
compte de SOUS-ENSEMBLE éditorial — « 2 invariants refusent tout champ
monétaire » (l.92) — à NE PAS confondre avec le total. Un \d+ invariants naïf
mordrait donc l'arbre PROPRE (il verrait {2, 14}). Le gate exclut le
sous-ensemble par lookahead négatif (\d+)\s+invariants(?!\s+refusent),
appliqué au .py comme au README.
Gate ajouté (ci/check_readme_claims.sh, bloc « Legal · CONFOTUR · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ;
- (b) toute mention-total « N invariants » du
.py(hors « refusent ») == |ledger| ; - (c) toute mention-total du README (hors « refusent ») == |ledger|. Une mention ou le registre absent échoue AUSSI (disparition d'un chiffre = régression de traçabilité #6).
Vérif — 3 checks verts sur l'arbre propre (ledger 1..14 = 14 marqueurs # N ·).
6 morsures adversariales : M1 self-report .py 14→15 (b) · M2 README commande
14→15 (c) · M3 README arborescence 14→13 (c) · M4 ajout marqueur # 15 · ⇒
ledger→15 pendant que la prose reste 14 (mord .py ET README, preuve que
l'autorité est le CODE) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6
doublon nu # 5 · ⇒ détection de vrai doublon (a). M7 no-false-positive :
sous-ensemble « 2 → 3 invariants refusent » reste VERT (le lookahead exclut bien
le sous-ensemble). Gates re-verts : check_artifacts · check_docs ·
guard_constraints · check_ci_integrity · check_regression ·
check_readme_claims tous exit 0.
Sprint 8 · buffer · Publiciste — gate d'ANCRAGE des DEVISES du site public sur CLAUDE.md #10
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le bloc Publiciste/branding existant ancre les 4 tokens DESIGN
de CLAUDE.md #4 (2 couleurs + 2 typographies) sur leurs 4 copies. Mais le MÊME
module publiciste porte une 2ᵉ constante constitutionnelle — les devises
CLAUDE.md #10 (« USD + DOP ») — recopiée à 4 endroits data-derived, aucun
gaté, tous invisibles à check_artifacts (pas un out/*.json) :
lib/branding.pyDEVISE_PRIMAIRE/DEVISE_SECONDAIRE— constantes auto- étiquetées « contrainte #10 · USD + DOP », sans gate ;lib/generator.py_fmt_usd/_fmt_dop— les formateurs qui IMPRIMENT le code devise sur la grille de prix du site publicvente.otov7.com(sortie face-public : une devise hors #10 = mandat trahi en silence) ;lib/generator.pyfooter HTML « Prix … en USD + DOP (Cardnet) » ;tests/test_publiciste.py— l'oracle hardcodéassertIn("USD 150 000", …), une copie de plus jamais comparée à CLAUDE.md.
Aucune suite tests/ (qui teste des fonctions de rendu, pas l'ancre à
CLAUDE.md) n'attrape la dérive : renommer la devise #10 et aligner le module
reste vert alors que le site émettrait la mauvaise devise ; ne rien bouger reste
vert alors que branding.py se dit « #10 » en mentant.
Gate ajouté (ci/check_readme_claims.sh, bloc « Publiciste/generator+branding
· DEVISES CANONIQUES ») — RE-PARSE les 2 devises depuis la ligne #10 de CLAUDE.md
(indépendamment de exp amont · aucun skip hérité) puis exige :
- (a)
branding.pyDEVISE_PRIMAIRE/SECONDAIRE == #10 (ordre inclus) ; - (b) l'ensemble des codes émis par les formateurs
generator.py== #10 ; - (c) le footer HTML cite les 2 devises dans l'ordre +
(Cardnet); - (d) l'oracle du test == devise primaire #10. Un claim absent échoue AUSSI (traçabilité #6 · anti-évaporation).
Vérif — 5 checks verts sur l'arbre propre. 6 morsures adversariales :
M1 branding primaire USD→EUR · M2 formateur _fmt_usd→EUR (hors #10) · M3 footer
USD+DOP→USD+EUR · M4 oracle test → EUR · M5 CLAUDE.md #10 USD→EUR = cascade sur
les 4 copies simultanément (preuve que l'ancre est CLAUDE.md, pas la cohérence
interne) · M6 footer supprimé → INTROUVABLE. Gates re-verts :
check_readme_claims · check_docs · guard_constraints · check_ci_integrity
tous exit 0.
Sprint 8 · buffer · Fiscal e-CF DGII : gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » a déjà été appliqué à Chat
OTOIA et SEO (recompute le nombre d'invariants CI depuis le registre-source
# N · de _validate, jamais une liste à la main). Le 3ᵉ générateur portant
le même nombre magique restait NON couvert : fiscal/ecf_dgii/ecf_dgii_gen.py.
Le compte « 12 invariants » y est transcrit à la main à SEPT endroits,
aucun gaté, tous invisibles à check_artifacts (le .py source n'est pas un
out/*.json) :
ecf_dgii_gen.pydocstring module (l.24) ;- docstring
_validate(l.110) ; - message de succès CLI
validate« … schéma + 12 invariants verts. » (l.277) ; - aide du sous-parseur
validate(l.289) ; - README bloc de commande « schéma + 12 invariants » (l.65) ;
- README titre de section « ## Les 12 invariants » (l.69) ;
- README prose de score « 12 invariants de cross-cohérence » (l.105).
Ajouter un contrôle # 13 · à _validate sans toucher les 7 chaînes ⇒ le README
ET les self-reports du .py mentent en silence pendant que la CI applique 13
contrôles. Aucune suite tests/ (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
Particularité du registre fiscal (≠ SEO/Chat OTOIA au ledger propre 1..N) :
l'invariant 1 est la validation de SCHÉMA (validate(bundle, schema), non
marquée # 1 ·), les marqueurs couvrent 2..N, et l'invariant 12 est
splitté en # 12a · (unicité évènements) / # 12b · (comptes du manifeste) —
deux volets d'un même invariant. Le gate reconstruit donc le ledger = {1:schéma}
∪ marqueurs-top-level, en collapsant 12a/12b, tout en mordant un vrai doublon
d'un numéro NON suffixé.
Gate ajouté (ci/check_readme_claims.sh, bloc « Fiscal · e-CF DGII · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# N · de _validate) et exige :
- (a) ledger CONTIGU 1..N (schéma #1 présent + marqueurs 2..N sans lacune ; doublon nu = défaut mordu ; 12a/12b comptés 1×) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (c) l'annonce du titre README == |ledger| ;
- (d) toute mention « N invariants » du README == |ledger| ;
- (e) l'ÉNUMÉRATION numérotée du README (« 1. … 2. … N. ») elle-même contiguë 1..N (item ni sauté ni dupliqué).
Vérif — 5 checks verts sur l'arbre propre (ledger 1..12 = schéma #1 + 11
marqueurs). 8 morsures adversariales : M1 self-report .py 12→13 (b) · M2
titre README 12→13 (c+d) · M3 mention README 12→13 (d) · M4 ajout marqueur # 13 ·
⇒ ledger→13 pendant que la prose reste 12 (b+c+d, preuve que l'autorité est le
CODE, pas la prose) · M5 suppression # 7 · ⇒ gap non-contigu (a) · M6 doublon nu
# 5 · ⇒ détection de vrai doublon · M7 énumération README 7.→8. ⇒ deux 8/pas de
7 (e) · M8 suppression validate(bundle, schema) ⇒ invariant 1 introuvable. Gates
re-verts : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_readme_claims · check_regression tous exit 0.
Sprint 8 · buffer · Mobile app_config : gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate_bundle (8ᵉ instance)
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). La classe « ledger d'invariants » (recompute le nombre d'invariants CI
depuis le registre-source numéroté de la fonction de validation, jamais une liste à
la main) était déjà appliquée à 7 générateurs : Chat OTOIA · SEO · Fiscal e-CF ·
Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM Commissions. Le 8ᵉ
générateur portant le même nombre magique restait NON couvert :
mobile/app_config/app_config_gen.py.
Le compte « 12 invariants » y est transcrit à la main à QUATRE endroits,
aucun gaté, tous invisibles à check_artifacts (le .py/.md/test n'est pas un
out/*.json) :
app_config_gen.pymessage de succès CLI « … schéma + 12 invariants verts. » (l.228) ;README.mdbloc de commande « schéma + 12 invariants » (l.48) ;README.mdprose de récap « Schéma + 12\ninvariants verts » (l.67-68 — le « 12 » et le mot séparés par un saut de ligne) ;tests/test_mobile_app_config.pydocstring « … + les 12 invariants du CLI » (l.4).
Ajouter un contrôle # Invariant 13 · à _validate_bundle sans toucher les 4
chaînes ⇒ le README, le self-report CLI ET la docstring de test mentent en silence
pendant que la CI applique 13 contrôles. Aucune suite tests/ (qui EXÉCUTE la
validation mais ne compte jamais la prose) ne l'attrape.
Particularités vs les 7 blocs précédents (documentées dans l'en-tête du gate) :
- le registre-source utilise le style
# Invariant N ·(le mot « Invariant » précède le numéro) — les blocs ancrés sur le style nu# N ·NE le voient PAS ; ancrage sur#\s*Invariant\s+(\d+)…; - la fonction s'appelle
_validate_bundle(pas_validate) — l'ancredef _validate\bdes autres blocs échoue (\béchoue avant_bundle) ; - ce README n'a NI titre « ## Les N invariants » NI énumération numérotée → on gate (a)(b)(b')(d), en AJOUTANT la surface fichier-de-tests (b'), absente des 7 blocs précédents (leurs tests ne portaient pas le chiffre).
Gate ajouté (ci/check_readme_claims.sh, bloc « Mobile · app_config · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(# Invariant N · de _validate_bundle) et exige :
- (a) ledger CONTIGU 1..N (schéma compté à part ; lacune OU doublon nu = défaut mordu) ;
- (b) toute mention « N invariants » du
.py== |ledger| ; - (b') toute mention « N invariants » du fichier de tests == |ledger| ;
- (d) toute mention « N invariants » du README == |ledger|.
Vérif — 4 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs). 8
morsures adversariales : M1 self-report CLI 12→13 (b) · M2 README bloc-commande
12→13 (d) · M3 README récap (newline-split) 12→13 (d, preuve que \s+ traverse le
saut de ligne) · M4 docstring test 12→13 (b', la surface neuve) · M5 ajout marqueur
# Invariant 13 · ⇒ ledger→13 pendant que les 3 surfaces prose restent 12 (b+b'+d,
preuve que l'autorité est le CODE, pas la prose) · M6 suppression # Invariant 7 ·
⇒ gap non-contigu (a) + désync prose · M7 doublon nu # Invariant 5 · ⇒ détection
de vrai doublon · M8 renommage _validate_bundle ⇒ registre introuvable. Gates
re-verts : check_artifacts · check_docs · guard_constraints ·
check_ci_integrity · check_readme_claims · check_regression tous exit 0.
Sprint 8 · buffer · Doc QA : documentation du 2ᵉ étage QA (batterie de gates statiques ci/*.sh) dans 03_agents/qa/AGENT.md
Démarche — vérifier AVANT de gater (mémoire verify-uncovered-before-gating).
La série récente empile des gates anti-dérive ; avant d'en ajouter un 9ᵉ, j'ai
d'abord cherché une vraie dérive présente plutôt qu'un gate spéculatif :
- Scan ledger-vs-prose sur les 12 générateurs à ledger d'invariants. Faux
signal relevé :
fiscal/ecf_dgiisemblait afficherledger_markers=10vsprose=12. Investigation : le ledger n'est pas nu 1..12 — l'invariant #1 (validation de schéma,validate(bundle, schema)) n'est pas marqué# 1 ·par design, et #12 est scindé en12a/12b. Le gate fiscal (check_readme_claims.shl.1908) synthétise déjàledger = marqueurs{2..12} ∪ {1}et vérifie self-report / titre README / mentions / énumération = 12. Aucune dérive ; mon compteur grossier ne voyait ni le schéma-#1 ni12a/12b. - Sondé les comptes de
03_agents/{crm,qa}/AGENT.md(« Total CRM 81 (25+31+25) », « 17 contrôles / 5 dimensions », « 8 livrables + 7 métriques ») → tous gatés (l.681, 871, 2176, 2624, 3671…). Coverage très dense, conforme à la mémoire.
Conclusion : pas de gate à ajouter (ç'aurait été redondant · CLAUDE.md #5).
Trou documentaire réel comblé. Le titre + le scope de qa/AGENT.md promettent
des « gates de méta-niveau », mais le corps ne tabulait que les 4 modules
Python d'audit (audit_5d · audit_4big · regression · acceptance) — il
omettait entièrement la batterie de gates statiques ci/*.sh, qui sont
pourtant l'application méta-niveau littérale (moitié de l'enforcement QA). Un
lecteur croyait donc QA = 4 modules.
Ajout — section « Deuxième étage QA : la batterie de gates statiques (ci/*.sh) »
listant les 7 gates courants par nom + rôle (check_ci_integrity,
check_readme_claims, check_artifacts, check_regression, check_docs,
guard_constraints, validate_json). Anti-dérive by design : le texte ancre
l'autorité du périmètre sur check_ci_integrity (qui découvre l'ensemble via
git ls-files 'ci/*.sh' et refuse tout gate décâblé/orphelin) et présente la liste
comme l'état courant, PAS une constante figée → aucun compte agrégé driftable
introduit.
Sûreté — prose sans lien Markdown neuf (check_docs), sans URL git interdite ni
terme CRM/paiement banni nu ; les lignes citant les prohibitions portent un
marqueur (proscrites/interdits/bannie) donc guard_constraints les laisse
passer. Vérif : 7 gates re-verts (guard_constraints · check_docs ·
check_readme_claims · check_ci_integrity · check_artifacts · check_regression ·
validate_json tous exit 0).
Sprint 8 · buffer · Angle mort du gate « comptes de tests par suite » : la cellule rbac (fiche pointant une SPEC, pas un README)
Démarche — vérifier AVANT de conclure « couvert » (mémoire verify-uncovered-before-gating).
La série récente ayant épuisé les surfaces driftables évidentes, j'ai audité la
complétude d'un gate existant plutôt que d'en empiler un neuf (CLAUDE.md #5).
Constat. Le gate check_readme_claims.sh §« Fiches agents · comptes de tests
PAR SUITE (colonne « Tests ») » recompute chaque cellule | N | des tables de
livrables des 03_agents/*/AGENT.md depuis l'autorité regression_plan.json
(plan.suites[path].test_methods). Son row_re exigeait pourtant que le lien de
la ligne pointe …/README.md. Or une ligne y échappe : la fiche
erpnext_backend (l.28) pointe le module rbac/ via son point d'entrée
RBAC_50_ROLES_SPEC.md (le contrat 50-rôles, pas un README) — donc son
row_re ne mordait pas, et sa cellule « Tests | 10 | » restait non vérifiée.
Mutation-test (preuve du trou). Cellule rbac 10 → 99 sur la fiche →
check_readme_claims restait VERT (exit 0), aucune ligne rbac évaluée. C'est
exactement la classe « vert trompeur » que cette section du gate existe pour tuer
(un régénérateur de fiche pourrait y écrire un faux compte sans que la CI bronche).
La somme prose « RBAC 60 tests (10+11+12+11+16) » est order-indépendante → ne
contraint pas la cellule-table rbac individuelle. Vrai angle mort.
Correctif (extension du gate, pas gate N+1). row_re accepte désormais tout
*.md d'entrée du dossier module (/[\w.-]+\.md au lieu de /README\.md), pas
seulement README — le garde path not in auth (déjà présent) protège de tout
élargissement fortuit : seules les lignes pointant une suite gatée connue sont
évaluées, les autres restent ignorées. Une seule ligne entre ainsi dans le
périmètre : rbac. Commentaire mis à jour pour documenter le cas SPEC.
Vérif. (1) Arbre propre VERT + la ligne rbac est désormais VUE
(AGENT.md:28 · rbac — Tests 10 == source (10)). (2) Mutation 10→99 désormais
MORDUE (Tests 99 MAIS source dit 10 → régénérer la fiche, exit 1). (3)
Restauration → exit 0. 7 gates re-verts : check_ci_integrity · check_artifacts ·
check_docs · check_readme_claims · check_regression · guard_constraints ·
validate_json tous exit 0.
Sprint 8 · buffer · Reprise du canal daily_reports/ (lapsus depuis 2026-07-30) + sweep launch-readiness sourcée
Constat. Le canal canonique des rapports quotidiens
(05_deliverables_mvp/daily_reports/YYYY-MM-DD.md · fixé roadmap §Escalations,
référencé par les 13 fiches 03_agents/*/AGENT.md) n'avait plus d'instantané
consolidé depuis le 2026-07-30. Or ~2 douzaines de sessions buffer ont continué
depuis — leur trace fine vit dans 05_activity_log/ (journal technique par
session), mais aucun snapshot MVP sourcé n'existait pour la phase Sprint 8.
Trou de reporting réel au niveau stakeholder/direction.
Décision (workflow #5 — pas de gate redondant). La série récente ayant épuisé
les surfaces driftables évidentes en durcissant des gates, et la phase Sprint 8
attendant une vérification de régression exhaustive (pas un module neuf), j'ai
(a) mené une sweep de vérification launch-readiness et (b) repris le canal
daily_reports/ plutôt que d'empiler un gate N+1.
Réalisé.
- Créé
05_deliverables_mvp/daily_reports/2026-08-02.md: instantané MVP 100 % sourcé (chaque chiffre re-vérifié contre l'artefact commité · anti-invention #6),- note de convention réconciliant les 2 canaux de journalisation
(
05_activity_log/= technique/session ·daily_reports/= synthèse/direction — pas des doublons ; un lecteur d'AGENT.mdétait mal aiguillé vers un canal figé au 30/07).
- note de convention réconciliant les 2 canaux de journalisation
(
- Audit doc-vs-réalité de 2 fiches (interdit « documenter du code sans vérifier
son existence courante ») :
crm/AGENT.mdtable livrables exacte (25/31/25 = 81, recomptés depuisregression_plan.json) ;bim/AGENT.md— son seul point de contact commitéfaisabilite/generator/genlib/renderer.pyexiste et rend bien le Volet 4 Ingénierie en{{…}}. Aucune sur-promesse.
État courant sourcé (recalculé indépendamment, concordant) : 22/22 modules
qualité 4Big à 100/100 (PASS) · régression 22 suites · 564/564 tests
(PASS) · recette 15/15 promesses in_repo (true) · 13 agents · 7 gates
CI verts + reproductibilité out/ byte-identique.
Vérif. Les 7 gates re-verts (exit 0) avec le nouveau fichier : check_docs (liens du rapport résolus) · guard_constraints (aucun terme interdit introduit) · check_readme_claims · check_ci_integrity · validate_json · check_artifacts · check_regression. Sûreté : rapport dans le canal canonique (roadmap), zéro chiffre inventé, tous liens Markdown vers cibles existantes.
Sprint 8 · buffer · Audit d'exactitude doc-vs-réalité des 11 fiches AGENT.md restantes — 0 dérive réelle · 2 « faux positifs » d'audit levés · 1 clarification de lisibilité (faisabilite)
Nature — Poursuite de la tâche explicitement recommandée par le daily report
2026-08-02 (« Poursuivre l'audit d'exactitude des 11 fiches AGENT.md restantes,
mêmes contrôles que crm/bim — repérer toute sur-promesse d'artefact ou compte
périmé »). CLAUDE.md interdit « documenter du code sans vérifier son existence
courante ». Pas un gate de plus (workflow #5) : vérification, pas empilage.
Méthode — 4 agents Explore en parallèle sur les 11 fiches (devops ·
erpnext_backend · faisabilite · frontend_console · ifc_speckle · mobile ·
onapi_legal · publiciste · qa · rendu · seo). Pour chaque fiche : existence de
CHAQUE chemin in-repo cité · recompute de CHAQUE compte (tests via grep -c 'def test_' / regression_plan.json · invariants via marqueurs numérotés) ·
présence de CHAQUE artefact out/*.json · toute sur-promesse d'un livrable
in-repo inexistant.
Résultat brut des agents — 9 fiches « FULLY ACCURATE » (comptes de tests
recoupés : deploy_runbook 29 · e-CF 39 · confotur 44 · publiciste 23 · audit_5d 37
· audit_4big 34 · regression 26 · acceptance 37 · seo 36 · portails 19 · chat 31 ;
SEO 258 mots-clés / 10 nœuds schema.org / 10 pages hreflang ; Expo SDK 54). 2
fiches signalées en « dérive » — toutes deux VÉRIFIÉES FAUX POSITIFS après
contrôle manuel (mémoire verify-uncovered-before-gating : prouver avant d'agir) :
-
onapi_legal (agent : 3 chemins « CRITICAL cassés » l.40/49/99 :
../rbac/rbac_50_roles.json,../crm/workflow_vente/[out/]). Faux positif : ces chemins backtick sont relatifs à l'arbre des livrables (05_deliverables_mvp/legal/, sujet de la fiche §24), PAS à l'emplacement de la fiche. Ils MIROITENT le code du générateur :rbac_scan.pyfaitos.path.join(_DELIVERABLES, "rbac", "rbac_50_roles.json")etbuilder.pypublieworkflow_source: "crm/workflow_vente/workflow_vente_spec.json". Les 3 cibles existent bien depuis05_deliverables_mvp/legal/(vérifiéls). L'agent avait supposé à tort une résolution fiche-relative. Convention documentée dans la mémoirebacktick-path-escapes-check-docs(les code-spans backtick sont volontairement NON link-checkés). Aucune correction — la fiche est exacte. -
faisabilite (agent :
data_room/_TEMPLATE_…/,_META/version.json,otoia/capabilities/knowledge/faisabilite_template_v{X}.md« HIGH · sources inexistantes »). Faux positif : ces chemins sont explicitement VPS-runtime / « Composants à créer » — préfixe absolu/opt/oto/…, placeholder de version{X}, section §95 « Composants à créer ». La fiche les réconcilie déjà (§15-17 « composants runtime OTOIA/VPS … restent hors périmètre worker » ; §27-29 « aucune faisabilité concrète PXX n'est commitée ici … le rendu réel … s'exécute côté OTOIA/VPS #8 »). Hors-repo ASSUMÉ, pas une sur-promesse. Table §Livrables exacte :TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md(existe) · generator 16 tests · bancable 22 tests. Aucune sur-promesse in-repo.
Seule action — clarification de LISIBILITÉ (faisabilite), pas un correctif de
dérive. Le point qui a induit l'agent en erreur est une vraie ambiguïté pour un
lecteur humain : le bloc « Sources canoniques » (§31-34) listait 3 chemins VPS
{X} sans signaler que la contrepartie commitée/testée/gatée du template
canonique est l'in-repo TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md (pourtant déjà en
tête de la table §Livrables). Ajout d'une note additive (style de réconciliation
déjà employé §15-17) : bandeau « Chemins VPS runtime — hors périmètre worker (#8),
non commités in-repo » + lien Markdown vers le template committé « référence de
vérité pour le worker ». Zéro chiffre, zéro constante, zéro fait inventé ;
purement désambiguïsant.
Sûreté / vérif — Avant édition : grep confirme qu'AUCUN gate ne parse la fiche
faisabilite ni la section « Sources canoniques » (édition prose libre). Après :
7 gates re-verts (exit 0) — check_ci_integrity · check_artifacts ·
check_docs (le nouveau lien Markdown résout vers une cible existante) ·
check_readme_claims · check_regression · guard_constraints (aucun terme
interdit introduit) · validate_json. Bilan audit : les 13 fiches
AGENT.md sont désormais TOUTES auditées exactes (crm/bim la session précédente +
les 11 ici) ; les 2 « dérives » signalées étaient des artefacts de convention
(backtick deliverables-relative · chemins VPS/futurs assumés), pas des dérives.
Session 20260802_120544 · Clôture de journée du canal daily_reports/
Contexte — Reprise en phase Sprint 8 · buffer (tous sprints 1→7 livrés
côté repo). Relu CLAUDE.md, la roadmap, MEMORY.md et le journal du jour. Constat :
la file de travail substantielle est saturée — 7 gates verts, 22/22 qualité,
564/564 régression, 15/15 recette, 13/13 fiches AGENT.md auditées exactes
(campagne close au commit 2bc3afb, session 113542).
Décision (workflow #5 · pas d'empilage de gate redondant ; #6 · anti-invention)
— Aucune tâche roadmap ouverte ni blind-spot prouvé. Plutôt qu'un gate N+1 ou un
faux « module », réconcilier le reporting stakeholder : le snapshot canonique du
jour 05_deliverables_mvp/daily_reports/2026-08-02.md (écrit à 11:11) était devenu
vérifiablement périmé — sa propre « Prochaine tâche #2 » (auditer les 11 fiches
restantes) et sa réserve −4 (« audit limité à 2/13 fiches ») ont été closes à
11:35 par le commit 2bc3afb.
Réalisé
- Recompute indépendant (
python3sur les artefacts commités) de TOUS les agrégats de tête, concordant : qualité 22 modules PASS (pass_score95) · régression 22 suites / 564 ran / 564 passés / 0 échec · recette matrice de 15 items, verdicttrue· 13 répertoires03_agents/*/. - Addendum de clôture ADDITIF au snapshot
2026-08-02.md(corps 110539 laissé intact comme record historique) : acte la clôture 13/13 fiches, lève la réserve −4 caduque, réaffiche l'état re-vérifié (inchangé/vert), révise la « prochaine tâche » (file d'audit vide → veille buffer). Zéro chiffre inventé — chaque valeur re-dérivée d'un artefact. - Choix anti-doublon (interdit #5 · « JAMAIS accumuler doublons ») : addendum au fichier-jour canonique plutôt qu'un énième fichier per-session quasi-identique.
Vérifications — 7 gates CI re-joués → exit 0 (check_ci_integrity ·
check_artifacts byte-identique · check_docs liens résolus · check_readme_claims
· check_regression · guard_constraints aucun terme interdit · validate_json).
Aucune commande touchant au VPS.
Non fait (hors périmètre #8) — déploiement serveur réel, runner Gitea, Playwright
serveur : VPS 153.75.250.214, plan ordonné devops/deploy_runbook.
Session 20260802_123546 · Dérive « 12 agents » sur les docs de mandat de tête (hors README)
Contexte — Reprise en Sprint 8 · buffer. Relu CLAUDE.md, roadmap (0 item
ouvert), MEMORY.md, journal du jour. Re-vérification indépendante multi-angle de la
santé (recompute python3 sur artefacts commités) : 7 gates → exit 0 · qualité
22/22 @ 100/100 (verdict PASS · scores tous à 100, pas seulement le seuil) ·
régression 22 suites / 564 tests / 0 échec, 13/13 modules présents dans la
matrice · recette 15/15 in_repo · constantes canoniques (3 %/8.5 %/52 %) toutes
ancrées · README (13 agents ×2, 22/22, 15 promesses) exact vs artefacts.
Blind-spot RÉEL trouvé (surface distincte de l'audit fiches 03_agents/* clos au
2bc3afb) — audit des docs de mandat de TÊTE, non couverts par le gate de claims
(scopé README.md uniquement). PORTAIL_BANCABLES_4BIG.md vérifié exact (commande +
sorties 50_financier_bancable/{fr,en,es}.md concordent avec bancable_gen.py). Mais
deux docs affichaient encore « 12 agents » :
AGENTS_EXISTING_ASSETS.md:1(titre) ·02_master_prompt/MASTER_PROMPT_DTP_v2.md:28.
Réalité re-dérivée : 13 agents — 13 répertoires 03_agents/, README canonique
« 13 agents » (×2, déjà gaté), le 13e (publiciste) LIVRÉ+GATÉ (fiche +
générateur 05_deliverables_mvp/publiciste/ 100/100 + suite de régression
publiciste). Les « 12 » précédaient l'ajout du publiciste → dérive doc-vs-réalité.
Réalisé
- Correction du compte 12 → 13 dans les deux docs (fait objectif,
03_agents/= source unique · #6 anti-invention). - Clarification publiciste (
AGENTS_EXISTING_ASSETS.md§13) : marqueur « (à créer) » levé, note « Statut mandat : LIVRÉ » pointant fiche + générateur gaté, tout en préservant la liste « À développer » comme runtime VPS hors périmètre worker (#8) — respecte la convention « VPS-runtimeà créer≠ dérive » (memoryfiche-accuracy-audit-closed). Deux liens Markdown ajoutés → résolus (check_docs). - Extension du gate
check_readme_claims.sh(surface prouvée NON couverte par mutation-test) : nouvelle boucle qui réutilise len_agentsdéjà dérivé degit ls-files 03_agents/*/AGENT.md(pas de nouvelle machinerie · #5 pas de doublon) et exige que les DEUX docs de tête citent exactement ce compte. Classedoc-numeric-claims-gate.
Vérifications — nouveau check mutation-testé (12 réinjecté → gate ROUGE « dit 12 MAIS 03_agents/ en compte 13 », restauré → exit 0). 7 gates re-joués → exit 0. Aucune commande touchant au VPS.
Non fait (hors périmètre #8) — runtime VPS publiciste (publiciste.py, systemd/
inotify, WhatsApp), déploiement serveur : hors périmètre worker.
Sprint 8 · buffer · Compte d'agents — ancrage des 3 surfaces vives de GAP_ANALYSIS_SPRINT1.md (dont la coche de couverture DoD) sur la source unique n_agents
Contexte — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de chiffres »).
Le commit précédent (cb8d931) a ancré « 13 agents » à git ls-files 03_agents/*/AGENT.md
(source unique n_agents) sur les 2 docs de mandat de tête (AGENTS_EXISTING_ASSETS titre +
MASTER_PROMPT §28), en plus du README ×2. Un balayage de complétude a exhumé une 3ᵉ
surface entièrement HORS gate : le livrable Sprint 1 05_deliverables_mvp/ GAP_ANALYSIS_SPRINT1.md (Definition of Done) retranscrit ce même compte à trois
endroits vifs — l.12 périmètre d'audit (« audit des 13 agents DTP »), l.53 constat
global (« 12 des 13 agents partent d'un socle existant »), et surtout l.214 la coche
de couverture DoD (« Couverture 13 agents + OTOIA core ✅ »). Cette coche AFFIRME une
complétude COURANTE : elle DOIT suivre le nombre réel de fiches.
Le « vert trompeur » — PROUVÉ ungate avant ajout (mémoire
verify-uncovered-before-gating) : GAP_ANALYSIS n'est référencé par AUCUN ci/*.sh
(grep = 0) ; muter l.214 « 13 »→« 14 » laissait check_readme_claims exit 0. Or ce
NOMBRE a une dérive prouvée : le publiciste fut ajouté comme 13ᵉ agent (12→13), forçant
la correction de cb8d931. Si un 14ᵉ agent était mandaté, la coche DoD mentirait en silence.
check_artifacts est aveugle (la SPEC n'est pas un out/*.json) ; aucune suite tests/ ne
touche l'ancre doc↔disque.
Gate ajouté — EXTENSION de la boucle head-docs existante (ci/check_readme_claims.sh,
bloc « Comptes d'agents dans les docs de mandat de tête »), pas un bloc neuf : 3 tuples
(GAP, motif, label) réutilisant le n_agents DÉJÀ dérivé (zéro nouvelle machinerie ·
zéro re-dérivation · #5). On n'ancre QUE le total — le « 12 » analytique de l.53
(agents à socle existant) N'EST PAS gaté (motif \d+ des (\d+) agents capture le 2ᵉ nombre).
Surface INTROUVABLE = échec (anti-évaporation #6). ci/README.md mis à jour : le décompte
d'agents est désormais documenté comme ancré à 7 surfaces vives.
Vérif — 3 checks verts sur l'arbre propre (les 3 == 13). 6 morsures adversariales,
reverts par cp ciblé (jamais git checkout large — cf. incident consigné) : M1 DoD
13→14 (mord) · M2 périmètre 13→99 (mord) · M3 constat total 13→12 (mord) · M4
ligne DoD supprimée ⇒ INTROUVABLE · M5 no-false-positive : muter le « 12 » analytique
(12→11), total intact ⇒ gate exit 0 (preuve qu'on ne gate que le total) · M6
SILENT-GREEN : dérive de la SOURCE (git rm --cached d'une fiche ⇒ n_agents=12), docs
figés à 13 ⇒ les 5 surfaces head-docs+GAP mordent « dit 13 MAIS 03_agents/ en compte 12 »
(preuve que l'autorité est le git-index, pas la cohérence interne). Arbre restauré. 7 gates
re-joués → exit 0 : check_ci_integrity · check_artifacts · check_docs · check_readme_claims ·
check_regression · guard_constraints · validate_json. Aucune commande touchant au VPS.
Sprint 8 · buffer · roles_couverts (« 44 ») — enquête anti-dérive → non-surface prouvée + documentation du garde-fou mono-source (évite un gate N+1 redondant #5)
Contexte — Série anti-dérive Sprint 8. Un balayage des comptes numériques a
exhumé un candidat de cohérence croisée : le total « 44 rôles couverts par les
5 portails » apparaît, identique, dans trois manifestes byte-gatés
(frontend/portails, frontend/chat_otoia, mobile/app_config ·
counts.roles_couverts=44). Chaque « 44 » vif n'est ancré qu'à SON propre manifeste
(mobile README · demo/scenarios/out/run_sheet.md). Aucun ci/*.sh ne compare les
trois entre eux. Hypothèse initiale : surface non couverte, analogue au cross-check
RBAC « 50 » (rbac/apply_plan/rbac_apply_plan.py:149 exige
fixtures==userperm==roleprofile).
Enquête — l'analogie RBAC-50 ne tient PAS (mémoire fix-vs-gate-transitively-
protected-constant). Le « 50 » EST cross-gaté car ses trois rendus re-dérivent
chacun leur décompte avec leur propre logique. Ici, au contraire, les trois
modules importent la même fonction de partition : mobilelib/deps.py:77 et
chatlib/deps.py:84 font tous deux roles_by_portail = portails_builder.roles_by_portail
(unique définition frontend/portails/wslib/builder.py:36), appliquée à l'unique
contrat rbac/rbac_50_roles.json (une seule copie de chaque dans git ls-files).
roles_couverts est donc transitivement mono-sourcé (code partagé + donnée
partagée) : les trois totaux ne peuvent diverger que si cette fonction ou ce
contrat change — auquel cas les trois changent ensemble et check_artifacts
(reproductibilité byte-à-byte) vérifie chaque rebuild. Un gate « 44==44==44 » serait
redondant (#5) et masquerait la vraie garantie (le partage de code), pas une
surface à protéger.
Livraison — DOCUMENTATION du garde-fou, pas un gate. Ajout d'un bloc sourcé
« Non-surface documentée — roles_couverts N'EST PAS cross-gaté, à dessein » dans
ci/README.md (entre le bloc mobile Expo et le bloc CONFOTUR) : cite les 3 imports
partagés + le contrat unique + contraste explicite avec le cross-check RBAC-50, pour
qu'une session future ne rajoute PAS le gate redondant. Chemins en backticks (non
link-checkés, cf. mémoire backtick-path-escapes-check-docs).
Vérif — 4 citations de lignes vérifiées exactes (deps.py:77, deps.py:84,
wslib/builder.py:36, rbac_apply_plan.py:149). 7 gates re-joués → exit 0 :
guard_constraints · validate_json · check_docs · check_artifacts · check_regression ·
check_ci_integrity · check_readme_claims. Aucune commande touchant au VPS.