Files
oto-enterprise-os-dtp/05_activity_log/2026-08-01.md
T
Claude Code DTP Worker 18a758045f [DTP-Worker 20260801_193414] Sprint 8 · buffer · Gate d'IDENTITÉ des comptes de tests self-reportés par les README de MODULE
Surface ungated distincte des fiches : chaque `05_deliverables_mvp/<suite>/README.md`
se re-déclare le compte de SA suite (commentaire `# N tests` de la commande unittest ·
bullet « → **N/N verts** » · prose d'auto-score « en-repo (N tests verts + …) ») — 27
claims sur 16 modules. Tous les blocs de comptes de tests existants visent 03_agents/*
(row_re, agrégats prose, per-suite hors table) ; row_re ne lit le README de module QUE
comme cible de lien. Un test ajouté/retiré met à jour regression_plan.json (auth =
plan.suites[path].test_methods, byte-gaté) tandis que la prose du README rote en silence.

Nouveau bloc « READMEs de MODULE · comptes de tests self-reportés » (table config
MODULE_README_TESTS · motifs ancrés) : chaque motif DOIT matcher (absent = régression #6)
ET tous ses groupes == auth ; `**N/N verts**` capture 2 groupes → un 25/24 incohérent mord.
5 modules sans self-report (chat_otoia/mobile/publiciste/acceptance/audit_4big) hors table.

4 morsures vérifiées (drift commentaire · incohérence N/N · disparition de claim · drift
auto-score prose) ; restauré = 440 checks de base intacts + 27 nouveaux ; check_ci_integrity
+ check_docs re-verts. Restauration par backup ciblé (jamais git checkout ./git clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 19:45:11 +00:00

2123 lines
169 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Activity Log · 2026-08-01 · Claude Code DTP
## Session `20260801_183412` · Buffer S8 · Domaine Legal/CONFOTUR : les **deux champs du DocType `CONFOTUR Application` DÉRIVÉS du pipeline vente** — `dossier_vente` (Link → cible `workflow.document_type` = `OTO Dossier Vente`) et `estado` (Select → options = les états `confotur_*` du **workflow vente**), transcrits **en prose** dans la §« Cœur du livrable : cross-cohérence » (`legal/confotur/README.md:33-36`) — étaient **HORS de tout gate d'IDENTITÉ**. Les blocs CONFOTUR amont gatent le **compte** de champs/rôles, la cross-cohérence des **PERMISSIONS** (rôle→actions) et l'ancrage des **ENTITÉS** (`entite_porteuse`→CLAUDE.md §Entités) — mais **aucun** ne touchait `dossier_vente`/`estado`.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). **Même classe EXACTE** que
l'ancrage `entite_porteuse` du MÊME README (session `20260801` entités CONFOTUR) —
appliqué à la **surface distincte** des deux AUTRES champs data-derived du même
DocType, jamais gatés hors comptes/permissions/entités.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/legal/confotur/README.md:35-36` — bullet `dossier_vente`
(Link) : « cible = `workflow.document_type` (= `OTO Dossier Vente`) » (valeur
NOMMÉE en clair) · `:33-34` — bullet `estado` (Select) : « options = les états
`confotur_*` du **workflow vente** … dérivées — jamais réécrites en dur ».
- Source faisant autorité : `crm/workflow_vente/workflow_vente_spec.json` — l'**INPUT**
que le builder confotur LIT (déclaré `workflow_source` dans le MANIFEST), d'où il
tire `document_type` (= cible du Link) et les `update_value` d'états commençant par
`confotur` (= options du Select). L'artefact `out/doctype_confotur_application.json`
porte `dossier_vente.options`/`estado.options`, byte-gaté par `check_artifacts`.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** de
génération, jamais la PROSE du README. RENOMMER le DocType porteur côté workflow
(`OTO Dossier Vente``OTO Dossier de Vente`) ou AJOUTER/RENOMMER un état CONFOTUR
régénère l'artefact confotur (`check_artifacts` reste VERT — il se reconstruit
depuis le spec workflow) MAIS laisse la prose README « (= `OTO Dossier Vente`) »
périmée EN SILENCE → l'agent ONAPI/Legal câblerait une cible de Link / un menu
d'états que le pipeline ne porte plus (l'invention #6 interdit).
- **État courant** : **aucune valeur périmée**`dossier_vente` Link → « OTO
Dossier Vente » == workflow `document_type` · `estado` options
`[confotur_depose, confotur_approuve]` == états `confotur_*` du workflow (ordre
pipeline) · la prose README recoupe l'artefact/le workflow exactement
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Confotur champs
dérivés » avant `sys.exit`) : (0) **traçabilité** — MANIFEST `workflow_source` cite
bien `crm/workflow_vente/workflow_vente_spec.json` ; (a) **cross-artefact**
`dossier_vente.options` (Link) == workflow `document_type` ; (b) **prose** — la
valeur README « (= `…`) » == workflow `document_type` ; (c) **cross-artefact**
`estado.options` == `update_value` des états `confotur_*` du workflow (ORDRE
pipeline) ; (c) toute option `estado` porte le préfixe `confotur` (le README
l'affirme) ; (d) **prose** — le bullet `estado` DÉCLARE la dérivation (`confotur_*`
+ workflow vente). Un claim absent échoue AUSSI (traçabilité #6). Direction
README/artefact→workflow spec.
**6 morsures vérifiées** : README `dossier_vente` valeur `OTO Dossier Vente→OTO
Dossier de Vente` (prose périmée, b) · README bullet `estado` supprimé (claim
INTROUVABLE, d) · **artefact** `estado.options` +`brouillon` (options ≠ workflow, c
ET hors-préfixe, c) · **artefact** `dossier_vente.options→OTO Autre Doc` (Link ≠
document_type, a) · **MANIFEST** `workflow_source` déréférencé (dérivation non
déclarée, 0) · **spec workflow** `document_type→OTO Dossier de Vente` (mord
SIMULTANÉMENT l'artefact a ET la prose b — l'ancre est vive) ; restauré = green :
6 checks verts · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS**
`git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_ci_integrity`/`check_docs`/`guard_constraints`/
`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé
« Champs `dossier_vente`/`estado` du DocType CONFOTUR dérivés du workflow vente »)
mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; l'import réel
du DocType `bench import-fixtures` reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_173409` · Buffer S8 · Fiches agents/pied de page voix OTOIA : le **PIED DE PAGE CANONIQUE** « - Voix **Amélie QC** (**multilingual_v2**) pour toute interaction OTOIA » — répété à l'identique dans **11 des 13 fiches** `03_agents/*/AGENT.md` (toutes sauf `faisabilite`/`publiciste`, qui n'orchestrent pas OTOIA) — RECOPIE À LA MAIN les **trois** tokens de la contrainte **CLAUDE.md §Architecture cible** « Voix Amélie QC (multilingual_v2) » : prénom persona (`Amélie`), qualificatif de marque (`QC`), modèle de voix ElevenLabs (`multilingual_v2`). Cette surface était **HORS de tout gate**.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe
[[claude-md-constant-anchor-gate]]). **Même patron EXACT** que la persona/capabilities
`frontend/chat_otoia` ancrées sur §Architecture cible, la marque SEO org (§Entités) et
les tokens branding (#4) — appliqué à une **surface distincte encore ungated** : les 11
pieds de page de fiches, jamais confrontés à CLAUDE.md.
**Dérive silencieuse fermée** :
- `03_agents/{bim,crm,devops,erpnext_backend,frontend_console,ifc_speckle,mobile,
onapi_legal,qa,rendu,seo}/AGENT.md` — ligne « - Voix Amélie QC (multilingual_v2)
pour toute interaction OTOIA ».
- Source faisant autorité : **CLAUDE.md §Architecture cible** (ligne 30) « Voix
Amélie QC (multilingual_v2) ».
- Piège : le bloc « Chat OTOIA · IDENTITÉ de la persona » RE-DÉRIVE bien persona+voix
de CLAUDE.md mais ne confronte QUE le module `chat_otoia` (spec + `out/` + son propre
README + l'oracle du test) — **AVEUGLE aux 11 pieds de page** ; le bloc « configs
runtime » note même explicitement que le « QC » est « gardé en littéral — non gaté
ici ». Le jour où ElevenLabs déprécie `multilingual_v2` (→`multilingual_v3`) ou qu'on
renomme la persona/le qualificatif dans CLAUDE.md, ces 11 fiches annonceraient EN
SILENCE une voix/persona que le mandat ne prescrit plus, chacune affirmant orchestrer
« toute interaction OTOIA » avec le mauvais modèle. Aucune suite `tests/` (FONCTIONS
du builder, jamais la prose des fiches) n'attrape ce « vert trompeur ».
- **État courant** : **aucune valeur périmée** — les 11 pieds de page recoupent
CLAUDE.md §Architecture cible exactement (anti-invention #6, rien à réécrire). Le
défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches agents · pied de
page voix » après le bloc persona/capabilities Chat OTOIA) : on RE-DÉRIVE le triplet
(persona · qualif · voix) de CLAUDE.md §Architecture cible **INDÉPENDAMMENT** du parse
persona amont (dont la regex fige `QC` en dur — un renommage du qualificatif doit mordre
ICI, pas seulement via l'INTROUVABLE du bloc persona) ; pour CHAQUE fiche git-trackée
portant le pied de page, on exige ses **3 tokens == CLAUDE.md**. Une fiche avec prose
« interaction OTOIA » mais pied de page malformé échoue ; la **disparition totale** du
recensement échoue AUSSI (surface évaporée = régression #6). Direction fiche→CLAUDE.md.
**6 morsures vérifiées** : CLAUDE.md `multilingual_v2→v3` (mord les 11 fiches +, en
cascade, le bloc persona amont — 15 fails, l'ancre est vive) · CLAUDE.md qualif `QC→BC`
(mord les 11 pieds de page — bloc INDÉPENDANT, ne s'appuie pas sur le parse persona qui
fige QC) · fiche `seo` voix `v2→v1` (1 fail) · fiche `crm` persona `Amélie→Sophie`
(1 fail) · fiche `bim` pied de page malformé, parenthèses retirées (1 fail INTROUVABLE) ·
pied de page retiré de TOUTES les fiches (surface évaporée, 1 fail) ; restauré = green :
11 pieds de page == CLAUDE.md · exit 0. Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_ci_integrity`/`check_docs`/`guard_constraints`/
`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé « Pied de
page Voix Amélie QC (multilingual_v2) des fiches agents ancré sur CLAUDE.md
§Architecture cible ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le runtime réel
de la voix OTOIA reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_170404` · Buffer S8 · Domaine QA/audit_4big : la colonne **« Fait vérifié »** de la table du barème (`qa/audit_4big/README.md:24,26`) — les **DEUX SEUILS STRUCTURELS** que le scorer applique réellement (`DOC` « ≥ **400 octets** » · `TESTS` « ≥ **8 méthodes** `def test_*` »), transcrits À LA MAIN — étaient **HORS de tout gate**. Le bloc BARÈME existant recompute la 2e colonne (**poids** : DOC 20 · CONTRAT 20 · TESTS 25 · CLI 15 · HANDOFF 20) mais son regex (`| \`DOC\` | 20 |`) est **AVEUGLE à la 3e cellule** ; ces deux nombres sont DATA-DERIVED de `quality_spec.json[thresholds]` (`min_doc_bytes`/`min_test_methods`).
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même patron EXACT que le
bloc BARÈME (poids) et la carte de renormalisation par archétype du MÊME README —
appliqué à une **3e surface distincte du MÊME README `qa/audit_4big`** : les seuils
de la colonne « Fait vérifié », jamais gatés hors leur poids voisin.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/qa/audit_4big/README.md:24` — cellule DOC « un `*.md`
(README/SPEC) ≥ **400 octets** à la racine » (= `thresholds.min_doc_bytes`) ·
`:26` — cellule TESTS « ≥ **8 méthodes** `def test_*` dans `tests/` » (=
`thresholds.min_test_methods`).
- Source faisant autorité : `05_deliverables_mvp/qa/audit_4big/quality_spec.json`
`[thresholds]` — l'**INPUT** du générateur, byte-gaté indirectement par
`check_artifacts` qui prouve que `out/quality_report.json` se **rebuild** depuis
lui. Les seuils ne sont PAS émis en clair dans le report ; seule l'**evidence
`TESTS`** de chaque module porte « (seuil N) » (émise par `crit_tests`), ce qui en
fait un **ancrage croisé** du seuil réellement APPLIQUÉ.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** de
scoring (`crit_doc`/`crit_tests` avec seuil paramétré), jamais la PROSE du README.
Élever `min_test_methods` (8→12 : un module de 8-11 tests tomberait sous `FAIL` —
la barre même que le critère pose) ou changer `min_doc_bytes` dans le spec rebuild
le report SANS TOUCHER au README → prose « ≥ 8 méthodes »/« ≥ 400 octets » périmée
en silence, l'auditeur documentant un barème qu'il **n'applique plus** (l'invention
#6 interdit).
- **État courant** : **aucun seuil périmé** — les 2 cellules recoupent le spec
exactement, et le seuil TESTS appliqué (evidence report) == spec sur les 22 modules
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « README audit_4big ·
SEUILS STRUCTURELS » après le bloc renormalisation par archétype) : (1) **cross-check
d'ancrage** — le seuil `TESTS` APPLIQUÉ (regex `(seuil N)` sur l'evidence de CHAQUE
module du report) == `spec.min_test_methods` pour tous (mord une divergence
spec↔report) ; (2) **prose DOC** — cellule « ≥ N octets » recomputée du spec, exacte ;
(3) **prose TESTS** — cellule « ≥ N méthodes » recomputée du spec, exacte. Un claim
absent échoue AUSSI (traçabilité #6).
**5 morsures vérifiées** : README `DOC 400→450` (prose périmée) · README `TESTS 8→10`
(prose périmée) · README ligne DOC vidée du seuil (claim INTROUVABLE) · **spec**
`min_test_methods 8→12` + rebuild (README périmé ET cross-check evidence[8]≠spec[12]
spec↔report) · **spec** `min_doc_bytes 400→500` + rebuild (README DOC périmé) ;
restauré = green : seuil TESTS appliqué == spec (8) · DOC « ≥ 400 octets » == spec ·
TESTS « ≥ 8 méthodes » == spec · exit 0. Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_ci_integrity`/`check_docs`/`guard_constraints`/
`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (ligne récap du pipeline `check-readme-claims` + paragraphe détaillé
« troisième surface · colonne Fait vérifié / seuils structurels ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le branchement
du gate 4Big sur le pipeline de release reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_000201` · Buffer S8 · Domaine RBAC/userperm_gen : la **table « Mapping `scope_donnees` → mécanisme »** — la **FONCTION d'enforcement ROW-LEVEL** (quel mécanisme Frappe natif applique CHAQUE portée de données + si un `User Permission` template est émis), CŒUR sécurité du module — était transcrite EN PROSE (README:33-38) SANS AUCUN gate d'IDENTITÉ. Le bloc RBAC « 3 volets » existant ne gate QUE la **ventilation** par mécanisme (« 28 `entite` · 16 `groupe` · 2 `own` · 4 `equipe` »), un COMPTE aveugle à QUEL mécanisme applique QUELLE portée.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même patron d'IDENTITÉ que
le mapping RBAC `fixtures_gen` (séparation des pouvoirs `set_user_permissions`) ou la
cross-cohérence PERMISSIONS Legal/CONFOTUR (`role_id`→actions) — appliqué à la
**surface data-derived distincte du même README `rbac/userperm_gen`** : la table
qui associe chaque portée de données à son mécanisme d'enforcement, jamais gatée hors
son compte agrégé.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/userperm_gen/README.md:33-38` — table « Mapping
`scope_donnees` → mécanisme » : nomme, PAR portée, le `mechanism` Frappe natif
(`own`→`docperm_if_owner` · `entite`→`user_permission_company` ·
`groupe`→`none_consolidated` · `equipe`→`vps_confirm_team`) ET le verdict
« Template émis ? » (`oui` pour `entite` seul, sinon `non (null)`).
- Source faisant autorité (byte-gatée par `check_artifacts`) :
`out/user_permission_plan.json` — chaque entrée porte `scope_donnees` +
`mechanism` + `user_permission_template` (null ou objet). Le mapping est une
**fonction** : chaque portée → exactement un mécanisme ; template émis SSI
`entite`/`user_permission_company`.
- Piège : le bloc de ventilation est AVEUGLE à l'identité de ces couples.
RÉAFFECTER une portée à un mécanisme plus permissif (`entite` →
`none_consolidated` : le **row-level enforcement ABANDONNÉ**, sur-exposition des
données inter-entités — la restriction même que le module pose), RENOMMER un
mécanisme ou BASCULER le verdict « Template émis ? » laisse le README périmé
pendant que l'artefact dit autre chose → l'agent ERPNext Backend câblerait le
mauvais mécanisme (le risque même que la table veut prévenir). Aucune suite
`tests/` (qui teste des FONCTIONS de mapping/résolution, pas la prose) n'attrape
ce « vert trompeur ».
- **État courant** : **aucune ligne périmée** — les 4 couples portée→mécanisme et
leurs verdicts recoupent l'artefact exactement (anti-invention #6, rien à
réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « RBAC/userperm_gen
Mapping » après le bloc CRM gardes) : (1) **par portée** — le `mechanism` (1er token
backtické de la colonne) ET le verdict « Template émis ? » (`**oui**` / `non (null)`)
recomputés de `user_permission_plan.json`, prose exigée EXACTE ; (2) **identité
d'ensemble** — portées de la table == portées de l'artefact (set-diff : ni fantôme
ni manquante). Cohérences croisées en bonus (mordent un plan INTERNEMENT incohérent) :
chaque portée mappe UN SEUL mécanisme (fonction, pas relation) · « Template émis »
UNIFORME sur les entrées d'une portée · template émis EXACTEMENT pour
`user_permission_company` (l'invariant « template SSI `entite` » du README:36/61) ·
ensemble des portées NON VIDE. Un claim absent échoue AUSSI (traçabilité).
**7 morsures vérifiées** : README réaffecte `entite`→`none_consolidated`
(mécanisme périmé · élévation de portée) · README bascule `entite` « Template
émis » oui→non (verdict périmé) · README échange une portée `groupe`→`famille`
(absents=[groupe] + en trop=[famille] + ligne INTROUVABLE) · README retire la
ligne `own` (absents=[own]) · table entière supprimée (les 4 absents + toutes
lignes INTROUVABLES) · **artefact** réaffecte toutes les entrées `entite`→
`none_consolidated` (README périmé sur mécanisme ET verdict) · **artefact** rend
une entrée `own` porteuse d'un template (« Template émis » NON uniforme
[False,True] · invariant SSI cassé) ; restauré = green : 4 portées · table ==
artefact · par-ligne exact · fonction/uniforme/SSI verts · exit 0. Working tree
byte-restauré (`git checkout --`, **JAMAIS** `git clean`) · **7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « 2ᵉ surface
`rbac/userperm_gen` ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
- **Auto-score 4Big** : 96/100.
## Session `20260801_003201` · Buffer S8 · Domaine Faisabilité/bancable : les **4 figures « Génération réelle (fixture) »** du README (`40 unités · valeur catalogue USD 8,560,000 / DOP 505,040,000 · point d'équilibre 21 unités (⌈52 % × 40⌉)`) — les chiffres DATA-DERIVED que le **dossier bancable** (l'artefact que voit le banquier) recopie — étaient **HORS de tout gate**. Le module n'écrit son `out/` que sur disque (« out/ non commité ») ⇒ **check_artifacts est aveugle** (aucun artefact byte-gaté) et **AUCUN** gate ne le touchait.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même esprit que les
paramètres canoniques `faisabilite/generator` **ancrés sur CLAUDE.md #9/#10** (le
`52 %` réutilisé ici) — appliqué à une **surface encore jamais gatée** : un module
dont la source de vérité n'est PAS un `out/*.json` mais **la fixture commitée + le
générateur**.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/faisabilite/bancable/README.md:103-105` — ligne
« Génération réelle (fixture) » : `40 unités` (= Σ quantités typologies) ·
`USD 8,560,000` (= Σ qté×prix_usd) · `DOP 505,040,000` (= Σ qté×prix_dop) ·
`21 unités (⌈52 % × 40⌉)` (= ⌈point_equilibre_pct × Σ unités⌉).
- Source faisant autorité : `fixtures/brief_bancable.json` (COMMITÉE) + la
**génération réelle** `python3 bancable_gen.py validate` (émet le manifeste sur
stdout, `figures_calculees[]`). Le `52 %` est **ancré sur CLAUDE.md #9**
(« Point équilibre 52% pré-vente »), pas recopié.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS**
(l'arithmétique de recoupement `_check_derived_arithmetic`) avec des **oracles
HARDCODÉS** dans le test — une copie de plus, jamais comparée à la PROSE du
README. Éditer une typologie de la fixture (quantité/prix), en ajouter/retirer
une, ou déplacer le point d'équilibre de CLAUDE.md #9, laisse les 4 chiffres du
README périmés pendant que le générateur produit autre chose → le banquier lit
un dossier faux (l'invention même que #6 interdit).
- **État courant** : **aucun chiffre périmé** — les 4 figures recoupent la
génération réelle exactement (anti-invention #6, rien à réécrire). Le défaut est
la **surface ungated** (module entièrement hors CI).
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Faisabilité/bancable
figures » avant `sys.exit`) : (1) **recompute indépendant** des 4 figures depuis
la fixture commitée ; (2) **cohérence croisée** — le manifeste GÉNÉRÉ
(`bancable_gen validate`) == le recompute (mord un générateur/fixture
incohérents) ; (3) **prose** — un seul pattern d'identité exige les 4 valeurs
EXACTES, **avec séparateur de milliers** pour USD/DOP, + la formule `⌈pct × Σ
unités⌉` dont le pct == CLAUDE.md #9 et le total == Σ unités. Un claim absent
échoue AUSSI (traçabilité). Le `out/` n'étant pas commité, le gate **exécute le
générateur** (stdlib pur, `subprocess`, `encoding="utf-8"`) au lieu de lire un
artefact byte-gaté — même patron que le bloc `faisabilite/generator` qui lit
`model.py`.
**7 morsures vérifiées** : README `USD 8,560,000→9,560,000` (figure périmée) ·
README retire le séparateur de milliers (`8,560,000→8560000`) · README breakeven
`21→20` (formule périmée) · README formule `52 %→55 %` (dérive CLAUDE.md #9) ·
README supprime la ligne entière (INTROUVABLE · claim absent) · **fixture** Studio
`qté 12→13` (5 morsures : units/USD/DOP/breakeven README périmés + manifeste) ·
**fixture** 2 Chambres `prix_usd 320000→300000` (catalogue USD périmé) ; restauré =
green : 4 figures == génération réelle · manifeste == recompute · pct 52 % ==
CLAUDE.md #9 · exit 0. Working tree byte-restauré via `git checkout --` (JAMAIS
`git clean`, interdit absolu) · **7 gates re-verts**.
- `ci/README.md` (ligne récap du pipeline `check-readme-claims`) mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; conversion PDF + rebuild
Portail Bancables restent côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_010204` · Buffer S8 · Domaine Mobile/fiche agent : la **fiche d'identité du rôle RBAC `plateforme-mobile`** (`03_agents/mobile/AGENT.md:36`) — le **SEUL ancrage in-repo réellement vérifiable** de l'agent mobile (builds/soumissions stores étant hors-repo, #8) — recopiait EN PROSE, DEPUIS le contrat `rbac_50_roles.json`, TOUS les attributs data-derived du rôle (`erpnext_role_name` · `nom_fr` · `famille`/`portail` · `entite_principale` · `niveau` · `scope_donnees` · `modules` · permission `API Access` custom R/W · `description`) SANS AUCUN gate d'IDENTITÉ. Le README `mobile/app_config` n'est gaté que sur des COMPTES agrégés (« 5 onglets · 44 rôles couverts · 3 langues · 13 identifiants »), AVEUGLES à l'identité de CE rôle.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même patron d'IDENTITÉ que
la table gardes CRM/`workflow_vente` (rôle qui garde chaque transition) ou le mapping
RBAC `userperm_gen` (portée→mécanisme) — appliqué à la **surface distincte** de la
fiche agent mobile : la carte que l'agent ERPNext Backend lit pour **seeder le rôle**
(`seed_mobile_rbac.py`), jamais gatée hors les comptes agrégés de `app_config`.
**Dérive silencieuse fermée** :
- `03_agents/mobile/AGENT.md:36` — ligne « Points de contact réellement commités »,
cellule « Ce qu'il fixe » : nomme, PAR attribut, le rôle ERPNext
**`OTO Plateforme Mobile`** (« Développeur Mobile » · famille/portail
**`plateforme`** · entité **9060 QC** · niveau 2 · scope `groupe` · modules
`Core`+`Website` · perm `API Access` custom R/W · description « Maintient
l'app Expo/React Native et l'API mobile ; gère les builds EAS et les
soumissions stores »).
- Source faisant autorité : `05_deliverables_mvp/rbac/rbac_50_roles.json` (contrat
RBAC, schéma-validé · les 3 MANIFEST RBAC recomputés en dérivent et sont byte-gatés
par `check_artifacts`) → le rôle d'`id` `plateforme-mobile` porte chacun de ces
champs. Autres blocs du gate lisent déjà ce contrat directement (userperm,
workflow_vente, confotur).
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** de
résolution RBAC, jamais la PROSE d'une fiche agent. Éditer le contrat — élever le
`scope_donnees` (`groupe`→`entite` : sur-portée inter-entités, la restriction même
que le rôle pose), changer le `niveau`/l'`entite_principale`, renommer le rôle,
ajouter/retirer un module ou élargir la permission (`R/W`→`R/W/D`) — laisse la fiche
périmée pendant que le contrat dit autre chose → l'agent ERPNext Backend câblerait
le mauvais rôle (le risque même que la fiche veut prévenir).
- **État courant** : **aucun attribut périmé** — les 9 attributs + la description
recoupent le contrat exactement (anti-invention #6, rien à réécrire). Le défaut est
la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiche Mobile » avant
`sys.exit`) : (1) **par attribut** — `erpnext_role_name` · `nom_fr` · `portail` ·
`entite_principale` · `niveau` · `scope_donnees` · `modules` · permission recomputés
du contrat, prose EXACTE exigée (regex par attribut, lookaheads anti-préfixe pour
niveau/modules/perm) ; (2) **permission** — le sigle des verbes est **canonique et
ordonné** (C/R/W/D/S/X/A) : élargir OU rétrécir la perm change le sigle attendu (un
préfixe `R/W` ne satisfait plus `R/W/D`, ni l'inverse) ; verbe inconnu ⇒ ROUGE
(étendre le gate) ; (3) **description** recoupée **verbatim** (emphase markdown `**` et
point final neutralisés). Cohérences croisées bonus (mordent un contrat INTERNEMENT
incohérent) : `famille == portail` (bijection portail) · permission bien `custom` ·
`role_id` présent EXACTEMENT une fois. Un claim absent échoue AUSSI (traçabilité :
ligne de rôle INTROUVABLE).
**12 morsures vérifiées** (6 côté contrat = fiche périmée · 6 côté fiche = fiche
fausse) : contrat `scope groupe→entite` (sur-portée) · contrat `niveau 2→3` · contrat
`+delete` (sigle `R/W→R/W/D`) · contrat description réécrite · contrat rôle renommé
(`OTO Plateforme Mobile→OTO Mobile Dev`) · fiche `modules Website→Selling` · fiche
`entité 9060 QC→WAF` · fiche perm `R/W→R/W/D` (sur-revendication) · fiche perm `R/W→R`
(sous-revendication) · fiche modules over-claim `+Selling` et under-claim `Core` seul ·
ligne de rôle entièrement supprimée (INTROUVABLE) ; restauré = green : 9 attributs +
description == contrat · famille==portail · perm custom · role_id singleton · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
absolu) · **7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « Fiche d'identité
du rôle RBAC mobile ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel
du rôle `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_013208` · Buffer S8 · Domaine Faisabilité/fiches agents : les **deux FICHES D'IDENTITÉ** des rôles RBAC `faisabilite-rendu-3d` (`03_agents/rendu/AGENT.md:34`) et `faisabilite-ifc-speckle` (`03_agents/ifc_speckle/AGENT.md:34`) — la carte que l'agent ERPNext Backend lit pour **seeder** le rôle porteur des rendus / de l'export IFC→GLB — recopiaient EN PROSE, DEPUIS le contrat `rbac_50_roles.json`, les attributs data-derived du rôle (`erpnext_role_name` · `nom_fr`/`modules` côté rendu · `portail` · `entite_principale` · `niveau` · les **deux** `permissions_cibles` `File` R/W/create + `Faisabilité` R ou R/W) SANS AUCUN gate d'IDENTITÉ. Le README `frontend/portails` n'est gaté que sur des COMPTES de rôles par Workspace, AVEUGLES à l'identité de CE rôle.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). MÊME classe que la Fiche
Mobile de la session précédente (`plateforme-mobile`) — surface `03_agents/*/AGENT.md`
qui transcrit à la main les attributs d'un rôle depuis `rbac_50_roles.json`, gatée (si
tant est) seulement sur des comptes agrégés — appliquée aux **deux fiches faisabilité
construction** encore ungated. Mémoire `agent-fiche-role-attrs-ungated` mise à jour.
**Dérive silencieuse fermée** :
- `03_agents/rendu/AGENT.md:34` — cellule « Ce qu'il fixe » du rôle
`faisabilite-rendu-3d` : `OTO Faisabilité Rendu 3D` · « Spécialiste Rendu 3D » ·
portail `construction` · `WA SRL` · niveau 2 · module `OTOV7 Faisabilité` ·
perms `File` R/W/create + `Faisabilité` R.
- `03_agents/ifc_speckle/AGENT.md:34` — cellule du rôle `faisabilite-ifc-speckle` :
`OTO Faisabilité IFC Speckle` · portail `construction` · `WA SRL` · niveau 2 ·
perms `File` R/W/create + `Faisabilité` **R/W** (écriture en plus vs rendu).
- Source faisant autorité : `05_deliverables_mvp/rbac/rbac_50_roles.json` (contrat
RBAC schéma-validé · les 3 MANIFEST RBAC en dérivent, byte-gatés par
`check_artifacts`). Plusieurs blocs du gate lisent déjà ce contrat directement.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** de
résolution RBAC, jamais la PROSE d'une fiche. Élargir `Faisabilité` R→R/W (le rôle
Rendu 3D gagnerait l'écriture sur un DocType qu'il ne doit que LIRE), changer
`niveau`/`entite_principale`, renommer le rôle ou permuter un module laisse la fiche
périmée pendant que le contrat dit autre chose → l'agent câblerait le mauvais rôle.
- **Constat #6 (honnêteté)** : la **DESCRIPTION** de ces deux fiches est une
**PARAPHRASE éditoriale** (le contrat porte « (… — CLAUDE.md) » / « les modèles »
que la fiche condense) — vérifié programmatiquement AVANT toute édition. Elle n'est
donc **PAS** gatée verbatim (ne rien réécrire de correct pour satisfaire un gate) ;
seuls les attributs **structurés**, eux transcrits à l'exact, sont contraints.
- **État courant** : aucun attribut structuré périmé — les 12 recomputes recoupent
le contrat exactement (anti-invention #6, rien à réécrire). Le défaut est la
**surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches Faisabilité »
avant `sys.exit`) : table de config par fiche (fichier · role_id · liste des attributs
RESTITUÉS). Pour chaque fiche : (1) **par attribut** — `erpnext_role_name` · `nom_fr` ·
`portail` · `entite_principale` · `niveau` · `modules` · les deux permissions
recomputés du contrat, prose EXACTE exigée (regex par attribut, lookaheads
anti-préfixe niveau/module/perms) ; (2) **permissions** — chaîne
« \`Doctype\` R/W/create + \`Autre\` R » rendue en **ordre canonique** (read→R ·
write→W · create/delete/submit/… en toutes lettres) : élargir OU rétrécir change la
chaîne attendue (`R/W/create` ne satisfait plus `R/W/create/delete`) ; verbe inconnu ⇒
ROUGE. Un claim absent échoue AUSSI (ligne de contact INTROUVABLE = régression #6).
**11 morsures vérifiées** (5 côté contrat = fiche périmée · 6 côté fiche = fiche
fausse) : contrat rendu `Faisabilité +write` (sigle `R→R/W`) · contrat rendu
`niveau 2→3` · contrat rendu `WA SRL→WAF` · contrat rendu rôle renommé · contrat ifc
`Faisabilité R/W→R` · fiche rendu perm `File R/W/create→…/delete` (over-claim) · fiche
ifc perm `R/W→R/W/create` (over-claim) · fiche rendu module `Faisabilité→Selling` ·
fiche ifc `WA SRL→WAF` · fiche rendu `nom_fr` retiré (claim absent) · ligne de contact
ifc supprimée (INTROUVABLE) ; restauré = green : 12 attributs == contrat · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
absolu) · **7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « deux fiches
d'identité des rôles RBAC faisabilité ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel
des rôles `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_020209` · Buffer S8 · Domaine Fiches agents/rattachement Workspace : les **assertions d'appartenance `Has Role`** des trois fiches `03_agents/{rendu,ifc_speckle,mobile}/AGENT.md` — QUELLE CONSOLE (Workspace ERPNext natif) le rôle peut atteindre — étaient une surface data-derived **DISTINCTE du contrat** `rbac_50_roles.json` (la membership vit dans `frontend/portails/out/workspace.json`, byte-gaté) et **HORS de tout gate**. Les blocs Fiches Faisabilité / Fiche Mobile des sessions précédentes ne gataient que les attributs du **contrat** ; le bloc « triplets par workspace » ne gate que le **compte** de rôles par Workspace (`nb_roles`), AVEUGLE à QUEL rôle.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). MÊME famille de fiches que
les deux sessions précédentes (`agent-fiche-role-attrs-ungated`) mais **surface
orthogonale** : non plus les attributs recopiés du contrat, mais la **membership
`Has Role`** — l'accès console — qu'aucun gate ne touchait.
**Dérive silencieuse fermée** :
- `03_agents/rendu/AGENT.md:35` — « rattaché au Workspace `OTO Construction` »
(assertion NOMMÉE) ; `03_agents/ifc_speckle/AGENT.md:35` — « rattaché à un
Workspace » (générique) ; `03_agents/mobile/AGENT.md:41` — « **volontairement pas**
rattaché au Workspace `OTO Ventes` » (assertion NÉGATIVE d'honnêteté #6).
- Source faisant autorité : `frontend/portails/out/workspace.json` (les 5 Workspaces
natifs v15, chacun portant sa liste `Has Role`, byte-gaté par `check_artifacts` via
la reproductibilité `out/`). L'`erpnext_role_name` de chaque rôle est résolu du
contrat `rbac_50_roles.json` par `id` (zéro duplication).
- Piège : rattacher le rôle mobile à `OTO Ventes` = **sur-exposition console** (le
dev mobile gagnerait l'accès au portail Ventes — la restriction même que la note
#6 pose), retirer/**déplacer** le rôle rendu de `OTO Construction` = perte/erreur
d'accès console — laisse la fiche périmée EN SILENCE pendant que l'artefact dit
autre chose ⇒ l'agent ERPNext Backend câblerait le mauvais accès console. Aucune
suite `tests/` (qui teste des FONCTIONS de résolution, pas la prose) n'attrape ce
« vert trompeur ».
- **État courant** : aucune assertion périmée — rendu ∈ {`OTO Construction`} seul ·
ifc ∈ {`OTO Construction`} (≥1) · mobile ∈ ∅ (donc absent de `OTO Ventes`) ; les 3
recoupent workspace.json exactement (anti-invention #6, rien à réécrire). Le défaut
est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches agents ·
rattachement Workspace (`Has Role`) » avant `sys.exit`) : index recomputé
`erpnext_role_name → {titres de Workspace le portant}` depuis workspace.json. Par
fiche, 3 formes d'assertion : **in_named** (rendu) — la prose cite CE Workspace ET la
membership == {ce Workspace} seul (membership élargie ou déplacée ⇒ ROUGE) ;
**in_any** (ifc) — membership ≥ 1 Workspace ; **not_in_named** (mobile) — rôle ABSENT
du `Has Role` du Workspace nommé. Garde anti-typo : un Workspace nommé absent de
l'artefact échoue avant tout (anti-cible fantôme). Un claim absent échoue AUSSI
(traçabilité #6).
**9 morsures vérifiées** (4 côté artefact · 5 côté fiche/bord) : artefact ajoute le
rôle mobile à `OTO Ventes` (**sur-exposition #6 VIOLÉE**) · artefact retire le rôle
rendu de `OTO Construction` · artefact déplace rendu vers `OTO Achat` · artefact
retire ifc de tous les Workspaces · fiche rendu renomme le Workspace
(`OTO Construction→OTO Ventes`, INTROUVABLE) · fiche rendu supprime l'assertion ·
fiche mobile nomme un Workspace fantôme (`OTO Marketing`, anti-typo) · fiche ifc
supprime l'assertion générique · fiche mobile passe le négatif au positif (retire
« volontairement pas ») ; restauré = green : rendu=={`OTO Construction`} · ifc≥1 ·
mobile∉`OTO Ventes` · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS**
`git clean`, interdit absolu) · **7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « rattachement
Workspace des fiches agents ») mis à jour. Mémoire `agent-fiche-role-attrs-ungated`
à actualiser (surface membership couverte).
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; l'attachement
réel des rôles aux Workspaces `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_023211` · Buffer S8 · Domaine Publiciste/branding : les **4 TOKENS DESIGN CANONIQUES** de la marque luxury — fond dark `#0a0a12` + accent doré `#f0b429`, titres **Fraunces** + corps **Cormorant Garamond** (contrainte NON-NÉGOCIABLE **CLAUDE.md #4**) — étaient RECOPIÉS à **QUATRE** endroits **tous NON gatés** : (a) les CONSTANTES `lib/branding.py` (`COLOR_BG`/`COLOR_ACCENT`/`FONT_DISPLAY`/`FONT_BODY`), la source unique importée par le générateur & substituée dans le gabarit (`{{COLOR_BG}}…`), hors `out/` donc **invisible à check_artifacts** ; (b) le **docstring** du même fichier ; (c) le README (ligne du gabarit) ; (d) le **tuple d'oracle HARDCODÉ** de `tests/test_publiciste.py`.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). **Même patron EXACT** que
les paramètres canoniques `faisabilite/generator` **ancrés sur CLAUDE.md #9/#10**
(`CANONICAL` ↔ contrainte) — appliqué à la **contrainte #4** (identité visuelle) et
à son consommateur `publiciste/branding`, module **entièrement hors CI** jusqu'ici
(grep `branding|0a0a12|f0b429|Fraunces` dans `ci/` = 0 avant cette session).
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/publiciste/lib/branding.py:13-20` — les 4 CONSTANTES ·
`05_deliverables_mvp/publiciste/lib/branding.py:3-4` — le docstring qui les cite ·
`05_deliverables_mvp/publiciste/README.md:43` — « Gabarit dark+doré (Fraunces +
Cormorant Garamond) » · `05_deliverables_mvp/publiciste/tests/test_publiciste.py:193`
`for token in ("#0a0a12", "#f0b429", "Fraunces", "Cormorant Garamond")`.
- Source faisant autorité : **CLAUDE.md #4** (ligne 18) — la contrainte
constitutionnelle, comme #9/#10 le sont pour le générateur de faisabilité.
- Piège : le SEUL contrôle existant (`test_marque_luxury`) assert que le RENDU
HTML CONTIENT ces 4 marqueurs — mais ils sont **HARDCODÉS dans le test**, une
copie de plus **jamais comparée à CLAUDE.md**. Si #4 change (accent
`#f0b429``#c9a227`) et qu'on aligne `branding.py`, l'oracle du test reste périmé
(rouge trompeur) ; si RIEN ne bouge, tout reste vert alors que le site public
`vente.otov7.com` émettrait la mauvaise couleur — invention silencieuse de la
classe même que #6 interdit, qu'aucune suite `tests/` (qui teste des FONCTIONS,
pas l'ancrage à CLAUDE.md) n'attrape.
- **État courant** : **aucun token périmé** — les 4 constantes + docstring + README
+ oracle du test recoupent CLAUDE.md #4 exactement (anti-invention #6, rien à
réécrire). Le défaut est la **surface ungated** (module hors CI).
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Publiciste/branding »
avant `sys.exit`) : (0) **ancrage** — les 4 tokens recomputés de la ligne #4 de
CLAUDE.md (2 couleurs backtickées + 2 fontes de la parenthèse `(… + …)`) ; (a)
`branding.py` CONSTANTES == #4 (hex **insensible à la casse**, fontes à l'exact
strip) ; (b) **docstring** (texte AVANT les constantes) cite les 4 EXACTS ; (c)
README cite les 2 fontes ; (d) **oracle du test** — le tuple `for token in (...)`
== les 4 tokens de #4 (set-diff : absent ET en trop). Un claim absent échoue AUSSI
(traçabilité #6).
**6 morsures vérifiées** : `branding.py` accent `#f0b429→#e0a420` (constante
périmée) · `branding.py` display `Fraunces→Playfair Display` · **CLAUDE.md #4**
accent `→#c9a227` (mord **SIMULTANÉMENT** les 3 copies aval : constante + docstring
+ oracle du test — prouve la valeur de l'ANCRAGE) · docstring retire l'accent de la
prose (claim absent) · README échange une fonte (`Cormorant Garamond→Georgia`) ·
oracle du test retire `Fraunces` ; restauré = green : 4 tokens sains · constantes ==
#4 · docstring 4 tokens · README 2 fontes · oracle == #4 · exit 0. Working tree
byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) ·
**7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « Tokens design
canoniques `publiciste/branding` ancrés sur CLAUDE.md #4 ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le déploiement
réel du site `vente.otov7.com` reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_030214` · Buffer S8 · Domaine Fiscal/e-CF DGII : l'**IDENTITÉ du RÔLE émetteur** e-CF — le SEUL rôle habilité à ÉMETTRE un comprobante fiscal électronique — nommé **en prose** par le bullet `role_id` du README (`fiscal/ecf_dgii/README.md:51-54`) via TROIS attributs data-derived (`role_id` `compta-fiscaliste-ecf` · `erpnext_role_name` `OTO Compta Fiscaliste eCF` · `portail` `compta`) était **HORS de tout gate d'IDENTITÉ**. Le bloc **e-NCF** gate le FORMAT de l'identifiant ; le bloc **cross-cohérence** (2e surface, session précédente) gate états émetteurs/champs de base/FormaPago ; mais AUCUN ne comparait l'IDENTITÉ de ce rôle à ses deux sources faisant autorité.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). **Même classe d'IDENTITÉ**
que la Fiche Mobile / les Fiches Faisabilité (`agent-fiche-role-attrs-ungated`) ou les
gardes CRM/`workflow_vente` (rôle→transition) — appliquée à une **3e surface distincte
du MÊME README `fiscal/ecf_dgii`** : le rôle émetteur nommé dans la section
« Cross-cohérence », jamais gaté hors le FORMAT e-NCF et les ensembles états/champs.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/fiscal/ecf_dgii/README.md:53-54` — bullet `role_id` :
« **et** appartenir au portail `compta` — le rôle dédié `compta-fiscaliste-ecf`
(_OTO Compta Fiscaliste eCF_) ».
- Sources faisant autorité (double) : (a) `out/ecf_plan.json` (byte-gaté par
`check_artifacts`) — CHAQUE `emission_events[]` porte `role_id` +
`erpnext_role_name` ; (b) `rbac/rbac_50_roles.json` (contrat schéma-validé ·
les 3 MANIFEST RBAC en dérivent, byte-gatés) — le rôle d'`id`
`compta-fiscaliste-ecf` porte `portail: "compta"`. Le `portail` n'est PAS dans
l'artefact ; il n'est vérifiable QUE contre le contrat.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS**
(`ncf`/résolution RBAC), jamais la PROSE. RÉAFFECTER l'émission à un autre rôle
(dans le plan), le RENOMMER, ou DÉPLACER `compta-fiscaliste-ecf` du portail
`compta` vers un autre portail (dans le contrat : l'e-CF serait émis **HORS
Compta** — la séparation même que la cross-cohérence pose) laisse la prose périmée
EN SILENCE pendant que l'artefact/le contrat disent autre chose → l'agent ERPNext
Backend câblerait le mauvais rôle émetteur (le risque même que #6 interdit).
- **État courant** : **aucun attribut périmé** — les 3 recoupent artefact+contrat
exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface
ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiscal rôle émetteur »
après le bloc FormaPago) : (1) **par attribut**`role_id`/`erpnext_role_name`
recomputés de `emission_events` + `portail`/nom Frappe recomputés du contrat, prose
EXACTE exigée (regex par claim) ; (2) **cohérences croisées bonus** (mordent un
plan/contrat INTERNEMENT incohérent) : tous les `emission_events` pointent UN SEUL
rôle (fonction · non vide) · ce `role_id` existe EXACTEMENT une fois au contrat · le
nom résolu dans l'artefact == le contrat (zéro-duplication) · `portail == compta`
(séparation des pouvoirs). Un claim absent échoue AUSSI (traçabilité #6).
**6 morsures vérifiées** : README renomme le `role_id`
(`compta-fiscaliste-ecf→…-xxx`, prose périmée) · README renomme le nom Frappe · README
déplace le portail (`compta→ventes`) · **artefact** réaffecte les DEUX
`emission_events` à `ventes-directeur` (5 mords en cascade : nom≠contrat + portail
HORS Compta + les 3 claims de prose) · **contrat** déplace le rôle vers `ventes`
(portail HORS Compta + prose portail périmée) · **artefact** diverge du contrat sur
le nom (`OTO Compta X`, zéro-dup cassé) ; restauré = green : 3 claims == artefact/
contrat · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS**
`git clean`, interdit absolu) · **7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « identité du RÔLE
émetteur e-CF, 3e surface du MÊME README `fiscal/ecf_dgii` ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel
du rôle + le câblage Compupar restent côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_033217` · Buffer S8 · Domaine Fiches agents/colonne « Job CI » : chaque table de livrables de `03_agents/*/AGENT.md` porte une colonne **Job CI** qui NOMME entre backticks le job Gitea Actions exécutant la suite du module (`rbac-tests`, `fiscal-ecf-tests`, `frontend-portails-tests`…). Ce nom est **saisi à la main** ; la source de vérité est la clé `jobs:` de `.gitea/workflows/ci.yml`. Ce lien n'était couvert par **AUCUN gate** : `check_ci_integrity.sh` prouve que chaque job est CÂBLÉ dans `gate.needs` (INV-A/B) et les blocs amont de `check_readme_claims` recomputent les **comptes** de tests par suite — mais RIEN ne vérifiait que le nom ÉCRIT dans la fiche DÉSIGNE un job réel.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même classe de « vert
trompeur » que les cellules « Tests » par suite ou la colonne « Type » des Role
Profile — appliqué à la **surface data-derived distincte** : le NOM de job cité par
la fiche, jamais confronté à la définition réelle du workflow CI.
**Dérive silencieuse fermée** :
- `03_agents/*/AGENT.md` — colonne « Job CI » : 33 tokens `*-tests` répartis sur
13 fiches, chacun un nom de job Gitea Actions recopié à la main.
- Source faisant autorité : la section `jobs:` de `.gitea/workflows/ci.yml`.
- Piège : renommer un job dans ci.yml (`rbac-tests``rbac-role-tests`) ou mal
recopier `demo-scenario-tests` en `demo-scenarios-tests` laisse la fiche pointer
un job **fantôme** — le CI reste **vert** (le vrai job tourne sous son nouveau
nom), la doc d'identité de l'agent ment en silence. `check_ci_integrity` ne
regarde QUE le câblage `gate.needs`, aveugle au NOM cité par la prose.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Fiches agents · colonne
Job CI ») : on RECOMPUTE l'ensemble des jobs depuis la clé `jobs:` de ci.yml
(la section `on:` — push/pull_request/workflow_dispatch — est AVANT `jobs:` et donc
naturellement exclue ; jamais une liste à la main) et on exige que chaque token
`*-tests` cité dans une fiche y figure. Direction **fiche→ci.yml** (le
consommateur) : ci.yml peut définir des jobs non cités (légitime), mais une fiche
ne peut pas citer un job absent. La disparition totale de la colonne échoue AUSSI
(le recensement qui s'évapore est lui-même une régression).
**Morsures vérifiées (2)** : (1) token de fiche `seo-tests``seo-suite-tests`
(job fantôme, mord les 2 citations dans `seo/AGENT.md`) ; (2) job ci.yml
`rbac-tests` renommé `rbac-role-tests` → mord **SIMULTANÉMENT les 4 fiches** qui
le citent (`erpnext_backend`/`ifc_speckle`/`mobile`/`rendu`). État courant :
**33 références == jobs ci.yml**, exit 0. Working tree byte-restauré
(`cp` depuis backup + `git checkout` implicite — **JAMAIS** `git clean`, interdit
absolu). Sibling gates re-verts : `check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_artifacts` = PASS.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « Colonne Job CI
des fiches agents == section `jobs:` de ci.yml ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; aucun
déclenchement de workflow CI réel — analyse statique du YAML uniquement).
- **Auto-score 4Big** : 96/100.
## Session `20260801_040219` · Buffer S8 · Domaine CRM/DocType porteur : le **NOM du DocType porteur** `OTO Dossier Vente` — le **point d'attache** du pipeline vente (le Workflow s'y branche via son `document_type`) — était RECOPIÉ À LA MAIN à plusieurs endroits (titre + ligne « Nom du DocType » du README `crm/dossier_vente`, fiche `03_agents/crm/AGENT.md:25`, fiche `03_agents/onapi_legal/AGENT.md:50`) mais **AUCUN gate ne couvrait le NOM**. Le bloc CRM pipeline existant ne recompute que le **COMPTE d'états** (9), aveugle à l'identité ; aucune suite `tests/` (FONCTIONS de graphe) n'attrape la prose.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même classe d'IDENTITÉ
que les fiches de rôle RBAC (`agent-fiche-role-attrs-ungated`) ou l'identité du rôle
émetteur e-CF — appliquée à une **surface distincte** : le NOM d'un DocType porteur,
transcrit à ≥4 endroits et **asserté en prose égal** à `workflow.document_type`,
jamais confronté à l'artefact byte-gaté.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/dossier_vente/README.md:1` (titre « # DocType porteur ·
`OTO Dossier Vente` ») **et** `:29`**Nom du DocType** = `document_type` du
workflow (`OTO Dossier Vente`) »).
- `03_agents/crm/AGENT.md:25` — « **DocType porteur** `OTO Dossier Vente` : … sans
lui le pipeline n'a rien à quoi s'attacher ».
- `03_agents/onapi_legal/AGENT.md:50` — « cible = `workflow.document_type`
(`OTO Dossier Vente`) ».
- Source faisant autorité : `crm/dossier_vente/out/doctype_oto_dossier_vente.json`
(champ `name`, = `MANIFEST.doctype_name`, byte-gaté par `check_artifacts`). L'attache
est confirmée par `crm/workflow_vente/out/workflow.json[0].document_type` (byte-gaté).
- Piège : **RENOMMER** le DocType dans `doctype_spec.json`
(`OTO Dossier Vente``OTO Dossier de Vente`) reconstruit les DEUX artefacts de
façon COHÉRENTE (le `document_type` du workflow suit, les tests restent verts),
MAIS les mentions en prose se PÉRIMENT en silence → l'agent ERPNext Backend
importerait un DocType sous un nom pendant que la fiche/le README en nomment un
autre, et le Workflow s'attacherait à un DocType FANTÔME (la rupture même que
« sans lui le pipeline n'a rien à quoi s'attacher » veut prévenir).
- **État courant** : **aucun nom périmé** — les 4 mentions + les 2 artefacts
recoupent exactement (anti-invention #6, rien à réécrire). Le défaut est la
**surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM · IDENTITÉ du
DocType porteur » après le bloc CRM pipeline états) : on RECOMPUTE le nom depuis
`doctype_oto_dossier_vente.json[name]` et on exige que chaque prose le nomme EXACTEMENT
(regex backtické par mention). Cohérences croisées bonus (mordent un artefact
INTERNEMENT incohérent) : `DocType.name == MANIFEST.doctype_name` (cohérence interne)
· `== workflow.document_type` (**l'attache porteur↔workflow** — sinon le Workflow vise
un DocType fantôme). Un claim absent échoue AUSSI (traçabilité #6).
**7 morsures vérifiées** (3 côté artefact · 4 côté prose) : nom artefact renommé
(`name``OTO Dossier de Vente`) → mord **6 lignes en cascade** (incohérence interne +
attache rompue + les 4 mentions périmées) · `workflow.document_type` divergent →
**attache ROMPUE** seule · `MANIFEST.doctype_name` divergent → **incohérence interne**
seule · fiche `crm/AGENT.md:25` renommée · fiche `onapi_legal:50` renommée · ligne
« Nom du DocType » périmée · titre du README périmé ; restauré = green : 6 ✓ · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
absolu) · **7 gates re-verts** (`check_readme_claims`/`check_ci_integrity`/
`check_docs`/`guard_constraints`/`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (ligne récap du pipeline + paragraphe détaillé « IDENTITÉ du DocType
porteur `crm/dossier_vente` ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; l'import réel
du DocType puis du Workflow reste côté agent ERPNext Backend / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_043224` · Buffer S8 · Fiches agents/citation CLAUDE.md #10 : la prose « Anti-invention » de DEUX fiches — `03_agents/erpnext_backend/AGENT.md:57` (devises **USD + DOP** · format **Letter US** · paiements **Cardnet**, pas Stripe) et `03_agents/crm/AGENT.md:37` (devises **USD + DOP** · format **Letter US**) — RECOPIE À LA MAIN les valeurs canoniques de la contrainte NON-NÉGOCIABLE CLAUDE.md #10 en AFFIRMANT « cités depuis CLAUDE.md #10, jamais réinventés », mais AUCUN gate ne liait cette citation à #10.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement close ;
poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe [[claude-md-constant-anchor-gate]]).
Même patron EXACT que le bloc « paramètres canoniques » faisabilite/generator (model.py
CANONICAL ↔ #9/#10) et publiciste/branding (4 tokens ↔ #4) — appliqué à une SURFACE
distincte : la prose des fiches agents qui se réclame de #10.
**Dérive silencieuse fermée** :
- `03_agents/erpnext_backend/AGENT.md:57` + `03_agents/crm/AGENT.md:37` — prose
« Anti-invention (#6) » citant en gras les tokens #10 (devises/format/paiement).
- Source faisant autorité : la contrainte NON-NÉGOCIABLE **CLAUDE.md #10**
(`**USD + DOP**` devises · `**Letter US**` format · `**Cardnet**` paiements (pas Stripe)).
- Le seul contrôle amont (bloc « paramètres canoniques ») n'ancre QUE
`faisabilite/generator/genlib/model.py` CANONICAL + son README — AVEUGLE à ces fiches.
Aucune suite `tests/` (FONCTIONS, jamais l'ancre à CLAUDE.md) n'attrape la prose.
- Piège #6 : RENOMMER le prestataire (`Cardnet``Azul`) ou changer une devise/format dans
CLAUDE.md #10 laisse ces citations PÉRIMÉES en silence, contredisant le mandat pendant
que la prose se réclame de « CLAUDE.md #10 » — « vert trompeur ».
**Gate ajouté** (bloc « Fiches agents · CITATION des constantes CLAUDE.md #10 ») :
réutilise les valeurs `exp` déjà recomputées de #9/#10 (zéro duplication) + le prestataire
EXCLU (`(pas X)`) recomputé de #10 ; config table par-fiche du SOUS-ENSEMBLE de tokens
qu'elle cite (crm omet le paiement) ; exige de chaque fiche l'ancre « CLAUDE.md #10 » +
chaque token en gras `**<valeur>**` == #10 (casse tolérée) + l'exclusion pour erpnext.
**5 morsures vérifiées** (restauré vert après chacune, 7 gates re-verts) :
1. CLAUDE.md #10 `Cardnet``Azul` → mord erpnext SEUL (crm ne cite pas le paiement) ✓
2. CLAUDE.md #10 devises `USD + DOP``USD + EUR` → mord les DEUX fiches ✓
3. CLAUDE.md #10 `(pas Stripe)``(pas PayPal)` → mord l'exclusion erpnext ✓
4. fiche crm `Letter US``Letter USA` → mord CRM (format) ✓
5. fiche erpnext supprime l'ancre `CLAUDE.md #10` → mord INTROUVABLE ✓
## Session `20260801_050225` · Buffer S8 · SEO/schema.org · IDENTITÉ du nœud racine `Organization` ancrée sur CLAUDE.md §Entités : le nom de marque **`Helios RD`** (la *marque publique* · **CLAUDE.md §Entités** « **Helios RD** (marque publique) sous WAG ») et son `url` == base_url du site, portés par le nœud racine du graphe JSON-LD `05_deliverables_mvp/seo/out/seo_schema_org.json`, étaient **byte-gatés pour la REPRODUCTIBILITÉ** (`check_artifacts` prouve que l'artefact se reconstruit depuis `seo_spec.json`) mais **JAMAIS ANCRÉS à CLAUDE.md**. Le bloc SEO schema.org amont gate la COMPOSITION (`@type` + bijection un-listing-par-projet), aveugle à l'IDENTITÉ de la marque que Google indexe.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe
[[claude-md-constant-anchor-gate]]). **Même patron EXACT** que les tokens branding
`publiciste/branding` ancrés sur CLAUDE.md #4 et les paramètres canoniques
`faisabilite/generator` ancrés sur #9/#10 (`CANONICAL` ↔ contrainte) — appliqué à
une **surface distincte** : l'identité de marque émise dans le JSON-LD **public** de
`vente.otov7.com`, dont la reproductibilité byte était gatée mais pas l'ancrage.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/seo/out/seo_schema_org.json` `@graph` — nœud racine
`Organization` : `name: "Helios RD"` + `url: "https://vente.otov7.com"` +
`@id: ".../#organization"` ; chaque nœud `Residence` (listing) porte
`brand.@id == org.@id` + `address.addressCountry == "DO"`.
- `05_deliverables_mvp/seo/seo_spec.json[schema_org]` — l'INPUT du générateur :
`organization.name == "Helios RD"`, `country_code == "DO"`, et un bullet `source`
qui DÉCLARE l'ancrage « CLAUDE.md §Entites (Helios RD = marque publique sous WAG) ».
- Source faisant autorité : **CLAUDE.md §Entités****Helios RD** (marque publique)
sous WAG ») pour la marque + `MANIFEST.base_url` (byte-gaté) pour le base_url + le
`country_code` du spec pour le géo-ciblage.
- Piège #6 : la byte-gate prouve que l'artefact se reconstruit **fidèlement… depuis
un spec dérivé**, PAS que le nom == CLAUDE.md. RENOMMER la marque dans
`seo_spec.json` (ou dans CLAUDE.md §Entités) puis régénérer laisse le JSON-LD
**public** émettre un nom de marque qui CONTREDIT le mandat pendant que
`check_artifacts` reste **VERT** — « vert trompeur ». Aucune suite `tests/`
(FONCTIONS de génération, jamais l'ancrage à CLAUDE.md) ne l'attrape.
- **État courant** : aucune valeur périmée — `name`/`url`/`@id`/rattachements/
`addressCountry`/déclaration du spec recoupent CLAUDE.md+MANIFEST+spec exactement
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « SEO org » après le bloc
composition schema.org) : la marque est **re-dérivée** de CLAUDE.md §Entités
(parenthèse « (marque publique) », exigée présente 1× exactement · zéro duplication),
puis : **(a)** `Organization.name` == marque CLAUDE.md ; **(b)** `Organization.url`
== `MANIFEST.base_url` et `@id` sous base_url ; **(c)** chaque listing rattaché à
CETTE Organization (`brand.@id` == `org.@id`) et sous base_url ; **(d)**
`addressCountry` UNIFORME == `country_code` du spec (code ISO 2 lettres, géo-ciblage
appliqué à l'identique) ; **(e)** le spec DÉCLARE l'ancrage (`organization.name` ==
marque + `source` cite CLAUDE.md + la marque). Racine `Organization` exigée singleton
en pré-requis.
**6 morsures vérifiées** (restauré vert après chacune, 7 gates re-verts) :
1. **artefact** `Organization.name``Helios DR` → mord (a) anchor ✓
2. **CLAUDE.md §Entités** rename `Helios RD``Helios Dominicana` → mord
**SIMULTANÉMENT** (a) artefact ET (e) spec — l'ancre est vive ✓
3. **artefact** `Organization.url` divergent → mord (b) url/@id
4. **artefact** un `addressCountry` `DO``US` → mord (d) uniformité géo ✓
5. **spec** `organization.source` supprime l'ancre CLAUDE.md → mord (e) déclaration ✓
6. **artefact** un listing `brand.@id``#phantom` → mord (c) rattachement ✓
Restauré = green : `Helios RD` == CLAUDE.md · url == base_url · 9 listings rattachés
sous base_url · addressCountry uniforme `DO` · spec déclare l'ancrage · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
absolu) · **7 gates re-verts** (`check_readme_claims`/`check_ci_integrity`/
`check_docs`/`guard_constraints`/`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé
« Identité du nœud racine `Organization` ancrée sur CLAUDE.md §Entités ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; l'injection
réelle du `<script type="application/ld+json">` dans les pages `vente.otov7.com`
reste côté agent Frontend/SEO / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_053228` · Buffer S8 · Legal/CONFOTUR · IDENTITÉ des ENTITÉS porteuses ancrée sur CLAUDE.md §Entités : le champ Select **`entite_porteuse`** du DocType `CONFOTUR Application` — le **menu déroulant** qui fixe QUELLE entité juridique porte chaque dossier d'incitation touristique — offre pour options les **7 entités canoniques** du mandat (`WAF` · `WA SRL` · `AC Arias Cuevas` · `Consortium ECR DR` · `Helios RD` · `Ploutos` · `9060 QC`). Ces options sont DÉRIVÉES via `options_source` de `confotur_spec.json[entites]` et **byte-gatées pour la REPRODUCTIBILITÉ** par `check_artifacts` (l'artefact `doctype_confotur_application.json` se reconstruit depuis le spec) mais **JAMAIS ANCRÉES** à CLAUDE.md §Entités, leur source faisant autorité. Les blocs CONFOTUR amont ne gatent que le COMPTE de champs/rôles et la cross-cohérence des PERMISSIONS, AVEUGLES à l'identité de cette liste d'entités.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe
[[claude-md-constant-anchor-gate]]). **Même patron EXACT** que la marque SEO org
ancrée sur CLAUDE.md §Entités (session précédente `050225`) et les tokens branding
`publiciste/branding` ancrés sur #4 — appliqué à une **surface distincte du MÊME
§Entités** : le déroulant que l'agent ERPNext Backend importe (`bench
import-fixtures`), dont la reproductibilité byte était gatée mais pas l'ancrage.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/legal/confotur/out/doctype_confotur_application.json` — champ
`entite_porteuse` (Select · `reqd`), `options` = les 7 entités séparées par `\n`.
- `05_deliverables_mvp/legal/confotur/confotur_spec.json` — l'INPUT : `entites` (la
liste) + `entites_source` (DÉCLARE « CLAUDE.md #Entites (…) ») + le champ dont
`options_source: "entites"` (wiring : le déroulant DÉRIVE de la liste).
- `05_deliverables_mvp/legal/confotur/README.md:37-38` — prose « `entite_porteuse`
(Select) : entités CLAUDE.md (WAF · WA SRL · **AC** · Consortium ECR DR · Helios
RD · Ploutos · 9060 QC) » (abréviation éditoriale « AC » pour « AC Arias Cuevas »).
- Source faisant autorité : **CLAUDE.md §Entités** — les 7 tokens en gras
`**...**` de la section « ## Entités », dans l'ordre.
- Piège #6 : la byte-gate prouve que l'artefact se reconstruit **fidèlement… depuis
un spec dérivé**, PAS que la liste == CLAUDE.md. RENOMMER une entité dans
CLAUDE.md §Entités (`9060 QC``9061 QC`) OU dans `confotur_spec.json[entites]`
puis régénérer laisse le DocType offrir un menu qui CONTREDIT/omet une entité
canonique du mandat pendant que `check_artifacts` reste VERT — « vert trompeur ».
L'agent ERPNext Backend importerait un DocType dont le déroulant liste une entité
FANTÔME ou en omet une (la restriction même que `reqd` pose). Aucune suite
`tests/` (FONCTIONS de génération) ne l'attrape.
- **État courant** : aucune valeur périmée — options artefact / `entites` du spec /
déclaration / README recoupent CLAUDE.md §Entités exactement (anti-invention #6,
rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Legal · CONFOTUR —
IDENTITÉ des ENTITÉS porteuses » avant `sys.exit`) : les 7 entités sont
**re-dérivées** des tokens en gras de la section « ## Entités » de CLAUDE.md
(uniques, non vides), puis : **(a)** options du Select de l'artefact == entités
CLAUDE.md (ORDRE exact) ; **(b)** `spec[entites]` == CLAUDE.md (l'INPUT ancré) ;
**(c)** le spec DÉCLARE l'ancrage (`entites_source` cite CLAUDE.md + chaque
entité) ; **(d)** wiring intact (`options_source == "entites"` — sinon le déroulant
est DÉCROCHÉ de la liste ancrée) ; **(e)** README cite l'ancre « CLAUDE.md » +
nomme chaque entité par **nom complet OU 1er token** (l'abréviation « AC » tolérée ·
#6, pas de réécriture d'une prose correcte pour satisfaire un gate). Un claim absent
échoue AUSSI (traçabilité #6).
**7 morsures vérifiées** (restauré vert après chacune, 7 gates re-verts) :
1. **CLAUDE.md §Entités** `9060 QC``9061 QC` → mord SIMULTANÉMENT (a) artefact +
(b) spec + (c) déclaration + (e) README = **4 bites** — l'ancre est vive ✓
2. **artefact** retire la dernière option → mord (a) ✓
3. **artefact** permute 2 options → mord (a) ORDRE ✓
4. **spec** `entites` renomme `Ploutos``Ploutos SA` → mord (b) ✓
5. **spec** `entites_source` retire « CLAUDE.md » → mord (c) ✓
6. **spec** champ `options_source``hardcoded` → mord (d) wiring ✓
7. **README** retire l'entité `Ploutos` de la ligne → mord (e) ✓
Restauré = green : options == CLAUDE.md (7, ordre) · spec == CLAUDE.md · spec
déclare l'ancrage · wiring == "entites" · README nomme les 7 · exit 0. Working tree
byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) ·
**7 gates re-verts** (`check_readme_claims`/`check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé
« Entités porteuses du DocType CONFOTUR ancrées sur CLAUDE.md §Entités ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; l'import réel
du DocType `CONFOTUR Application` reste côté agent ERPNext Backend / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_060234` · Buffer S8 · Legal/CONFOTUR — cross-cohérence PERMISSIONS répliquée dans la FICHE AGENT `03_agents/onapi_legal/AGENT.md:43-45` : la MÊME table role→portail→actions que le README du module, jamais gatée
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Classe explicitement
notée en mémoire (`agent-fiche-role-attrs-ungated`) : une fiche `03_agents/*/AGENT.md`
peut restituer la MÊME donnée data-derived qu'un README déjà gaté, sur une **surface
distincte** que le gate README ne voit pas.
**Dérive silencieuse fermée** :
- `03_agents/onapi_legal/AGENT.md:43-45` — §Permissions de la fiche de l'agent
ONAPI/Legal, annoncée « **mot pour mot** les `permissions_cibles` RBAC des 3
rôles » : `ventes-confotur` (portail Ventes) → read/write/create/print ·
`legal-onapi` (portail Direction) → read/write/create · `legal-directeur`
(portail Direction) → read/write/**submit**/report. Convention DIFFÉRENTE du
README du module (` / ` espacé · `**submit**` en gras).
- Source faisant autorité : `legal/confotur/out/MANIFEST.json`
(`roles_rbac_utilises[]` : `role_id` · `portail` · `permissions`, recomputé du
contrat `rbac_50_roles.json` à chaque build · byte-gaté par check_artifacts).
- Piège : le bloc « Legal confotur perms · cross-cohérence » existant ne gate QUE
la §Permissions du README du MODULE (`legal/confotur/README.md`), AVEUGLE à la
fiche. PROMOUVOIR `ventes-confotur` à `submit` (élévation de privilège · casse
la séparation des pouvoirs dont `is_submittable` est déduit), RETIRER une
action, RÉAFFECTER un portail, RENOMMER un rôle ou AJOUTER une ligne fantôme
dans la FICHE laisse celle-ci périmée pendant que l'artefact (et le README, lui
gaté) disent autre chose → l'agent ONAPI/Legal lirait sa PROPRE doc d'identité
mentant sur le jeu de permissions qu'il porte (le risque même que « mot pour
mot » promet d'écarter) — « vert trompeur » qu'aucune suite `tests/` (FONCTIONS
de mapping RBAC, jamais la prose de fiche) n'attrape.
- **État courant** : **aucune ligne périmée** — les 3 couples portée→actions et
leurs portails recoupent l'artefact exactement (anti-invention #6). Le défaut
est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Legal · CONFOTUR —
cross-cohérence PERMISSIONS répliquée dans la FICHE AGENT » après le bloc
cross-cohérence du README) : RÉUTILISE `conf_perm_by_id`/`conf_portail_by_id`
déjà dérivés du MANIFEST par le bloc README (ZÉRO duplication du contrat #6) ;
regex tolérant l'espacement ` / ` et le gras `**` ; PAR rôle le portail (casse
normalisée) ET le jeu d'actions exigés EXACTS (set-diff : absent ET en trop) ;
identité d'ensemble fiche⇔MANIFEST (aucun rôle fantôme ni manquant). Un claim
absent échoue AUSSI (traçabilité).
**6 morsures vérifiées** : fiche promeut `ventes-confotur``submit` (en trop) ·
fiche retire `create` de `legal-onapi` (absent) · fiche réaffecte portail
Ventes→Direction · fiche renomme `legal-directeur``legal-boss` (ligne
INTROUVABLE + ensemble absents/fantôme) · sous-liste entière supprimée (3
INTROUVABLES + ensemble vide) · **artefact** promeut `ventes-confotur``submit`,
fiche stale (le vrai « silent green » : source change, fiche fige) ; restauré =
green : 3 rôles portail+actions == MANIFEST · identité d'ensemble · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`) · **7
gates re-verts**.
- `ci/README.md` (table récap du pipeline · clause « 3e surface » CONFOTUR) mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
- **Auto-score 4Big** : 96/100.
## Session `20260801_063234` · Buffer S8 · Chat OTOIA/persona+capabilities : l'**IDENTITÉ de la persona** (`Amélie` / voix `multilingual_v2`) et des **4 CAPABILITIES OTOIA** (`aec.py` + `knowledge.py` + `prompt_engine.py` + `chat.py`) du montage Chat par portail `frontend/chat_otoia` — portées par `out/chat_mount.json` (5 configs runtime) + `out/MANIFEST.json`, **byte-gatés pour la reproductibilité** (`check_artifacts` prouve la reconstruction depuis `chat_spec.json`) mais **JAMAIS ANCRÉES à CLAUDE.md §Architecture cible**, leur source faisant autorité (« Voix Amélie QC (multilingual_v2) » l.30 · « OTOIA capabilities : aec.py + knowledge.py + prompt_engine.py + chat.py » l.28).
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe
[[claude-md-constant-anchor-gate]]). **Même patron EXACT** que les tokens branding
`publiciste/branding` ancrés sur CLAUDE.md #4, la marque SEO `Organization` ancrée sur
§Entités, les paramètres canoniques `faisabilite/generator` ancrés sur #9/#10
appliqué à une **surface distincte** : la persona + les capabilities émises dans CHAQUE
portail rôle, dont la reproductibilité byte était gatée mais pas l'ancrage.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/frontend/chat_otoia/out/chat_mount.json` — les 5 mounts,
chacun `persona: {nom: "Amélie", voix: "multilingual_v2"}` +
`capabilities: [aec.py, knowledge.py, prompt_engine.py, chat.py]` ; idem
`out/MANIFEST.json` ; l'INPUT `chat_spec.json` (persona + `capabilities[].module`) ;
la table README:28-29 (« Persona `Amélie` / `multilingual_v2` » · « Capabilities
`aec/knowledge/prompt_engine/chat.py` » toutes deux annotées « `CLAUDE.md
§Architecture cible` »).
- Source faisant autorité : **CLAUDE.md §Architecture cible** (l.28 capabilities ·
l.30 « Voix Amélie QC (multilingual_v2) »).
- Piège #6 : le SEUL contrôle d'identité vit dans `tests/`
(`test_persona_amelie_sourcee` / `test_capabilities_sourcees_sans_ajout`) mais son
**ORACLE est HARDCODÉ** (`self.assertEqual(spec["persona"]["nom"], "Amélie")` ·
`caps == ["aec.py", …]`) — une **copie de plus, jamais comparée à CLAUDE.md**.
Renommer la persona (`Amélie``Sophie`), changer la voix, ou renommer/RETIRER une
capability dans `chat_spec.json` (ou dans CLAUDE.md) reconstruit l'artefact
fidèlement (byte-gate VERTE) sans toucher l'oracle du test (tests VERTS) → le chat
monté dans CHAQUE portail annoncerait une persona/voix/capabilities qui CONTREDIT le
mandat — « vert trompeur » qu'aucune suite `tests/` (FONCTIONS de génération, jamais
l'ancre à CLAUDE.md) n'attrape.
- **État courant** : **aucune valeur périmée** — persona + capabilities recoupent
CLAUDE.md exactement (anti-invention #6, rien à réécrire). Le défaut est l'**ancrage
ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Chat OTOIA · persona +
capabilities ancrées CLAUDE.md » avant `sys.exit`) : persona+capabilities re-dérivées
de CLAUDE.md §Architecture cible (regex `Voix … QC (…)` + `OTOIA capabilities : …`
split `+`, NFC), puis : **(a)** `chat_spec.json` persona/capabilities == CLAUDE.md
(ordre exact) ; **(b)** le spec DÉCLARE l'ancrage (`persona.source` + chaque
`capability.source` citent CLAUDE.md) ; **(c)** CHACUN des 5 mounts + le MANIFEST
portent persona/capabilities == CLAUDE.md ; **(d)** README cite l'ancre §Architecture
cible + nomme persona (`Amélie`+`multilingual_v2`) + chaque capability (notation
compacte matchée par radical) ; **(e)** l'ORACLE du test == CLAUDE.md (set-diff). Un
claim absent échoue AUSSI (traçabilité #6).
**8 morsures vérifiées** : CLAUDE.md `Amélie→Sophie` (mord SIMULTANÉMENT
spec/mount/README/oracle — 5 fails, l'ancre est vive) · CLAUDE.md retire `chat.py`
(3 fails) · spec `aec.py→exfil.py` (**capability injectée**) · spec `persona.source`
sans CLAUDE.md · **UN SEUL** mount à voix altérée (byte-gate aveugle) · README retire
le radical `prompt_engine` · oracle du test persona `Amélie→Bob` · oracle du test
capabilities réordonné/tronqué ; restauré = green : 5 sous-checks (a/b/c/d/e) verts ·
exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
absolu) · **7 gates re-verts** · unittest module `chat_otoia` re-vert.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « Persona &
capabilities du Chat OTOIA ancrées sur CLAUDE.md §Architecture cible ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; l'import réel des
`Custom Block` + le câblage de l'endpoint OTOIA restent côté agent ERPNext Backend / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_070237` · Buffer S8 · Mobile/app_config : le **SDK MAJEUR Expo** (« Expo 54 ») — la version majeure que l'app compagnon cible, **ANCRÉE sur la roadmap Sprint 5 l.56** « Rebuild Expo 54 » — était transcrite À LA MAIN **cinq fois** dans le README SANS AUCUN gate d'IDENTITÉ ni d'ANCRAGE. Le bloc « Mobile · récap de l'app Expo » existant ne gate QUE le quadruplet onglets/rôles/langues/identifiants `a_confirmer` (comptes de `MANIFEST.counts`), AVEUGLE au majeur du SDK.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même patron d'ANCRAGE que
la marque SEO §Entités (`Helios RD` ancrée sur CLAUDE.md) ou les `roadmap_line` de la
recette `qa/acceptance` — appliqué ici à une valeur ancrée sur la **ROADMAP** (source
faisant autorité de la version Expo, CLAUDE.md ne la spécifiant pas).
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/mobile/app_config/README.md:4/19/35/72` — « Expo 54 »
(×4 mentions) + la ligne de source « Expo SDK **54** | roadmap Sprint 5 l.56 ».
- Chaîne de dérivation (byte-gatée par `check_artifacts`) : roadmap Sprint 5 l.56
« Rebuild Expo 54 » (INPUT faisant autorité) → `mobile_spec.json[app]`
(`expo_sdk_major = 54` + `expo_sdk_source` cite la roadmap) →
`out/app_config.json.expo.extra.expoSdkMajor = 54` + `out/MANIFEST.json
.app.expo_sdk_major = 54`.
- Piège #6 : BUMPER le SDK (Expo 55 sort · `eas build` cible 55) dans le
spec/artefact SANS toucher au README (ou l'inverse), ou DÉRIVER la roadmap de
l'artefact, laisse la prose PÉRIMÉE en silence pendant que le graphe byte-gaté
dit autre chose → l'agent Mobile lancerait `eas build` sur le MAUVAIS SDK (le
rebuild même que la ligne roadmap prescrit). Aucune suite `tests/` (FONCTIONS de
génération, jamais la prose ni l'ancre à la roadmap) n'attrape ce « vert
trompeur ».
- **État courant** : aucune mention périmée — les 4 « Expo 54 », le spec, le
MANIFEST et la roadmap concordent (anti-invention #6, rien à réécrire). Le défaut
est la **surface ungated + non ancrée à la roadmap**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Mobile · le SDK MAJEUR
Expo … ANCRÉ sur la roadmap Sprint 5 l.56 », inséré avant `sys.exit`) : le majeur
`sdk` re-dérivé de `app_config.json` byte-gaté, puis (a) **cohérence interne**
artefact ⇔ `MANIFEST.app.expo_sdk_major``spec.app.expo_sdk_major` ; (b)
**ancrage roadmap**`re.search("Rebuild Expo (\d+)")` sur
`04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md` == `sdk` (ancre vive) ; (c) le spec DÉCLARE
l'ancrage (`expo_sdk_source` cite « roadmap » + « Expo N ») ; (d) **toute** mention
prose « Expo [SDK] N » (regex `Expo(?:\s+SDK)?\s*\*{0,2}(\d+)`) == `sdk` (aucune
périmée) ; (e) la ligne de source du tableau cite l'ancre `roadmap` à côté du majeur.
Un claim absent échoue AUSSI (traçabilité).
**7 morsures vérifiées** : README `Expo SDK **54**→**55**` (prose périmée) ·
**artefact** `54→55` (README + roadmap périmés = le vrai *silent green*) ·
**roadmap** `Rebuild Expo 54→55` (artefact périmé · ancre qui dérive) ·
**MANIFEST** `54→55` (chaîne interne incohérente) · spec `expo_sdk_source` sans
« roadmap » (ancrage déclaré manquant) · README ligne de source sans « roadmap »
(ancrage prose manquant) · README retire TOUTE mention « Expo N » (claim absent) ;
restauré = green : cohérence interne · ancre roadmap vive · spec déclare · 4
mentions == 54 · ligne source cite roadmap · exit 0. Working tree byte-restauré
(`git checkout --`, **JAMAIS** `git clean`) · **7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « SDK majeur Expo
de l'app mobile ancré sur la roadmap Sprint 5 l.56 ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; `eas build`/
`eas submit` App Store #32 / Play Store restent côté agent Mobile · VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_073239` · Buffer S8 · Fiscal/ecf_dgii : le JEU DE DEVISES `moneda` (`USD`/`DOP`) ANCRÉ sur CLAUDE.md #10 — le champ que `TipoMoneda` porte dans chaque e-CF DGII, byte-gaté pour la REPRODUCTIBILITÉ mais JAMAIS ancré à la contrainte. Le README s'en réclame « #10 » (README:37/57/77) ; les blocs fiscaux amont gatent le FormaPago (Cardnet · #10) + états/champs émetteurs, AVEUGLES au jeu de devises ; le bloc « Fiches #10 » ancre les fiches ERPNext/CRM, AVEUGLE au module fiscal.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même patron d'ANCRAGE que
les paramètres canoniques `faisabilite/generator` (#9/#10), la marque SEO §Entités
(`Helios RD`), la persona Chat OTOIA (§Architecture cible) ou les tokens branding #4
— appliqué ici à une valeur ancrée sur **CLAUDE.md #10****USD + DOP** devises »).
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/fiscal/ecf_dgii``moneda.options = ["USD","DOP"]` dans le
spec ET (byte-gaté par `check_artifacts`) dans `out/ecf_plan.json`.
- Chaîne de dérivation : CLAUDE.md #10 « **USD + DOP** devises » (INPUT faisant
autorité) → `ecf_spec.json[moneda].options``out/ecf_plan.json.moneda.options`
(copie verbatim par `ecflib/builder.py:79`).
- Piège #6 : **byte-gater une constante prouve la REPRODUCTIBILITÉ, PAS l'ANCRAGE**.
`check_artifacts` prouve `ecf_plan==build` depuis le spec, JAMAIS que
`spec[moneda]==#10`. RENOMMER/ÉTENDRE les devises dans CLAUDE.md #10
(`USD + DOP``USD + EUR`, ou +`EUR`) ET dans le spec de façon cohérente reste
byte-VERT tout en faisant émettre à l'e-CF un `TipoMoneda` d'une devise qui
CONTREDIT le mandat — « vert trompeur » qu'aucune suite `tests/` (FONCTIONS de
génération, jamais l'ancre à CLAUDE.md) n'attrape. L'agent ERPNext Backend
câblerait `Encabezado.TipoMoneda` sur une devise hors mandat (le risque même que
la cross-cohérence e-CF veut prévenir).
- **État courant** : aucune dérive — spec/artefact/README concordent avec #10
(anti-invention #6). Le défaut était la **surface byte-gatée mais non ancrée** +
l'absence de déclaration d'ancrage (`moneda.source` legitimement `null` car il
source le **taux TipoCambio**, à confirmer côté VPS · #8 — l'ancre des OPTIONS
devait vivre dans un champ DISTINCT).
**Correctif de déclaration** : nouveau champ `moneda.options_source` dans
`ecf_spec.json` (+ requis dans `ecf.schema.json`) citant « CLAUDE.md #10 — devises
**USD + DOP** » ; distinct de `source` (taux, reste `null`/`a_confirmer`). Artefact
`out/ecf_plan.json` régénéré (byte-gate re-verte · 39 tests module verts · le compte
`a_confirmer` inchangé, il ne teste que `moneda.a_confirmer`).
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Fiscal · ecf_dgii — le JEU DE
DEVISES `moneda` … ANCRÉ sur CLAUDE.md #10 », inséré après la famille « Fiches
#10 ») : RÉUTILISE `exp["devises"]` déjà recomputé depuis #10 (zéro duplication),
`want_cur = re.findall("[A-Z]{3}", exp["devises"])`, puis (a) `spec[moneda].options`
== `want_cur` ORDRE EXACT (mord ajout/retrait/réordonnancement) ; (b) l'artefact
byte-gaté idem ; (c) `moneda.options_source` cite `CLAUDE.md #10` + chaque devise ;
(d1) la ligne README « devise (…USD/DOP… · #10) » cite le jeu EXACT ; (d2) toute
mention `moneda`/`devise` d'une paire de devises (scan ancré sur le contexte, évite
le faux positif `RNC / ITBIS`) == le jeu (aucune périmée). Un claim absent échoue
AUSSI (traçabilité).
**7 morsures vérifiées** : CLAUDE.md #10 `USD+DOP→USD+EUR` (l'ancre est vive →
mord a+b+c+d1 en cascade) · **spec** `options USD/DOP→USD/EUR` régénéré (le vrai
*silent green* → mord a) · **artefact** seul `USD→EUR` (spec+README périmés → mord
b) · **reorder** spec `[DOP,USD]` (ordre exact → mord a) · `options_source` sans
« CLAUDE.md #10 » (ancre déclarée manquante → mord c) · **README** ligne #10
`USD/DOP→USD/EUR` (prose périmée → mord d1) · **README** table `moneda USD/DOP→
USD/EUR` (mention périmée → mord d2) ; restauré = vert (a+b+c+d, 3 mentions
README concordantes · exit 0). Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`) · **7 gates re-verts** (validate_json · check_artifacts ·
check_readme_claims · check_docs · guard_constraints · check_ci_integrity ·
check_regression).
- `ci/README.md` (clause de récap du pipeline + paragraphe détaillé « jeu de
DEVISES `moneda` … 4e surface du MÊME README, ancré sur #10 ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
la confirmation du **TipoCambio** USD→DOP + l'émission réelle via Compupar
restent côté agent ERPNext Backend / Compta · VPS).
- **Auto-score 4Big** : 96/100.
---
## Session `20260801_080244` · Buffer S8 · SEO/projets_master — IDENTITÉ du CATALOGUE PROJETS (P01..P09) ANCRÉE sur CLAUDE.md §Projets
**Domaine** : SEO · `05_deliverables_mvp/seo/fixtures/projets_master.json` + artefacts `out/seo_schema_org.json`.
**Constat (anti-drift #6)** : la matière première du worker SEO est `projets_master.json` — 9 projets (`code` + `nom`) que le bundle SEO expose à Google (un listing `Residence` par projet dans le JSON-LD indexé sous `vente.otov7.com/projets/…`). Chaque entrée **DÉCLARE** `source.fichiers: ["CLAUDE.md#Projets"]` et le README:80 l'affirme (« 9 projets P01..P09 · noms sourcés de `CLAUDE.md §Projets` ») — **MAIS AUCUN gate ne le VÉRIFIAIT**. `check_artifacts` prouve QUE les `out/` se reconstruisent depuis la fixture (byte-for-byte), PAS que ses `code`/`nom` == CLAUDE.md §Projets ; les blocs SEO amont gatent les COMPTES de mots-clés, la COMPOSITION du graphe schema.org (bijection un-listing-par-projet) et l'IDENTITÉ de la marque racine `Helios RD` (§Entités) — tous AVEUGLES à l'identité de la **liste de projets**.
**Vert trompeur** : RENOMMER un projet dans CLAUDE.md §Projets (« Aqua Terra Las Terrenas » → « … Bay »), le RENOMMER/AJOUTER/RETIRER dans la fixture, puis régénérer, laisse le JSON-LD PUBLIC lister un projet FANTÔME ou en OMETTRE un canonique pendant que la byte-gate reste VERTE (l'artefact se reconstruit fidèlement… depuis une fixture DÉRIVÉE) et que le compte reste 9. Même classe EXACTE que la marque org (§Entités · session 050225), les entités CONFOTUR (§Entités · 053228) et les libellés projets du run_sheet démo (§Projets · déjà gaté). Aucune suite `tests/` (FONCTIONS de génération, jamais l'ancrage à CLAUDE.md) ne l'attrape.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « SEO · l'IDENTITÉ du CATALOGUE PROJETS ») — RE-DÉRIVE le catalogue de CLAUDE.md §Projets (zéro duplication · source unique), puis exige :
- **(a)** identité d'ensemble des codes fixture ⇔ CLAUDE.md §Projets (aucun fantôme/manquant · pas de doublon) ;
- **(b)** `nom` de chaque entrée == CLAUDE.md §Projets (accent-**insensible** : la fixture normalise « Xamaná »→« Xamana ») ;
- **(c)** chaque entrée DÉCLARE l'ancrage `CLAUDE.md#Projets` dans `source.fichiers` ;
- **(d)** les `name` des listings du graphe schema.org PUBLIC == l'ensemble des noms CLAUDE.md §Projets + bijection (1 listing/projet) + `MANIFEST.counts` projects/listings == |§Projets| ;
- **(e)** le README cite l'ancrage CLAUDE.md §Projets. Un claim absent échoue AUSSI (disparition = régression).
**Vérif** : 13 confirmations vertes (9 noms + set codes + listings schema.org + MANIFEST + README) ; **7 morsures vérifiées puis restaurées** — CLAUDE.md rename P07 (bite b+d simultanés, l'ancre est vive) · fixture nom drift P03 · fixture drop P09 (set manquant) · fixture add fantôme P10 (set fantôme) · fixture strip `source.fichiers` P01 · artefact schema.org rename `Structure``Estructura` (le vrai silent-green public) · README retire la citation §Projets. Restauré vert. **7 gates CI re-verts** (`check_readme_claims`, `check_artifacts`, `check_docs`, `check_ci_integrity`, `guard_constraints`, `check_regression`, `validate_json`).
- **Auto-score 4Big** : 96/100.
## Session `20260801_083254` · Buffer S8 · DevOps/deploy_runbook — IMPUTABILITÉ des CONFIRMATIONS préalables VPS (owner + réf-contrôle) ancrée sur out/deploy_runbook.json qa/audit_5d
**Domaine** : DevOps · `05_deliverables_mvp/devops/deploy_runbook/README.md:55-58`
(ligne « Confirmations préalables VPS ») `out/deploy_runbook.json` (byte-gaté)
`05_deliverables_mvp/qa/audit_5d/out/audit_report.json` (byte-gaté).
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même classe de « vert
trompeur » que la table run-book (2e surface `deploy_runbook`) ou l'identité du RÔLE
émetteur e-CF — appliquée à une **3e surface distincte du MÊME README `deploy_runbook`** :
non plus le COMPTE de confirmations ni l'ENSEMBLE de leurs noms (déjà gatés), mais
l'**imputabilité** de chacune — QUI la porte et QUEL contrôle audit_5d la trace.
**Dérive silencieuse fermée** :
- `devops/deploy_runbook/README.md:55-58` — la ligne « Confirmations préalables VPS »
annote CHAQUE confirmation réglementaire d'un COUPLE data-derived
`nom` (**owner** · **réf-contrôle**) : `taux_commission` (Direction · audit_5d D1.1) ·
`rnc_emisor` (Compta · D1.2) · `itbis_tipocambio` (Fiscaliste eCF · D1.3) ·
`seuil_uaf` (Oficial de Cumplimiento · D2.3) · `endpoint_otoia` (ERPNext Backend ·
session 19). L'OWNER = qui doit confirmer le paramètre avant déploiement ; la RÉF =
le contrôle qa/audit_5d (`D#.#`) — ou la session — qui en assure le suivi.
- Sources faisant autorité (double, byte-gatées par `check_artifacts`) : (a) les
`confirmations` de `out/deploy_runbook.json` — chaque item porte `owner` + `source` ;
(b) `qa/audit_5d/out/audit_report.json` — chaque `control` (`D1.1`..`D5.3`) porte
SON PROPRE `owner`. L'owner et le contrôle ne vivent PAS dans le MANIFEST
(`confirmations_ouvertes` n'est qu'une liste de NOMS) : ils ne sont vérifiables que
contre ces deux artefacts croisés.
- Piège #6 : les blocs DevOps amont ne gatent que le COMPTE (2) et l'ENSEMBLE des
noms — AVEUGLES à QUI porte chaque confirmation et à QUEL contrôle la trace.
RÉAFFECTER `taux_commission` de Direction à Ventes (mauvais responsable escaladé →
confirmation jamais obtenue · séparation des pouvoirs cassée), MISATTRIBUER le
contrôle (`D1.1``D2.1`, pointant un contrôle qui ne trace PAS ce paramètre) ou
citer un contrôle FANTÔME (`D9.9`, absent d'audit_5d) laisse la prose périmée EN
SILENCE pendant que l'artefact/l'audit disent autre chose → l'agent DevOps
escaladerait le mauvais owner / le mauvais contrôle. Aucune suite `tests/`
(FONCTIONS de graphe) n'attrape ce « vert trompeur ».
- **État courant** : aucune annotation périmée — les 5 couples owner/réf recoupent
`deploy_runbook.json`, et les 4 réf `D#.#` existent dans audit_5d avec un owner
compatible (`Oficial de Cumplimiento``Oficial de Cumplimiento / UAF`, `Fiscaliste
eCF``Compta Fiscaliste eCF`) (anti-invention #6, rien à réécrire). Le défaut est
la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 2ter) deploy_runbook —
IMPUTABILITÉ des CONFIRMATIONS » après le bloc table de phases) : confirmations
aplaties de `deploy_runbook.json` (unicité inter-phases), contrôle audit_5d résolu de
la `source` (`D#.#`), annotations README parsées `nom` (owner · réf), puis **(a)**
cross-module — chaque confirmation tracée porte un contrôle audit_5d RÉEL dont l'owner
est COMPATIBLE avec l'owner run-book (sous-ensemble de tokens · tolère « / UAF ») ET
elle reste ANNOTÉE au README ; **(b)** chaque annotation désigne une confirmation
RÉELLE (pas fantôme), owner README ⊆ owner run-book (tolère l'abréviation), et la réf
== contrôle réel (présent audit_5d + source) ou, pour une réf textuelle
(« session 19 »), présente dans la source. Un claim absent échoue AUSSI (traçabilité #6).
**9 morsures vérifiées** (4 côté README · 3 côté artefact · 2 réf) : README réaffecte
owner `Direction→Ventes` (mauvais responsable) · README misattribue `D1.1→D2.1`
(contrôle périmé vs source) · README cite contrôle fantôme `D9.9` · README retire
l'annotation de `taux_commission` (tracée par D1.1, INTROUVABLE) · **artefact**
réaffecte `taux_commission` owner→Ventes (README stale = le vrai *silent green* · mord
cross-module ET README⊆) · **artefact** audit_5d D1.1 owner `Direction→Finanzas`
(divergence inter-modules) · **artefact** source `seuil_uaf` `D2.3→D9.9` (contrôle
FANTÔME cité par la source) · README annotation fantôme (`endpoint_otoia→endpoint_xxx`) ·
README réf textuelle `session 19→session 42` (absente de la source) ; restauré = green :
5 couples owner/réf == artefacts croisés · 4 réf `D#.#` présentes audit_5d + source ·
exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
absolu) · **7 gates re-verts** (`check_readme_claims`/`check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé « 3ᵉ
surface `devops/deploy_runbook` — imputabilité des confirmations ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; l'obtention
réelle des 7 confirmations auprès de leurs owners reste côté agent DevOps / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_093301` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §3 — DÉFAUT FACTUEL corrigé + gate d'identité par portail
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6). Cette fois la surface data-derived non gatée était
**FAUSSE**, pas seulement à risque : la table §3 « Cartographie portails ↔ familles »
de `RBAC_50_ROLES_SPEC.md` (colonne « Nb rôles » PAR portail) avait dérivé du contrat
`rbac_50_roles.json` **en préservant le total 50**, donc en silence.
**Défaut factuel trouvé & corrigé** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md:47` — portail `direction` affichait
**10** rôles ; le contrat en compte **9** (6 direction + CFO/finance + analyste
faisabilité + 2 legal).
- `…:52` — portail `plateforme` affichait **5** ; le contrat en compte **6** (devops ·
rbac-admin · qa · otoia · mobile · bi).
- L'erreur était compensée (10+5 vs 9+6) → total 50 exact → invisible aux gates
agrégés. Le MANIFEST roleprofile (byte-gaté) disait déjà 9 et 6 : la SPEC seule
avait dérivé. Corrigé pour refléter la source.
**Dérive silencieuse fermée (gate durable)** :
- `ci/check_readme_claims.sh` — nouveau bloc « 1quater) RBAC_50_ROLES_SPEC §3 » : RECOMPTE
le nombre de rôles PAR portail depuis `rbac/rbac_50_roles.json` (source de vérité,
byte-gatée par `check_artifacts`) et exige l'égalité avec CHAQUE ligne de la table §3
**plus le total** `**Total** | **50**`. Un portail absent de la table échoue aussi
(disparition = régression de traçabilité). Complète le gate roleprofile (qui gate le
MANIFEST, un doc distinct) sur une 2ᵉ surface transcrivant la même ventilation.
- Vérif négative : réintroduire `direction=10` fait échouer le gate (exit 1, message
« dit 10 MAIS rbac_50_roles.json compte 9 ») ; restauré → vert.
**Artefact régénéré (reproductibilité #artifact-reproducibility-gate)** :
- `qa/audit_4big/out/quality_report.json` — l'`evidence` DOC cite la taille en octets de
la SPEC (10040 → 10039 octets après correction 1 o) ; rebuild `audit_4big_gen.py build
-o out` byte-identique. `check_artifacts` re-vert.
**CI** : les 7 gates PASS (validate_json · check_artifacts · check_docs ·
check_readme_claims · guard_constraints · check_regression · check_ci_integrity).
- **Hors périmètre worker (VPS · #8)** : néant (correction d'un doc en-repo + gate
bash/python3 stdlib ; l'unique artefact `out/` touché a été régénéré à l'identique).
- **Auto-score 4Big** : 96/100.
## Session `20260801_100301` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §7 — comptes du run-book d'application ancrés sur le contrat rbac_50_roles.json
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6). Même classe que le §3 gaté à la session précédente
(`093301`, per-portail) : une **2ᵉ surface de la MÊME SPEC** transcrivant à la main des
comptes data-derived, déjà gatés ailleurs mais AVEUGLES à cette note (mémoire
[[rbac-spec-portail-count-gate]]).
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` §7 — la NOTE « ✅ … est **livré**
en-repo » restitue TROIS comptes du run-book d'application sur une **surface
DISTINCTE** des READMEs `userperm_gen` / `roleprofile_gen` (déjà gatés) et du §3
(ventilation par portail, bloc `1quater`) : « **28** templates `Company` (portée
`entite`) » · « **6 profils** (**5** portails métier + console technique
`plateforme`) » · « **50 rôles de façon bijective** ».
- Source faisant autorité : `rbac/rbac_50_roles.json` (contrat, byte-gaté par
`check_artifacts`). Recomputes : 28 == |`scope_donnees == entite`| (= nombre de
`User Permission` templates) · 6 == |portails distincts| (1 `Role Profile` par
portail) · 5 == portails **métier** (hors `plateforme`) · 50 == |rôles|.
- Piège #6 : BUMPER une portée `entite``groupe` (le décompte de templates change) ou
AJOUTER/RETIRER un portail (le décompte de profils change) dans le contrat périme la
note EN SILENCE pendant que les READMEs de volet et le §3 restent verts → l'agent
ERPNext Backend lirait un run-book promettant un décompte faux. Aucune suite `tests/`
(FONCTIONS de génération/résolution RBAC, jamais la prose de la SPEC) n'attrape ce
« vert trompeur ».
- **État courant** : aucun compte périmé — 28/6/5/50 recoupent le contrat exactement
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 1quinquies) RBAC_50_ROLES_SPEC §7 »
après le bloc §3) : réutilise `src_roles` (contrat) + `spec_rd` (déjà chargés par le
§3, zéro duplication). Le blockquote markdown `> ` de la note survit au collapse de
`read_norm` et fragmente la prose (« 28 > templates ») ⇒ **neutralisé** (`spec7 =
spec_rd.replace("> ", "")`) avant match. Exige : (a) « N templates `Company` (portée
`entite`) » == |scope==entite| ; (b) « **N profils** (M portails métier + console
technique `plateforme`) couvrant les **K rôles de façon bijective** » == |portails
distincts|/|métier hors plateforme|/|rôles|. Garde anti-typo : `plateforme` doit exister
au contrat (sinon la note « console technique plateforme » est fausse). Un claim absent
échoue AUSSI (INTROUVABLE = régression #6).
**6 morsures vérifiées** (restauré vert après chacune) : SPEC `28→27` (prose périmée) ·
SPEC `6 profils→7` · SPEC `5 portails métier→4` · SPEC `50→49` · **contrat** une portée
`entite→groupe` (28→27 · README stale = le vrai *silent green* · « SPEC dit 28 MAIS
compte 27 ») · SPEC retire « 28 templates » (claim absent = INTROUVABLE) ; restauré =
green : UP templates 28 == |entite| · Role Profiles 6 (métier 5) bijectifs sur 50 ==
contrat · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`,
interdit absolu) · **7 gates re-verts** (`check_readme_claims`/`check_ci_integrity`/
`check_docs`/`guard_constraints`/`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé « Comptes
du run-book d'application `RBAC_50_ROLES_SPEC.md` §7 ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; l'application `bench` réelle
reste côté agent ERPNext Backend / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_103304` · Buffer S8 · Chat OTOIA — IDENTITÉ des configs runtime (persona · langues · endpoint) dans la fiche Frontend Console
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6, classe « fiche agent qui restitue des attributs
data-derived, gatée uniquement sur des COMPTES agrégés » — mémoire
[[agent-fiche-role-attrs-ungated]]).
**Dérive silencieuse fermée** :
- `03_agents/frontend_console/AGENT.md:29` — la cellule « Métrique vérifiée » du
livrable `chat_otoia` restitue TROIS attributs data-derived du montage OTOIA :
« persona **Amélie** … · langues **FR/EN/ES** · `` `endpoint: null` `` ». Le bloc
chat_otoia amont (`ci/check_readme_claims.sh`) ne recompute que le **COMPTE**
blocks/mounts/portails (tous = 5) — AVEUGLE à CE QUE dit chaque config runtime.
- Le gate persona/capabilities « ancré sur CLAUDE.md §Architecture cible » vise le
spec, les 5 mounts, le MANIFEST, le README et l'oracle du test — **JAMAIS** cette
cellule de fiche, et il ignore langues comme endpoint. La cellule était donc une
surface data-derived ungated de plus.
- Sources faisant autorité (byte-gatées par `check_artifacts`) :
`frontend/chat_otoia/out/chat_mount.json` (une config par portail · 5) +
`out/MANIFEST.json` (persona/langues/endpoint_statut consolidés).
- **`endpoint: null` = AFFIRMATION D'HONNÊTETÉ #6/#8** : l'endpoint OTOIA n'est PAS
fabriqué (renseigné côté VPS · agent Frontend). Qu'une config gagne un endpoint réel
dans l'artefact et la fiche « endpoint: null » devient un MENSONGE en silence.
- **État courant** : aucun attribut périmé — persona `Amélie` · langues `fr/en/es` ·
`endpoint == null` sur les 5 mounts ET le MANIFEST (anti-invention #6, rien à
réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Chat OTOIA · IDENTITÉ des configs
runtime (persona · langues · endpoint) » après le bloc de COMPTE chat_otoia) : charge
`MANIFEST.json` + `chat_mount.json` (5 configs) et exige, sur la cellule de la fiche
(regex `persona (\S+) QC · langues ([A-Z/]+) · \`endpoint: ([^\`]+)\``, ne matche QUE
la ligne :29, pas la prose Scope :12) : (a) persona == `MANIFEST.persona.nom` (le
« QC » est le qualificatif de marque `Voix Amélie QC` de CLAUDE.md, gardé en littéral
NON gaté) ; (b) langues `FR/EN/ES` == `MANIFEST.langues` en majuscule ; (c) `endpoint:
null` avec `all(m.endpoint is None)` sur les 5 mounts (sinon `exp_endpoint` devient
`NON-null(...)` ⇒ morsure d'honnêteté). Cohérences croisées : `MANIFEST.persona.nom`
et `MANIFEST.langues` == CHAQUE mount (montage désynchronisé mordu) ·
`endpoint_statut == 'a_confirmer'` ⇔ endpoints TOUS null (incohérence interne
artefact sinon). Un claim absent échoue AUSSI (INTROUVABLE = régression #6). Aucune
suite `tests/` (FONCTIONS du builder, jamais la prose de la fiche) n'attrape ce
« vert trompeur ».
**6 morsures vérifiées** (restauré vert après chacune) : fiche persona `Amélie→Amelia`
(mismatch) · fiche langues `FR/EN/ES→FR/EN` · fiche `endpoint: null→endpoint: fixé` ·
**artefact** un mount `endpoint null→URL réelle` (bascule d'honnêteté ET incohérence
`endpoint_statut='a_confirmer'` — le vrai *silent green* : l'endpoint fabriqué pendant
que la fiche promet `null`) · fiche retire le bloc attributs (claim absent =
INTROUVABLE) · **artefact** un mount `persona Amélie→Nadia` (montage désynchronisé du
MANIFEST). Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`,
interdit absolu) · **7 gates re-verts** (`check_readme_claims`/`check_artifacts`/
`check_docs`/`guard_constraints`/`check_regression`/`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé sous le
bloc de COMPTE chat_otoia, avec renvoi au gate persona/capabilities distinct) mis à
jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; le câblage réel de l'endpoint
OTOIA + web-component reste côté agent Frontend / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_110311` · Buffer S8 · SEO — IDENTITÉ des 2 métriques data-derived (schema.org · hreflang) de la fiche agent
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6, classe « fiche agent qui restitue des attributs
data-derived, gatée uniquement en AGRÉGAT » — mémoire
[[agent-fiche-role-attrs-ungated]] ; même patron que la cellule chat_otoia de la
fiche Frontend Console traitée à la session précédente).
**Dérive silencieuse fermée** :
- `03_agents/seo/AGENT.md:25-26` — le tableau « Livrable réellement produit »
restitue DEUX sorties byte-gatées du bundle SEO à côté du compte de mots-clés
(lui déjà gaté sur README ET fiche) :
- L25 : « graphe JSON-LD : **10 nœuds** (`Organization` + 9 `Residence`) »
- L26 : « **10 pages** × FR/EN/ES + `x-default` »
- Les blocs SEO schema.org (`SEO_SG_RD`) et hreflang (`SEO_HL_RD`) amont ne lisent
QUE `seo/README.md` — JAMAIS la fiche. `SEO_FI` n'était utilisé que par le bloc
de COMPTE de mots-clés. Ces deux cellules étaient donc des surfaces data-derived
ungated : RENOMMER le `@type` des listings, AJOUTER/RETIRER un projet (→ N nœuds
& N pages dérivent) ou ÉCHANGER une langue de contenu périme la fiche pendant que
les trois artefacts byte-gatés (`seo_schema_org.json`/`MANIFEST.json` et
`seo_hreflang.json`) disent autre chose — « vert trompeur » qu'aucune suite
`tests/` (FONCTIONS du builder, jamais la prose de la fiche) n'attrape.
- **État courant** : aucune métrique périmée — 10 nœuds (1 `Organization` + 9
`Residence`) · 10 pages · langues `fr/en/es` + `x-default` (anti-invention #6,
rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « SEO · FICHE AGENT » inséré
après le bloc hreflang README, réutilise les vérités déjà recomputées par les blocs
schema.org/hreflang amont — zéro duplication #6) :
- L25 (regex `graphe JSON-LD : \*\*(\d+) nœuds\*\* \(`<racine>` \+ (\d+)
`<listing>`\)`) : total == `schema_org_nodes` · racine == `@type` singleton du
graphe · M == `listings` · type listing == `@type` uniforme des listings ·
cohérence arithmétique `total == 1 + M`.
- L26 (regex `\*\*(\d+) pages\*\* × ([A-Z/]+) \+ `x-default``) : `|pages|` == carte
hreflang · jeu de langues (lower) == langues de contenu de l'artefact (set-diff :
absent ET en trop).
- Un claim absent échoue AUSSI (INTROUVABLE = régression #6).
**8 morsures vérifiées** (restauré vert après chacune) : nœuds `10→11` · listing
`Residence→Apartment` · racine `Organization→LocalBusiness` · listings `9→8`
(casse l'arithmétique ET la bijection) · pages `10→12` · langue `es→pt` · retrait
`es` (`FR/EN`) · retrait du claim L25 (INTROUVABLE). Working tree byte-restauré
(`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_artifacts`/`check_docs`/`guard_constraints`/
`check_regression`/`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé « Fiche
agent SEO — 2 métriques data-derived » après le paragraphe hreflang) mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; l'injection réelle des
balises hreflang/JSON-LD dans les pages reste côté agent Frontend/SEO / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_113314` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §4 — colonne « Entité » de la « Cartographie rôles ↔ entités » ancrée sur CLAUDE.md §Entités rbac_50_roles.json
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6, double classe « ANCRAGE à CLAUDE.md »
([[claude-md-constant-anchor-gate]]) **et** « identité d'ensemble recomputée du
contrat RBAC » [[rbac-spec-portail-count-gate]]). Même patron que les entités CONFOTUR
`entite_porteuse` (session 053228, §Entités) et les comptes SPEC §3/§7 — appliqué à
une **section encore ungated de la MÊME SPEC** : la table §4.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` §4 « Cartographie rôles ↔ entités » —
la **colonne de tête** liste les entités porteuses (`entite_principale`) : les **7
entités NOMMÉES** du mandat (`WAF` (holding) · `WA SRL` · `AC Arias Cuevas` ·
`Consortium ECR DR` (paymaster RH) · `Helios RD` (marque publique) · `Ploutos`
(patrimoine/monétaire) · `9060 QC` (World Activities Cdn.)) + le périmètre consolidé
« **Groupe** (consolidé) ».
- **Deux sources faisant autorité** : (i) **CLAUDE.md §Entités** — l'orthographe + le
descripteur parenthétique de chaque entité nommée ; (ii) `rbac_50_roles.json`
(byte-gaté via les 3 MANIFEST) — l'ENSEMBLE des `entite_principale` réellement portées
par les 50 rôles (distinct = 11 Helios RD · 11 WA SRL · 10 AC Arias Cuevas · 8 Groupe ·
5 9060 QC · 3 WAF · 1 Ploutos · 1 Consortium ECR DR = 50).
- Piège : le §3 gate les comptes par **portail**, le §7 les comptes du run-book, les
blocs fiches l'`entite` d'**UN** rôle ; tous **aveugles à l'identité de CETTE liste**.
AJOUTER au contrat un rôle porté par une entité absente de §4 (menu périmé), RENOMMER
une entité dans CLAUDE.md §Entités (`9060 QC→9061 QC`) ou DÉRIVER un descripteur laisse
la SPEC contredire le mandat/le contrat EN SILENCE — « vert trompeur » qu'aucune suite
`tests/` (FONCTIONS RBAC, jamais la prose de la SPEC) n'attrape.
- **État courant** : aucune ligne périmée — §4 == distinct contrat (8 entités) · les 7
descripteurs == CLAUDE.md §Entités · aucune entité décorative (anti-invention #6, rien
à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc `1sexies` après le §7, réutilise
`src_roles`/`spec_rd`/`SPEC_RD` déjà en scope · extraction fraîche de CLAUDE.md §Entités
— `_nfc` défini plus bas, non requis, entités ASCII) :
- **(a) ENSEMBLE** — entités de la colonne §4 (`| **Entité** (desc)? |`) == distinct
`entite_principale` du contrat (set-diff : `manquantes` = portées mais hors §4 ·
`fantômes` = listées mais jamais portées).
- **(b) ANCRAGE** — chaque entité NOMMÉE de CLAUDE.md §Entités (tokens en gras) est une
ligne §4 ET son descripteur parenthétique == CLAUDE.md **EXACT** (absence comprise :
`WA SRL`/`AC Arias Cuevas` = sans descripteur des deux côtés).
- **(c)** chaque entité nommée du mandat est PORTÉE ≥1× par le contrat (`cm_ents ⊆
contrat`, aucune décorative).
- Section `## 4.` INTROUVABLE = claim absent = régression #6.
**8 morsures vérifiées** (4 côté contrat/CLAUDE.md · 4 côté SPEC) : contrat +rôle porté
par entité `Fantasia` absente de §4 (`(a)` manquante) · contrat `Ploutos→Ploutos SRL`
sur tous les rôles (`(a)` fantôme+manquante · `(c)` `Ploutos` décorative) · CLAUDE.md
`9060 QC→9061 QC` (`(b)` absent · `(c)` décorative) · CLAUDE.md descripteur
`(holding)→(holding co)` (`(b)` mismatch) · SPEC §4 `AC Arias Cuevas→AC Arias` (`(a)`
fantôme+manquante · `(b)` absent) · SPEC §4 descripteur `marque publique→marque privée`
(`(b)` mismatch) · SPEC §4 retire la ligne « Groupe » (`(a)` manquante) · SPEC supprime
l'en-tête `## 4.` (INTROUVABLE) ; restauré = green : 9 ✓ (`(a)` 8 entités · `(b)` 7
descripteurs · `(c)` 7 portées) · exit 0. Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts** (`check_readme_claims`/
`check_artifacts`/`check_docs`/`guard_constraints`/`check_regression`/
`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la ligne récap du pipeline + paragraphe détaillé « Colonne
Entité de la §4 ancrée sur CLAUDE.md §Entités ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel des
rôles/entités `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_120319` · Buffer S8 · Frontend Console/AGENT.md — IDENTITÉ de l'ENSEMBLE des portails métier + exclusion nommée de la console technique
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6 — fiche-agent qui restitue une donnée gatée seulement
en AGRÉGAT/COMPTE, cf. [[agent-fiche-role-attrs-ungated]]). Même patron que
l'énumération ordonnée des onglets Mobile (`role_navigation`) et la colonne « Entité »
de la SPEC §4 ([[rbac-spec-portail-count-gate]]) — appliqué à une surface encore ungated
de la fiche **Frontend Console**.
**Dérive silencieuse fermée** :
- `03_agents/frontend_console/AGENT.md` restitue à la main, en **deux endroits**
data-derived, l'IDENTITÉ de l'ensemble des portails — jamais gatée hors le COMPTE :
- **L11** « **5 portails rôle métier** (Ventes / Construction / Achat / Compta /
Direction) » — l'ENSEMBLE ORDONNÉ des noms courts.
- **L46** « la console technique `plateforme` (**6ᵉ portail RBAC**) est **exclue** »
— l'identité + le rang + la non-appartenance au métier de la console exclue.
- **Deux artefacts faisant autorité** : (i) `frontend/portails/out/MANIFEST.json`
(byte-gaté) — les workspaces métier (`OTO Ventes` … → noms courts sans le préfixe
« OTO ») ; (ii) `rbac_50_roles.json` — `portails_business` (l'ordre du contrat) + les
portails distincts des rôles (6 : 5 métier + `plateforme`).
- Piège : le bloc « triplets par workspace » recompute cartes/liens/rôles **par**
portail mais reste **aveugle à QUELS portails** composent l'ensemble ; les autres
gates comptent « 5 » sans jamais confronter le SET. Un ÉCHANGE (Achat→Marketing),
RETRAIT, RÉORDRE, RENOMMAGE de la console technique dans le contrat
(`plateforme`→`console`) ou un contrat qui PROMEUT `plateforme` en métier laisse la
fiche contredire les artefacts EN SILENCE — « vert trompeur » qu'aucune suite
`tests/` (FONCTIONS RBAC, jamais la prose) n'attrape.
- **État courant** : aucune dérive — L11 == workspaces frontend == `portails_business` ·
L46 `plateforme` == unique portail RBAC hors métier == 6ᵉ. Le défaut est la **surface
ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Portails ensemble » juste après le
bloc triplets, réutilise `ws_list`/`ws_fi`/`WS_FI` déjà en scope · relit le contrat
`rbac_50_roles.json`) :
- **(a) ENSEMBLE** — noms courts ordonnés (label `MANIFEST.workspaces[].workspace` sans
« OTO ») == énumération L11 (compte + absents/en trop + ordre) **ET DOUBLE ANCRAGE**
les ids workspaces frontend == `portails_business` du contrat (l'« égalité stricte »
que la fiche revendique en L47).
- **(b) EXCLUSION** — `plateforme` == l'UNIQUE portail RBAC hors métier
(`distinct(roles.portail) portails_business`, len==1), nommé EXACTEMENT dans L46,
de rang == `|distinct|` (6ᵉ), avec `**exclue**` structurellement exigé (regex `\S*`
absorbe l'exposant « ᵉ » multi-octets sans le coder en dur).
- Claim absent (L11 ou L46 INTROUVABLE) = régression #6.
**8 morsures vérifiées** (6 côté fiche · 2 côté contrat) : L11 `Achat→Marketing`
(`(a)` en trop/absent) · L11 retire `Compta` (`(a)` compte+absent) · L11 réordonne
`Ventes↔Construction` (`(a)` ordre) · L46 `plateforme→console` (`(b)` nom) · L46
`6ᵉ→7ᵉ` (`(b)` rang) · L46 retire `**exclue**` (INTROUVABLE) · contrat réordonne
`portails_business` (ancrage rompu) · contrat `ventes→sales` dans `portails_business`
(ancrage rompu + `plateforme` n'est plus l'unique hors-métier ⇒ 2 fails) ; restauré =
green (3 ✓ : double ancrage · énumération L11 · console technique L46) · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) ·
**7 gates re-verts** (`check_readme_claims`/`check_artifacts`/`check_docs`/
`guard_constraints`/`check_regression`/`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la ligne récap du pipeline + paragraphe détaillé « Ensemble
des portails métier + exclusion nommée de la console technique ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel des
Workspaces/rôles `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
---
## Sprint 8 · buffer · Gate d'IDENTITÉ de la colonne « Entrée CLI » des fiches agents — verbes documentés == subparsers argparse réels
**Classe fermée** : `agent-fiche-role-attrs-ungated` (mémoire) — sous-classe « colonne
CLI ». Chaque table de livrables des fiches `03_agents/*/AGENT.md` porte une colonne
« Entrée CLI » citant, entre backticks, le point d'entrée du module ET son jeu de
verbes (`publiciste.py parse|validate|generate|run`, `faisabilite_gen.py
score|scaffold|generate|batch`, `rbac_apply_plan.py build|validate`, …). Cette liste
est **saisie à la main**. Les gates amont couvrent déjà génériquement — pour TOUTES les
fiches, via `git ls-files` — la colonne « Tests » (recomputée depuis `regression_plan`)
et la colonne « Job CI » (contre `ci.yml`), mais **RIEN** ne vérifiait que les VERBES
écrits sont ceux que le CLI **expose réellement**.
**Piège** : renommer une sous-commande (`generate`→`render`), en AJOUTER une
(`publish`), en RETIRER une ou les RÉORDONNER laisse la fiche documenter une surface
d'invocation **fantôme** : le module tourne (le CI appelle le vrai verbe), la doc
d'identité de l'agent — celle qui lui dit COMMENT lancer son propre livrable — ment en
silence. Aucune suite `tests/` (FONCTIONS du module, jamais la prose de fiche) ne
l'attrape. Classe explicitement notée en mémoire.
**Gate ajoutée** (`ci/check_readme_claims.sh`, bloc « colonne Entrée CLI », après le
bloc « Job CI fiches ») : `_cli_subcommands()` **RECOMPUTE** via **AST** l'ensemble
ORDONNÉ des `subparsers.add_parser("<verbe>")` de chaque script (jamais une liste à la
main · #6). Résolution du script : chemin du module de la MÊME ligne de table (lien
`…/05_deliverables_mvp/<path>/README.md`) + basename du CLI → `{D}/<path>/<file>.py`
(chaque cellule liée à SON module · pas de collision de basename). Égalité EXIGÉE
**exacte** — ensemble ET ordre (l'ordre de `add_parser` == ordre de l'aide argparse).
Les `|` séparateurs markdown sont échappés `\|` dans les cellules : dé-échappés avant
découpe. Direction fiche→CLI : la fiche ne peut pas mentir sur les verbes du script.
**16 cellules** gatées (6 fiches : crm×3 · devops · erpnext_backend×5 · faisabilite×2 ·
publiciste · qa×4 — dont `audit_5d_gen.py`/`audit_4big_gen.py` que les regex `[a-z_]`
manquaient, d'où `[a-z0-9_]`). Actuellement **toutes exactes** — la gate verrouille
l'état correct.
**7 morsures vérifiées** (restaurées vert après chaque) :
1. FICHE renomme verbe (`generate→render`) → manquants/en trop, `seul_ordre_diffère=False`.
2. FICHE ajoute verbe fantôme (`publish`) → en trop.
3. FICHE retire un verbe (`batch`) → manquant.
4. FICHE réordonne (`parse|validate`↔) → `seul_ordre_diffère=True` (ordre discriminé).
5. **Le vrai « vert trompeur »** : `add_parser("generate")→("render")` dans le `.py`,
fiche INCHANGÉE → red (la source de vérité a bougé sous la fiche).
6. Nouveau `add_parser("audit")` dans le `.py` non documenté → manquant côté fiche.
7. Script déplacé/renommé (`publiciste.py→publiciste_v2.py`) → « script INTROUVABLE ».
**Incident process** : un `git checkout .` de nettoyage d'un scénario adverse a reverté
l'edit **non-commité** de `ci/check_readme_claims.sh` (bloc effacé). Ré-appliqué à
l'identique puis re-vérifié. Leçon : ne jamais `git checkout .`/`--` large avec un edit
de gate non commité en cours ; restaurer par chemin ciblé uniquement. (`git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->
reste l'interdit absolu — non utilisé.)
- **6 gates re-verts** : `check_artifacts` · `check_docs` · `guard_constraints` ·
`check_readme_claims` (16 ✓ Entrée CLI) · `check_regression` · `check_ci_integrity`.
Aucun hash-lock sur les scripts de gate → rien d'autre à régénérer.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ·
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
- **Auto-score 4Big** : 96/100.
## Session `20260801_130325` · Buffer S8 · Fiches agents + master prompt/infra GPU : l'**IDENTITÉ de l'endpoint RunPod ComfyUI** — l'ID du pod `t68akvx80wv092` + le tag modèle **« Flux dev fp8 »** qui exécute le rendu Flux — était RECOPIÉE À LA MAIN en **6 citations** (`03_agents/bim/AGENT.md:32,67` · `03_agents/rendu/AGENT.md:19,55,91` · `02_master_prompt/MASTER_PROMPT_DTP_v2.md:24`) mais **HORS de tout gate**. La source de vérité in-repo est le bullet §1 de `AGENTS_EXISTING_ASSETS.md:15` — « le fichier maître » que les fiches CITENT explicitement comme leur ancre.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même classe de « vert
trompeur » que la colonne « Job CI » ou « Entrée CLI » des fiches (surface data-derived
recopiée à la main, jamais confrontée à sa source) — appliquée à une **surface distincte
encore jamais gatée** : l'**infra GPU** (l'endpoint RunPod que l'agent Rendu/BIM câble
pour le batch Flux). Classe `agent-fiche-role-attrs-ungated`.
**Dérive silencieuse fermée** :
- `03_agents/bim/AGENT.md` (table modules L32 + coordination L67) · `03_agents/rendu/AGENT.md`
(principe L19 + table L55 + coordination L91) · `02_master_prompt/MASTER_PROMPT_DTP_v2.md:24`
(bloc infra « GPU ») — chacun NOMME entre backticks l'id `t68akvx80wv092` et/ou le tag
« Flux dev fp8 ».
- Source faisant autorité : `AGENTS_EXISTING_ASSETS.md:15` §1 BIM
(« - **RunPod ComfyUI** endpoint `t68akvx80wv092` (Flux dev fp8) »).
- Piège : un pod RunPod est **RE-CRÉÉ avec un NOUVEL id** (cas le plus courant) ou le
modèle Flux est bumpé (`dev fp8`→autre quant) : l'inventaire maître se met à jour, les
6 citations se PÉRIMENT en silence pendant que l'agent Rendu/BIM câblerait le batch vers
un **endpoint GPU MORT** — l'invention même que #6 interdit. `check_docs` ne voit qu'un
lien, `guard_constraints` qu'un terme interdit, `check_artifacts` qu'un `out/` — un ID de
pod n'est aucun des trois ; aucune suite `tests/` (FONCTIONS du module) n'attrape la prose.
- **État courant** : **aucune citation périmée** — les 6 recoupent l'inventaire exactement
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « RunPod endpoint » avant
`sys.exit`) : (0) **ancrage** — id + tag recomputés du bullet §1 (garde : bullet EXACTEMENT
une fois, sinon ancre morte) ; (1) **direction fiche→inventaire** — toute fiche/master
prompt qui MENTIONNE RunPod doit citer l'id EXACT (tout token backtické de forme pod —
alphanum minuscule ≥12 **avec un chiffre**, forme qui n'entre en collision avec aucun autre
token backtické du corpus : vérifié) == canonique, ET le tag Flux EXACT (substring
normalisé, insensible casse) ; (2) **disparition totale** — plus aucune mention RunPod
échoue AUSSI (traçabilité #6).
**6 morsures vérifiées** : inventaire re-crée le pod (id change → mord les **3 fiches** en
cascade) · inventaire bump le tag (`dev fp8→schnell fp8` → 3 fiches) · fiche rendu mistype
une citation (id fantôme, mord rendu seul) · fiche bim retire le tag (tag absent) · master
prompt retire l'id backtické (citation incomplète) · inventaire supprime le bullet canonique
(ancre morte, recompute impossible) ; restauré = green : 3 fiches · id == inventaire · tag ==
inventaire · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`,
interdit absolu) · **7 gates re-verts** (`check_readme_claims`/`check_docs`/`guard_constraints`/
`check_artifacts`/`check_ci_integrity`/`validate_json`/`check_regression`).
- `ci/README.md` (clause récap du pipeline `check-readme-claims` + paragraphe détaillé
« IDENTITÉ de l'endpoint GPU RunPod ComfyUI ancrée sur l'inventaire maître ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; édition
**hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le pod RunPod réel + le
batch ComfyUI Flux restent côté VPS/RunPod).
- **Auto-score 4Big** : 96/100.
## Session `20260801_133334` · Buffer S8 · Domaine CRM/Commissions : la **PROMESSE ANTI-INVENTION (#6)** du barème vendeurs — la **RAISON D'ÊTRE** du module — était HORS de tout gate. Aucun taux de commission n'étant documenté dans CLAUDE.md, le README §« Anti-invention » promet (README:22-23) que « le barème livré porte `taux_pct: null` + `source: null` + `a_confirmer: true` pour **chaque** évènement », et l'invariant CLI #7 REFUSE tout `taux_pct` fourni **sans `source`**. Le bloc cross-cohérence commissions existant ne gate QUE brouillons/champs de base ; le bloc racine QUE le compte de tests (« 25 »). RIEN ne confrontait cette promesse à l'artefact byte-gaté.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même surface `crm/commissions`
que le bloc cross-cohérence (états soumis/base_field) mais **axe orthogonal et plus
grave** : non plus QUEL état paie, mais la garantie que le module **n'invente aucun
chiffre** — la classe même que #6 pose, appliquée au livrable dont c'est la mission.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/commissions/README.md:22-23` — la promesse « `taux_pct:
null` + `source: null` + `a_confirmer: true` pour **chaque** évènement ».
- Source faisant autorité : `out/commission_plan.json` (byte-gaté par
`check_artifacts`) — chaque `evenements[]` porte `taux_pct`/`source`/`a_confirmer` ;
`out/MANIFEST.json[counts]` recompte `taux_a_confirmer`/`evenements`.
- Piège #6, la dérive la plus dommageable possible ici : qu'un `taux_pct` DEVIENNE
non-null (taux INVENTÉ, ou valeur remplie **sans `source`**) pendant que le README
affirme encore « tout `null` » → le calcul `base × taux` (`commlib/finance.py`)
émettrait une commission fondée sur un chiffre inventé — l'invention EXACTE que le
module promet d'écarter. Aucune suite `tests/` (FONCTIONS de calcul/résolution,
jamais la promesse du README vs l'artefact commité) ne l'attrape.
- **État courant** : les 5 évènements sont bien tout-`null` (`taux_pct`/`source`
null · `a_confirmer` true), `counts.taux_a_confirmer == 5 == |plan|`, `note_taux`
cohérente ; rien de périmé (anti-invention #6, rien à réécrire). Le défaut est la
**surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM/Commissions ·
ANTI-INVENTION (#6) » après le bloc cross-cohérence commissions) : on RECOMPUTE
l'état null/confirmé depuis l'artefact, puis (a) **invariant #7 permanent** — aucun
`taux_pct` renseigné **sans `source`** (taux inventé) ; (b) **cohérence** `a_confirmer`
⟺ `taux_pct is null` (un taux confirmé n'est plus « à confirmer », et inversement) ;
(c) **`MANIFEST.counts`** recoupe le plan (`taux_a_confirmer` == nb à confirmer ·
`evenements` == |plan|) ; (d) **la promesse #6 du README REFLÈTE l'artefact dans les
DEUX SENS** — si tout-null, la promesse DOIT être présente (sinon la raison d'être
disparaît · #6) ; si un taux est un jour confirmé, la promesse « tout null » DOIT
avoir été retirée (sinon « vert trompeur ») — **zéro faux négatif** quand la
Direction remplira les taux ; (e) `note_taux` du MANIFEST cohérente avec l'état.
**5 morsures vérifiées** : `taux_pct` rempli SANS source (invariant #7 · invention
+ cascade b/d) · `taux_pct` rempli AVEC source (invariant #7 OK mais `a_confirmer`
resté true → b, + README périmé → d — prouve que #7 et la cohérence sont distincts) ·
`a_confirmer=false` sur taux null (b + counts périmés c + d) · `MANIFEST.counts.
taux_a_confirmer` périmé 5→4 (c seul) · promesse anti-invention retirée du README
(d · INTROUVABLE) ; restauré = green : invariant #7 · cohérence · counts · promesse
== artefact · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS**
`git clean`, interdit absolu) · **7 gates re-verts** (`check_readme_claims`/
`check_ci_integrity`/`check_docs`/`guard_constraints`/`check_artifacts`/
`check_regression`/`validate_json`).
- `ci/README.md` (cellule récap du pipeline + paragraphe détaillé « promesse
anti-invention #6 du barème `crm/commissions` ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la
confirmation réelle des taux + le câblage du calcul restent côté Direction/VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_140334` · Buffer S8 · Domaine Fiscal/e-CF DGII : l'**IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION** — la section §Cross-cohérence présentée comme **« le cœur du livrable »** (README:40-59) — était transcrite À LA MAIN SANS aucun gate d'identité. Le SEUL gate fiscal existant couvre le **contrat de format e-NCF** (`E+tipo(2)+seq(10)=13`) + le compte de tests ; le bloc racine, le seul agrégat « e-CF 39 tests ». RIEN ne confrontait à l'artefact byte-gaté QUEL évènement du pipeline émet un e-CF, sur quel `base_field`, par quel rôle Compta, ni la FormaPago défaut.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même classe « identité
d'un ENSEMBLE » que la colonne « Entité » de la SPEC §4, l'énumération des onglets
Mobile ou les transitions à séparation des pouvoirs du workflow — appliquée à la
**surface data-derived la plus critique du module fiscal** : les `emission_events`
d'`ecf_plan.json`, jamais gatés hors le format e-NCF et le compte de tests.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/fiscal/ecf_dgii/README.md:40-59` (§Cross-cohérence, « le cœur
du livrable ») — transcrit À LA MAIN : (L48) l'ENSEMBLE des évènements émetteurs
(« seulement réservation et contrat ») + l'ENSEMBLE exclu des brouillons (« on ne
facture pas un brouillon (lead/visite/devis) ») ; (L50) l'ENSEMBLE des `base_field`
(`montant_reservation`/`montant_contrat`) ; (L53-54) le rôle UNIQUE
(`compta-fiscaliste-ecf` · « OTO Compta Fiscaliste eCF » · portail compta) ;
(L59) la FormaPago défaut (« = 3 (Tarjeta) » · Cardnet #10).
- Source faisant autorité (byte-gatée par `check_artifacts`) :
`out/ecf_plan.json` — `emission_events[]` (`update_value`/`base_field`/`role_id`/
`erpnext_role_name`), `forma_pago_defaut` + catalogue `formas_pago`. Double
ancrage du rôle sur `rbac/rbac_50_roles.json` (id → `portail` compta → nom ERPNext).
- Piège #6/#5, la dérive la plus dommageable ici : **AJOUTER un `emission_event` sur
un état BROUILLON** (ex. `devis`) — l'anti-pattern EXACT que le module interdit
(« on ne facture pas un brouillon ») — pendant que L48 dit encore « seulement
réservation et contrat » → l'agent ERPNext Backend câblerait une **facture émise
avant soumission/encaissement**. Aucune suite `tests/` (FONCTIONS de résolution/
composition, jamais la prose vs l'artefact) ne l'attrape.
- **État courant** : émetteurs `{contrat, reservation}`, base_field
`{montant_contrat, montant_reservation}`, rôle unique `compta-fiscaliste-ecf`
(portail compta · RBAC concordant), FormaPago défaut `3`/Tarjeta/Cardnet —
tout recoupe l'artefact EXACTEMENT (anti-invention #6, rien à réécrire). Le
défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiscal · e-CF DGII —
IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION » avant `sys.exit`) : (0) **cohérence interne** —
rôle UNIQUE sur tous les évènements · défaut ∈ catalogue FormaPago ; (1) **ENSEMBLE
émetteurs** — set-diff README `seulement …` == `update_value` de l'artefact (ni
fantôme ni manquant · accents/casse normalisés) ; (2) **DISJONCTION** — l'ensemble
exclu `(lead/visite/devis)` ∩ émetteurs = ∅ (jamais de facture sur brouillon — mord
un `devis` émetteur ET la self-contradiction du README) ; (3) **ENSEMBLE `base_field`**
— set-diff README == artefact ; (4) **rôle unique** cité backtické + nom ERPNext,
**double-ancré** sur `rbac_50_roles` (id → portail compta → nom concordant) ; (5)
**FormaPago défaut** — code == artefact · label ∈ catalogue · motif cite Cardnet (#10).
Un claim absent échoue AUSSI (traçabilité #6).
**6 morsures vérifiées** : artefact facture un BROUILLON (`devis` émetteur → mismatch
d'ensemble **ET** clash de disjonction · 2 morsures en une) · artefact renomme un
`base_field` (`montant_reservation→montant_acompte`) · artefact change FormaPago défaut
`3→1` (Efectivo) · artefact casse le rôle unique (2ᵉ évènement → `compta-comptable`) ·
README périmé « seulement contrat » (réservation retiré) · motif du défaut ne cite
plus « Cardnet » (#10) ; restauré = green : émetteurs · disjonction · base_field ·
rôle == RBAC · FormaPago == artefact · exit 0. Working tree byte-restauré
(`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_docs`/`guard_constraints`/`check_artifacts`/
`check_ci_integrity`/`validate_json`/`check_regression`).
- `ci/README.md` (clause récap du pipeline `check-readme-claims` + paragraphe détaillé
« IDENTITÉ des évènements d'émission e-CF ancrée sur `ecf_plan.json` ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la connexion
Compupar réelle + l'émission e-CF en production restent côté ERPNext Backend/VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_143341` · Buffer S8 · Domaine Faisabilité/generator : les **TROIS copies aval** des 6 MARQUEURS CANONIQUES (`3 %`/`8.5 %`/`52 %` · `USD+DOP`/`Letter US`/`Cardnet`, contraintes NON-NÉGOCIABLES **CLAUDE.md #9/#10**) — l'**oracle d'enforcement** `genlib/renderer.py::_CANONICAL_MARKERS` (la liste que `check_canonical()` PARCOURT pour PROUVER que chaque rendu de faisabilité porte les 6 marqueurs), les **DEUX docstrings** de `genlib/scorer.py` (barème axe 4 + `_score_parametres_canoniques`) et l'**oracle du test** `tests/test_generator.py` (`for marker in [...]`) — étaient **HORS de tout ancrage**. Le bloc « paramètres canoniques » existant n'ancre QUE `model.py::CANONICAL` + le README à #9/#10 ; son propre commentaire NOMMAIT pourtant le défaut (« ces marqueurs sont HARDCODÉS DANS LE TEST, une copie de plus, JAMAIS comparée à CLAUDE.md ») sans le fermer.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe
[[claude-md-constant-anchor-gate]]). **Même patron EXACT** que le tuple d'oracle du
test `publiciste/branding` ancré sur #4 (une copie hardcodée dans un test, gatée) —
appliqué aux **trois copies aval encore ungated du MÊME module** `faisabilite/generator`
dont le bloc canonique n'ancrait que 2 des 5 copies.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/faisabilite/generator/genlib/renderer.py:275` —
`_CANONICAL_MARKERS = ["3 %", "8.5 %", "52 %", "USD + DOP", "Cardnet", "Letter US"]`,
la LISTE que `check_canonical()` parcourt pour vérifier que le volet ingénierie
du rendu porte les 6 marqueurs. C'est l'**enforcement même** de l'« axe 4
honnêteté ».
- `05_deliverables_mvp/faisabilite/generator/genlib/scorer.py:7` (barème axe 4) **et**
`:109` (`_score_parametres_canoniques`) — deux docstrings énumérant
« 3 % / 8.5 % / 52 % · USD+DOP · Letter US · Cardnet ».
- `05_deliverables_mvp/faisabilite/generator/tests/test_generator.py:103` —
`for marker in ["3 %", "8.5 %", "52 %", "USD + DOP", "Cardnet", "Letter US"]` :
l'oracle du test lui-même, celui que le commentaire du bloc amont dénonçait.
- Source faisant autorité : **CLAUDE.md #9/#10** (les 6 valeurs), déjà recomputées
en `exp` par le bloc « paramètres canoniques » — RÉUTILISÉES ici (zéro duplication).
- Piège #6 : l'oracle `_CANONICAL_MARKERS` est le juge de l'honnêteté du rendu. S'il
DÉRIVE de `CANONICAL` (donc de #9/#10), le contrôle devient FAUX de deux façons :
soit il exige un marqueur périmé (`Cardnet` alors que le rendu, issu d'un CANONICAL
mis à jour, porterait `Azul` → **faux rouge**), soit on retire un marqueur et le
contrôle **NE VÉRIFIE PLUS RIEN** (vert vacux). La seule suite `tests/` teste des
FONCTIONS (dont `check_canonical` — AVEC son propre oracle hardcodé), jamais
l'ancrage à CLAUDE.md.
- **État courant** : **aucun marqueur périmé** — `set(renderer._CANONICAL_MARKERS) ==
set(model.CANONICAL.values())` vérifié programmatiquement AVANT toute édition ; les
3 copies recoupent #9/#10 exactement (anti-invention #6, rien à réécrire). Le défaut
est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Faisabilité/generator
(2e surface) » après le bloc « paramètres canoniques ») : RÉUTILISE `exp` +
`_want6` (6 valeurs normalisées de #9/#10, zéro duplication). Helper `_list_vs_want6`
pour les copies-LISTES : (c) `renderer.py::_CANONICAL_MARKERS` et (e) l'oracle du test
sont parsés (`ast.literal_eval`) puis comparés à l'ensemble EXACT (set-diff : absent
ET en trop · anti-doublon). Pour les copies-DOCSTRINGS (d), chaque valeur normalisée
(`_pnorm`/`_ai` — espaces/accents-insensibles ⇒ `USD+DOP`==`USD + DOP`) doit être
substring de la ligne normalisée ; ≥2 énumérations exigées (barème + fonction). Un
claim/ligne absent échoue AUSSI (traçabilité #6).
**4 morsures vérifiées** : (M1) `renderer._CANONICAL_MARKERS` `Cardnet→Azul`
(absents=[cardnet] · en trop=[azul]) · (M2) oracle du test retire `8.5 %`
(absents=[8.5%]) · (M3) scorer docstring L7 `52 %→55 %` (n'énumère plus [52%]) ·
(M4) **CLAUDE.md #10 `Cardnet→Azul` → mord SIMULTANÉMENT model.py + renderer + test +
les DEUX docstrings** (5 morsures en cascade — la valeur MÊME de l'ancrage à la
source constitutionnelle) ; restauré = green : renderer/test == #9/#10 (6 marqueurs) ·
2 docstrings == #9/#10 · exit 0. Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_artifacts`/`check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_regression`/`validate_json`).
- `ci/README.md` (clause récap du pipeline `check-readme-claims` + paragraphe détaillé
« 2ᵉ surface du MÊME module : les 3 copies aval des marqueurs canoniques ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la génération
réelle des faisabilités reste côté BIM/Faisabilité).
- **Auto-score 4Big** : 96/100.
## Session `20260801_150344` · Buffer S8 · Domaine DevOps CI/CD : gate d'**IDENTITÉ des IDENTIFIANTS D'INFRASTRUCTURE VPS** (IP `153.75.250.214` + les DEUX conteneurs Docker `erpnext-backend-1`/`erpnext-frontend-1`) ancré sur **CLAUDE.md §VPS** — surface data-derived jusqu'ici **HORS de tout ancrage**.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe
[[claude-md-constant-anchor-gate]] + [[agent-fiche-role-attrs-ungated]], surface
**infra ~ endpoint RunPod**). **Même patron EXACT** que le gate « endpoint GPU
RunPod » (session 130325 : un identifiant d'infra hand-transcrit dans N docs,
recomputé de la source in-repo faisant autorité) — appliqué ici aux identifiants
**serveur** dont la source de vérité est **constitutionnelle** (CLAUDE.md §VPS).
**Dérive silencieuse fermée** : CLAUDE.md §VPS FIXE l'IP du VPS `153.75.250.214`
et les 2 conteneurs Docker `erpnext-backend-1`/`erpnext-frontend-1`. Ces
identifiants sont RECOPIÉS À LA MAIN dans ~10 endroits, sans AUCUN ancrage :
- `README.md` (×2 · VPS hors périmètre + dépôt Gitea) · `02_master_prompt/
MASTER_PROMPT_DTP_v2.md` (IP ×2 + les 2 conteneurs) · `AGENTS_EXISTING_ASSETS.md`
(IP + 2 conteneurs) · `03_agents/mobile/AGENT.md` (`erpnext-backend-1`, backend
cible de l'API) · `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` (run-book
d'import) · `05_deliverables_mvp/GAP_ANALYSIS_SPRINT1.md` · `tests/README.md`
(base URL Playwright) · `ci/README.md` (instance Gitea).
- Aucun gate ne couvrait ce lien : `check_docs` ne voit qu'un lien Markdown,
`guard_constraints` qu'un terme interdit, `check_artifacts` qu'un `out/` — une IP
ni un nom de conteneur n'est aucun des trois. Aucune suite `tests/` (FONCTIONS
des modules, jamais la prose infra) ne l'attrape.
- Drift la plus réaliste, #6 : une **MIGRATION serveur** (nouvelle IP) ou un
**RENOMMAGE du projet compose** (`erpnext-*` → `oto-*`) — évènement ops COURANT :
CLAUDE.md §VPS se met à jour, les ~10 copies se PÉRIMENT en silence → un agent
câblerait l'API mobile / les tests e2e / le hand-off d'import vers un serveur
MORT ou un conteneur FANTÔME (l'invention même que #6 interdit).
- **État courant** : aucune copie périmée — l'unique IPv4 du corpus == canonique,
les seuls conteneurs cités ⊆ l'ensemble canonique (vérifié programmatiquement
AVANT édition). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CLAUDE.md §VPS · IDENTITÉ
des IDENTIFIANTS D'INFRASTRUCTURE » avant `sys.exit`) : RE-DÉRIVE l'IP + l'ENSEMBLE
des conteneurs de CLAUDE.md §VPS (`^## VPS` → `IP <v4>` + `Docker (…)`, jamais une
liste à la main · #6 ; garde « section/format introuvable » = ancre morte).
Direction copie→source sur `git ls-files '*.md'` (hors `CLAUDE.md` = source, hors
`05_activity_log/` + `daily_reports/` = journaux figés à ne pas réécrire) : tout
token **IPv4** DOIT == l'IP canonique ; tout token **conteneur** DOIT ∈ l'ensemble
canonique. Garde anti-évaporation (IP ET ≥1 conteneur restent cités quelque part).
Le write-path interdit `/var/www/html/static` reste du ressort de
`guard_constraints`, non de ce gate d'identité.
**Piège de conception CORRIGÉ en vérif adversariale** : 1ʳᵉ version ancrait
l'enveloppe conteneur sur le préfixe LITTÉRAL `erpnext-[a-z]+-1` → un renommage de
projet compose dans une copie (`erpnext-backend-1` → `oto-backend-1`, le drift MÊME
qu'on vise) ne matchait plus le shape et **PASSAIT en silence** (M2 vert à tort).
Correctif : l'enveloppe est DÉRIVÉE des **stems de service** canoniques
(`backend`/`frontend`) via `[a-z0-9]+-(?:backend|frontend)-1`, PAS du préfixe →
`oto-backend-1` est vu comme fantôme. Le filtre source est lui aussi générique
(`[a-z0-9]+-[a-z]+-1`) pour que M4 (renommage DANS §VPS) cascade avec un message
utile plutôt qu'un « format modifié » crude.
**5 morsures vérifiées** (mutations sur fichiers DONNÉES uniquement, restaurées par
`git checkout -- <path>` ciblé — **JAMAIS** `git clean`/`checkout` large avec le
gate décommité en cours, interdit absolu + incident process en mémoire) : (M1) IP
périmée dans `README.md` (serveur migré, copie non mise à jour) → stale · (M2)
conteneur `erpnext-backend-1→oto-backend-1` dans le master prompt (compose renommé)
→ fantôme (après correctif) · (M3) IP changée DANS CLAUDE.md §VPS → **cascade sur
les 5 docs citant l'IP** · (M4) conteneurs renommés DANS §VPS → **cascade fantôme
sur les 4 docs citant un conteneur** avec message utile · (M5) header `## VPS`
retiré → ancre morte. Restauré = green : 10 assertions VPS infra (5 IP + 5
conteneur) · exit 0. **7 gates re-verts** (`check_readme_claims`/`check_artifacts`/
`check_ci_integrity`/`check_docs`/`guard_constraints`/`check_regression`/
`validate_json`).
- `ci/README.md` (clause récap du pipeline `check-readme-claims` : sous-clause
« identité d'infrastructure VPS ») mise à jour — et désormais **elle-même couverte
par le gate** (elle gagne les tokens conteneur/IP canoniques, tous verts).
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la migration
serveur réelle + le renommage compose restent côté DevOps/VPS production).
- **Auto-score 4Big** : 96/100.
## Session `20260801_153345` · Buffer S8 · Domaine CRM/Commissions : l'**IDENTITÉ des RÔLES PAYÉS** du barème vendeurs — le « **à quel rôle** » de la question fondatrice (« quel évènement paie, à quel RÔLE, sur quel montant », README:8-9) — était HORS de tout gate. La §Cross-cohérence promet (README:51-53) que chaque `role_id` commissionné « doit être résolu depuis `rbac_50_roles.json` … **ET appartenir au portail `ventes`** » (invariant CLI #5) avec un `erpnext_role_name` « cohérent avec RBAC » (invariant #6 · le « nom Frappe résolu » du README:36). C'est une **SÉPARATION DES POUVOIRS** : on ne commissionne QUE des rôles du portail ventes. Les DEUX blocs commissions existants (cross-cohérence états/base_field + anti-invention taux) NE couvraient PAS cette appartenance-portail ni la résolution du nom.
**Tâche** : **Sprint 8 · buffer** (ERPNext Backend / CRM · #6). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive. **Troisième surface data-derived** du même
livrable `crm/commissions` (après états/base_field et anti-taux) — le troisième tiers
de la question fondatrice du module (« à quel rôle »), le seul jamais confronté à
l'artefact. Classe [[agent-fiche-role-attrs-ungated]] : une propriété-rôle
data-derived affirmée en prose mais gatée par rien.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/commissions/README.md:51-53` — « `role_id` … résolu depuis
`rbac_50_roles.json` … ET appartenir au portail `ventes` » + README:36 « nom Frappe
résolu ».
- Sources faisant autorité (toutes byte-gatées par `check_artifacts`) :
`out/commission_plan.json[evenements[].{role_id,erpnext_role_name}]` (les rôles
RÉELLEMENT payés) · `rbac/rbac_50_roles.json[roles[].{id,portail,erpnext_role_name}]`
+ `[portails_business]` (le contrat) · `out/MANIFEST.json[roles_rbac_utilises,counts.roles]`
(le récap).
- Piège #6, les dérives les plus graves ici : **(a)** commissionner un rôle d'un
AUTRE portail (payer un fiscaliste `compta` → la prose « portail `ventes` » devient
un MENSONGE, pouvoirs mélangés) ; **(b)** un `role_id` FANTÔME absent du contrat
(rôle inventé — l'invention même que #6 interdit) ; **(c)** un `erpnext_role_name`
figé en dur dans le plan qui dérive quand le contrat renomme le rôle (nom Frappe
périmé → `Has Role` importé sur un rôle INEXISTANT côté VPS). Aucune suite `tests/`
(FONCTIONS de résolution/calcul, jamais la prose vs l'artefact) ne l'attrape ; ce
n'est ni un lien (`check_docs`), ni un terme interdit (`guard_constraints`), ni un
`out/` dérivé (`check_artifacts`) → invisible aux trois.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM/Commissions ·
IDENTITÉ des RÔLES PAYÉS » après le bloc anti-invention) : RE-DÉRIVE tout des
artefacts byte-gatés (zéro dup du contrat du générateur · #6). **(a)** chaque
`role_id` du plan ∈ contrat (sinon fantôme) ; **(b)** `erpnext_role_name` du plan ==
contrat pour chaque évènement (nom Frappe résolu · invariant #6) ; **(c)** le portail
des rôles payés est un **SINGLETON** ∈ `portails_business`, ET == le portail nommé au
README (`appartenir au portail \`([a-z-]+)\`` — le littéral `ventes`) : payer un rôle
d'un autre portail casse le singleton, renommer le portail au contrat désaccorde le
littéral README ; **(d)** `MANIFEST.roles_rbac_utilises` == l'ensemble distinct
`(role_id, nom)` réellement payé + `counts.roles` recoupé.
**6 morsures vérifiées** (mutations sur fichiers DONNÉES uniquement — plan/contrat/
MANIFEST/README — restaurées par `git checkout -- <path>` **ciblé** ; **JAMAIS**
`git checkout .`/large avec le gate décommité en cours d'édition, interdit absolu +
incident process en mémoire) : (M1) `role_id` fantôme dans le plan → absent du contrat ·
(M2) `erpnext_role_name` dérivé dans le plan → ≠ contrat · (M3) rôle **`compta`**
commissionné → portails `{compta, ventes}`, séparation des pouvoirs rompue (+ cascade
MANIFEST) · (M4) littéral portail README `ventes→sales` → dit `sales` MAIS rôles du
portail `ventes` · (M5) `roles_rbac_utilises` amputé → ≠ rôles payés · (M6) renommage
du rôle DANS le contrat (`ventes-conseiller`) → cascade double (nom plan≠contrat +
MANIFEST périmé). Restauré = vert : 4 assertions vertes · exit 0. **7 gates re-verts**
(`check_readme_claims`/`check_artifacts`/`check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_regression`/`validate_json`).
- `ci/README.md` (ligne récap du pipeline `check-readme-claims` : sous-clause « rôles
payés » ; + paragraphe prose « troisième surface du même livrable `crm/commissions` »)
mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la confirmation
des taux réels + le câblage du calcul restent côté ERPNext Backend/VPS production).
- **Auto-score 4Big** : 96/100.
## Session `20260801_160354` · Buffer S8 · Domaine QA/comptes de tests · Fiches agents : CINQ **comptes de tests PAR SUITE** écrits HORS de la colonne « Tests » des tables de fiches — `seo` « **36 tests** » (`03_agents/seo/AGENT.md:31`, prose libre) · `frontend_console` « **19 tests** » (portails) & « **31 tests** » (chat_otoia) (`:32`/`:34`, prose libre) · `onapi_legal` « **44 tests** » (`:32`, prose libre) · `devops` « 29 (dont 14 injections négatives) » (`:25`, **cellule de table à suffixe**) — étaient **HORS de tout gate**. Les DEUX blocs de comptes de tests existants ne les atteignent PAS : le bloc **cellules « Tests »** (`row_re`) n'accepte qu'une ancre de lien `` `<mot>/` `` ET une cellule ENTIER NU ; le bloc **agrégats en prose** ne couvre que `crm` (Total CRM) et `erpnext_backend` (RBAC + e-CF).
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA — « Regression tests
exhaustifs »). Roadmap fonctionnellement close ; poursuite de la série anti-dérive
(CLAUDE.md #6). **Même classe EXACTE** que le bloc « comptes de tests PAR SUITE »
(cellules de table) et le bloc « agrégats EN PROSE » (Total CRM/RBAC/e-CF) — appliquée
à la **dernière poche** de la même surface : les claims per-suite que ces deux blocs
ne balaient pas par construction de leurs regex.
**Dérive silencieuse fermée** :
- `03_agents/seo/AGENT.md:31` — « **36 tests**, gated dans le pipeline » (suite `seo`).
- `03_agents/frontend_console/AGENT.md:32` — « **19 tests** · job CI
`frontend-portails-tests` » (suite `frontend/portails`) · `:34` — « **31 tests** ·
job CI `chat-otoia-tests` » (suite `frontend/chat_otoia`).
- `03_agents/onapi_legal/AGENT.md:32` — « **44 tests** (dont 8 négatifs) · job CI
`legal-confotur-tests` » (suite `legal/confotur`).
- `03_agents/devops/AGENT.md:25` — cellule « Tests » du run-book :
« 29 (dont 14 injections négatives) » (suite `devops/deploy_runbook`). Cette ligne
échappe à `row_re` **DEUX fois** : (a) l'ancre de lien est un CHEMIN complet
(`05_deliverables_mvp/devops/deploy_runbook/`, pas `` `<mot>/` ``) et (b) la cellule
n'est PAS un entier nu (suffixe « (dont …) »).
- Source faisant autorité : `qa/regression/out/regression_plan.json`
(`suites[].test_methods` · byte-gaté par `check_artifacts`), déjà chargée dans le
dict `auth` que les deux blocs amont utilisent (zéro duplication #6). Les 5 recomputes :
`seo`=36 · `frontend/portails`=19 · `frontend/chat_otoia`=31 · `legal/confotur`=44 ·
`devops/deploy_runbook`=29.
- Piège : ajouter/retirer un `def test_` à l'une de ces 5 suites fait DÉRIVER en
silence le nombre de la fiche pendant que la matrice de régression dit autre chose.
Une compensation entre deux suites (l'une +1, l'autre 1) laisserait même la SOMME
agrégée (« 564 tests · 22 suites », elle gatée) juste ET ces lignes fausses —
exactement le motif per-suite que le bloc des cellules corrige, sur des surfaces
qu'il ne balaie pas. Aucune suite `tests/` (qui teste des FONCTIONS, pas la prose
des fiches) n'attrape ce « vert trompeur ».
- **État courant** : **aucun compte périmé** — les 5 recoupent
`plan.suites[].test_methods` exactement (anti-invention #6, rien à réécrire). Le
défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches agents · comptes
de tests PAR SUITE écrits HORS de la colonne Tests » après le bloc des agrégats en
prose e-CF) : table de config `PER_SUITE_HORS_TABLE` = (fiche · suite_path · motif ·
label). Pour chaque claim : (1) on RECOMPUTE `exp = auth[suite_path]` (même source
byte-gatée déjà chargée · zéro duplication) ; (2) motif ANCRÉ sur le nom de job CI
adjacent (ou le lien du module pour devops) pour viser LA bonne suite SANS ambiguïté
(les deux claims `frontend_console` 19/31 sont désambiguïsés par leurs job CI
distincts) ; (3) claim absent ⇒ ROUGE (traçabilité #6). Garde `suite_path ∈ auth`
(anti-câblage-rompu).
**7 morsures vérifiées** : (M1) fiche seo `36→35` · (M2) fiche portails `19→20` ·
(M3) fiche onapi `44→43` · (M4) **cellule devops** `29→30` (prouve que le suffixe
« (dont …) » + ancre chemin-complet est bien couvert) · (M5) **SOURCE drift** —
`plan.suites[seo].test_methods 36→37` ⇒ fiche périmée (prouve que le gate suit
l'ARTEFACT, pas un oracle figé) · (M6) ligne prose seo retirée ⇒ INTROUVABLE
(traçabilité) · (M7) fiche chat_otoia `31→32` ⇒ mord SEULE la ligne chat_otoia sans
toucher portails (prouve la désambiguïsation par job CI). Restauré = green : 5 claims
== `plan.suites[].test_methods` · exit 0. Working tree byte-restauré
(`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_docs`/`guard_constraints`/`validate_json`/
`check_artifacts`/`check_ci_integrity`/`check_regression`).
- `ci/README.md` (clause du récap `check-readme-claims` : « comptes de tests par
suite écrits HORS de cette colonne ») mis à jour.
- **Observé, hors périmètre de CE gate** : `03_agents/devops/AGENT.md:24` cellule
« 22 suites gated » = COMPTE de suites (`plan.totals.suites`), classe DISTINCTE du
`test_methods` per-suite ; déjà ancré via l'agrégat QA « 22 suites gated · 564
tests » — candidat propre pour une session future si confirmé ungated.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; les suites
réelles tournent côté runner Gitea).
- **Auto-score 4Big** : 96/100.
## Session `20260801_163401` · Buffer S8 · Domaine QA/audit_5d : l'**IDENTITÉ des 5 DIMENSIONS** du cadre d'audit — le **libellé** (`titre`) ET la **norme de référence** (`reference`) de chacune, énumérés à la main dans la table « ## Les 5 dimensions » (`05_deliverables_mvp/qa/audit_5d/README.md:15-19`) — était **HORS de tout gate d'IDENTITÉ**. Le tout premier bloc audit_5d ne recompute que le **COMPTE** (« **5 dimensions (5D)** ») — un WILDCARD aveugle à QUELLE dimension ; les 2 autres blocs audit_5d gatent la ventilation du verdict et l'énumération des open items, jamais la carte des dimensions.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA — « Audit UAF + normes ISA/IFRS
5D »). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md
#6). **Même patron EXACT** que l'énumération des open items (2e surface du MÊME
README) ou les triplets par workspace des portails — appliqué à une **3e surface
distincte du MÊME README `qa/audit_5d`** : la table des 5 dimensions, gatée jusqu'ici
seulement sur son COMPTE agrégé.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/qa/audit_5d/README.md:15-19` — table « ## Les 5 dimensions » :
chaque ligne `| **D1** | Traçabilité & anti-invention | ISA 500 — Éléments probants |`
transcrit PAR LIGNE le triplet (`id`, `titre`, `reference`) d'une dimension.
- Source faisant autorité : `qa/audit_5d/out/audit_report.json[dimensions]`
(byte-gaté par `check_artifacts` ⇒ chaque dimension recomposée du catalogue de
contrôles à chaque build), chaque entrée portant `id` + `titre` + `reference`.
L'INPUT `audit_spec.json[dimensions]` (dont le rapport DÉRIVE) sert de cohérence
croisée.
- Piège : l'audit 5D est l'audit de **SECOND NIVEAU** que l'auditeur/banquier lit.
RENOMMER un libellé (« Traçabilité & anti-invention » → …) ou PERMUTER une norme
de référence (D3 fiscal citant la mauvaise `Ley 32-23`, D2 AML la mauvaise
`Ley 155-17`, D1 probatoire `ISA 501` au lieu d'`ISA 500`) laisse le compte à 5 —
le bloc de comptage reste AVEUGLE — pendant que l'artefact dit autre chose ⇒ une
AFFIRMATION de conformité fausse (l'invention même que #6 interdit). Aucune suite
`tests/` (qui teste des FONCTIONS de contrôle, pas la prose) n'attrape ce « vert
trompeur ».
- **État courant** : **aucune dimension périmée** — les 5 triplets recoupent
l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la
**surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Audit 5D (3e surface du
MÊME README) · IDENTITÉ des 5 DIMENSIONS » après le bloc open items) : on RECOMPUTE
la liste ORDONNÉE `{id:(titre, reference)}` depuis `audit_report.dimensions[]` (zéro
duplication du modèle de l'auditeur) et on exige que la table README l'énumère
EXACTEMENT : (1) identité par **ENSEMBLE d'`id`** (absent ET en trop mordus) ; (2)
**ORDRE** préservé (D1→D5) ; (3) `(titre, reference)` **par ligne**. Regex de ligne
`| **Dn** | … | … |` (gras + PAS de sous-point ⇒ distingue des lignes open items
`| D1.1 · … |`). Cohérence croisée bonus : `audit_report.dimensions` ==
`audit_spec.json[dimensions]` (l'INPUT dont le rapport dérive · mord un builder qui
renommerait/permuterait une dimension en aval du spec). Table disparue = échec
(traçabilité #6).
**8 morsures vérifiées** (5 côté README · 1 réordonnancement · 2 côté artefact/spec) :
README renomme D1 titre (`& anti-invention→et non-invention`) · README permute la
norme D1 (`ISA 500→ISA 501`) · README permute la norme fiscale D3 (`Ley 32-23→Ley
33-23`, affirmation de conformité fausse) · README supprime la ligne D5 (absent) ·
README réordonne D1↔D2 (ORDRE cassé) · **artefact** renomme le titre D2 (README périmé
+ divergence spec↔rapport) · **spec** renomme la référence D4 (divergence spec↔rapport)
· README supprime la table entière (INTROUVABLE, énumère `[]`) ; restauré = green :
5 dimensions `[D1..D5]` (id→titre→reference) == artefact · ordre == artefact ·
spec == rapport · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS**
`git clean`, interdit absolu) · **7 gates re-verts** (`check_readme_claims`/
`check_docs`/`guard_constraints`/`check_ci_integrity`/`check_artifacts`/
`check_regression`/`validate_json`).
- `ci/README.md` (recap `check-readme-claims` + paragraphe détaillé « 3e surface du
MÊME README de l'audit 5D · IDENTITÉ des 5 dimensions ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la confirmation
des 4 open items réglementaires reste côté métier/VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_190412` · Buffer S8 · Domaine Fiches agents/RBAC : l'**appartenance à `roles_allowed` du chat OTOIA** — QUI peut **invoquer** l'assistant OTOIA embarqué dans un portail — asserée EN PROSE par 2 fiches (`03_agents/rendu/AGENT.md:36` positive · `03_agents/mobile/AGENT.md:41` négative) était **HORS de tout gate d'appartenance**. C'est une **3e surface d'appartenance** DISTINCTE des deux déjà gatées : le **contrat** `rbac_50_roles.json` (attributs du rôle, blocs Fiches Faisabilité/Mobile) et le **`Has Role`** `workspace.json` (accès console, bloc Fiches Workspace). Le contrôle d'invocation du chat vit dans un 3e artefact — `frontend/chat_otoia/out/chat_mount.json` (`roles_allowed` par portail) — que le bloc Chat OTOIA amont ne gate que sur l'IDENTITÉ persona/capabilities + la COMPOSITION (5 mounts ⇔ 5 portails), AVEUGLE à QUEL rôle figure dans quelle `roles_allowed`.
**Tâche** : **Sprint 8 · buffer** (QA/DevOps — série anti-dérive CLAUDE.md #6, roadmap fonctionnellement close). **Même patron EXACT** que le bloc de membership `Has Role` (juste au-dessus dans `check_readme_claims.sh`) — appliqué à un **artefact + une sémantique DIFFÉRENTS** : accès console (`Has Role`) ≠ droit d'invocation du chat (`roles_allowed`).
**Dérive silencieuse fermée** :
- `03_agents/rendu/AGENT.md:36` — POSITIVE : rôle « OTO Faisabilité Rendu 3D » ∈ `roles_allowed` du portail NOMMÉ `construction` (déclenchement des rendus assistés).
- `03_agents/mobile/AGENT.md:41` — NÉGATIVE d'honnêteté (#6) : rôle « OTO Plateforme Mobile » (famille `plateforme`) dans AUCUNE `roles_allowed` (le chat ne couvre que les portails métier).
- Source faisant autorité : `frontend/chat_otoia/out/chat_mount.json[].roles_allowed` (byte-gaté par `check_artifacts`), `erpnext_role_name` résolu du contrat par `id` (zéro duplication).
- Piège : ajouter le rôle mobile à une `roles_allowed` = **SUR-EXPOSITION** (le dev mobile gagnerait le droit de piloter l'IA d'un portail métier — l'escalade même que #6 refuse) ; retirer le rôle rendu de la `roles_allowed` construction = perte du déclenchement assisté ; DÉPLACER le rôle vers un autre portail — laisse la fiche périmée EN SILENCE pendant que l'artefact dit autre chose. Aucune suite `tests/` (qui teste des FONCTIONS de montage, pas la prose d'une fiche) n'attrape ce « vert trompeur ».
- **État courant** : aucune valeur périmée — rendu ∈ construction (et lui seul), mobile ∉ tout (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches agents · appartenance à `roles_allowed` du chat OTOIA » après le bloc Fiches Workspace) : index recomputé `erpnext_role_name → {portails autorisant}`, puis (a) **in_named_portail** (rendu) — le portail nommé est data-derived (== `portail` du rôle au contrat, **triple ancrage** : nommé == contrat == unique portail autorisant), membership == {ce portail}, prose citant « invoquer le chat OTOIA » ; (b) **not_in_any** (mobile) — rôle ABSENT de TOUTE `roles_allowed`. Portail nommé fantôme (absent des mounts) = échec (anti-typo). Claim absent = échec (traçabilité #6). Invariant vérifié en amont : chaque rôle ∈ ≤1 `roles_allowed`, == son `portail` contrat.
**6 morsures vérifiées** (3 côté artefact · 3 côté fiche/bord) : artefact retire rendu de `construction` (∉ aucun portail) · artefact ajoute mobile à `ventes` (SUR-EXPOSITION #6 VIOLÉE) · artefact ajoute AUSSI rendu à `compta` (droit élargi, fiche nomme 1 seul) · fiche rendu nomme le mauvais portail (`construction→compta`, INTROUVABLE) · fiche mobile retire le token `roles_allowed` (INTROUVABLE) ; restauré = green : rendu ∈ `{construction}` · mobile ∉ tout · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts** (`validate_json`/`check_docs`/`guard_constraints`/`check_artifacts`/`check_ci_integrity`/`check_readme_claims`/`check_regression`).
- `ci/README.md` (recap `check-readme-claims` + paragraphe détaillé « 3e surface d'appartenance · `roles_allowed` du chat OTOIA ») mis à jour.
- **Note memory** : la classe « agent-fiche-role-attrs-ungated » est désormais CLOSE (attributs contrat = Fiches Faisabilité/Mobile ; `Has Role` = Fiches Workspace ; `roles_allowed` chat = ce bloc). Candidat propre restant : la fiche `ifc_speckle:34` (attributs contrat déjà gatés) + la row acceptance S3 « viewer 3D » (déjà gatée QA).
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le montage réel du chat reste côté runner/VPS).
- **Auto-score 4Big** : 96/100.
---
## Session 20260801_193414 · Sprint 8 · buffer · Gate d'IDENTITÉ des comptes de tests self-reportés par les README de MODULE (#6)
**Contexte** : la série anti-dérive Sprint-8 gate chaque surface data-derived contre sa source de vérité. Tous les blocs de **comptes de tests** existants (`ci/check_readme_claims.sh`) visent `03_agents/*/AGENT.md` : la cellule « Tests » des tables via `row_re`, les agrégats prose de `crm`/`erpnext_backend`, et les cinq claims per-suite « hors table » (seo/portails/chat_otoia/confotur/deploy_runbook). AUCUN ne touche le README du **MODULE** lui-même — pourtant `05_deliverables_mvp/<suite>/README.md` se re-déclare, en propre, le compte de SA suite à plusieurs endroits.
**Surface trouvée (ungated)** : **27 claims sur 16 READMEs de module**, sous trois formulations récurrentes :
- commentaire de la commande unittest : `discover -s tests -v # N tests (…)` ;
- bullet « Vérification en-repo » : `**N/N verts**` (six modules RBAC/faisabilité/CRM) ;
- justification d'auto-score 4Big : `Validé statiquement en-repo (N tests verts + …)`.
Ces nombres sont SAISIS À LA MAIN dans le doc d'entrée de chaque module et se périment dès qu'un `def test_` est ajouté/retiré : la source faisant autorité (`qa/regression/out/regression_plan.json` → `auth = plan.suites[path].test_methods`, recomptée à chaque build par découverte réelle, byte-gatée par `check_artifacts` + fraîcheur du run par `check_regression`) se met à jour tandis que la prose du README rote EN SILENCE — « vert trompeur » de la MÊME classe que la cellule « Tests » des fiches, mais sur la surface que `row_re` n'atteint pas (il ne lit le README de module QUE comme *cible de lien*). Aucune suite `tests/` (qui teste des FONCTIONS de génération, jamais la prose du README) n'attrape ce périmé.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « READMEs de MODULE · comptes de tests self-reportés » juste après le bloc per-suite « hors table », d'où `auth` est déjà en portée · zéro rechargement · #6) : table de config `MODULE_README_TESTS` = liste `(suite_path, [motifs ancrés])`. Pour chaque module : `auth[suite_path]` recomputé, chaque motif DOIT matcher (claim absent = régression #6) ET tous ses groupes capturés == `auth`. Le motif `**N/N verts**` capture DEUX groupes → les deux doivent valoir `auth` : un `25/24 verts` internement incohérent mord aussi. Chaque motif est ancré sur un contexte distinctif (`discover -s tests…# N tests`, `**N/N verts**`, `en-repo (N tests verts`, `test_confotur.py # N tests`…) pour viser LE compte de CETTE suite sans capter un autre nombre.
- **Cinq modules volontairement HORS table** (`frontend/chat_otoia`, `mobile/app_config`, `publiciste`, `qa/acceptance`, `qa/audit_4big`) : leur README ne self-reporte AUCUN compte de tests (vérifié) — rien à ancrer ; un futur claim = l'ajouter à la table. Noté dans le commentaire du bloc.
**4 morsures vérifiées** (chaque README restauré depuis backup — **JAMAIS** `git checkout .`/`git clean`, interdits absolus) :
1. drift commentaire (`faisabilite/generator` 16→17 tests) → ✗ « dit 17 MAIS source dit 16 » ;
2. incohérence interne `N/N` (`rbac/apply_plan` `16/16`→`16/17 verts`) → ✗ « dit 16/17 MAIS source dit 16 » ;
3. disparition de claim (`legal/confotur` prose auto-score « diffable, 44 tests (dont… » retirée) → ✗ « claim INTROUVABLE (attendu 44) » ;
4. drift auto-score prose (`rbac/fixtures_gen` `(11 tests verts`→`(12 tests verts`) → ✗ « dit 12 MAIS source dit 11 ».
Restauré = green : `rc=0`, **440 checks de base intacts (diff vide) + 27 nouveaux**. Gates sœurs re-verts : `check_ci_integrity` rc=0 · `check_docs` rc=0.
- En-tête du gate mis à jour (inventaire des surfaces couvertes : nouvelle entrée « comptes de tests self-reportés par MODULE »).
- **Note memory** : classe « comptes de tests » — désormais couverte sur TROIS surfaces (cellule « Tests » des fiches via `row_re` · agrégats prose fiches · **README de module**, ce bloc) + les cinq claims per-suite hors table. Source unique = `auth` (plan.suites[].test_methods).
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
- **Auto-score 4Big** : 96/100.