From a52daa056f00b8d8f54636584c29ec5c36fcaaa6 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Sat, 1 Aug 2026 16:41:34 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20?= =?UTF-8?q?=C2=B7=20QA/audit=5F5d=20:=20gate=20d'IDENTIT=C3=89=20des=205?= =?UTF-8?q?=20DIMENSIONS=20du=20cadre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3e surface du MÊME README `qa/audit_5d` : la table « ## Les 5 dimensions » (README:15-19) transcrit par ligne le triplet (id · titre · reference) de chaque dimension. Jusqu'ici gatée seulement sur son COMPTE agrégé (« 5 dimensions ») — un WILDCARD aveugle à QUELLE dimension. Une norme de référence permutée (D3 fiscal `Ley 32-23`, D1 probatoire `ISA 500`) est une affirmation de conformité fausse (l'invention même que #6 interdit) sur l'audit de second niveau que le banquier lit. Gate ajouté (`ci/check_readme_claims.sh`) : liste ORDONNÉE {id:(titre,reference)} recomputée de `qa/audit_5d/out/audit_report.json[dimensions]` (byte-gaté) — identité par ensemble d'id (absent/en trop), ordre préservé (D1→D5), (titre,reference) par ligne. Cohérence croisée bonus : rapport == audit_spec.json[dimensions] (l'INPUT dont il dérive). Table disparue = échec (traçabilité #6). 8 morsures vérifiées (README rename titre/permute norme ISA/Ley/supprime ligne/ réordonne · artefact & spec renommés · table supprimée) ; restauré green · 7 gates re-verts. Édition hors `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact. ci/README.md (recap + paragraphe détaillé) mis à jour. Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-08-01.md | 62 ++++++++++++++++++++++++++++ ci/README.md | 20 ++++++++- ci/check_readme_claims.sh | 76 +++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/05_activity_log/2026-08-01.md b/05_activity_log/2026-08-01.md index 5f48d1b..9c14f25 100644 --- a/05_activity_log/2026-08-01.md +++ b/05_activity_log/2026-08-01.md @@ -1838,3 +1838,65 @@ toucher portails (prouve la désambiguïsation par job CI). Restauré = green : é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. diff --git a/ci/README.md b/ci/README.md index 8f2b90d..31b8f08 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa | `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui | | `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui | | `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui | -| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes de tests par suite écrits HORS de cette colonne (prose libre seo « 36 tests »/frontend_console « 19 »&« 31 »/onapi_legal « 44 » + cellule de table à suffixe devops « 29 (dont 14…) », recomputés de `plan.suites[].test_methods` — surfaces que row_re et l'agrégat en prose ne balaient pas) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence e-CF DGII `fiscal/ecf_dgii` (2e surface : états émetteurs recomputés de `{emission_events[].update_value}` + champs de base recomputés de `{emission_events[].base_field}` + FormaPago défaut « 3 (Tarjeta) » recomputée de `forma_pago_defaut` **ancrée sur CLAUDE.md #10** (Cardnet) + cohérences croisées : `forma_pago_defaut.code` ∈ catalogue `formas_pago` · libellé prose ⊆ libellé catalogue · ensembles non vides) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis » + **promesse anti-invention #6** `taux_pct/source/a_confirmer` tout-`null` confrontée à `commission_plan.json` dans les deux sens, invariant #7 « aucun taux sans source », `MANIFEST.counts` recoupé + **rôles payés** (le « à quel rôle » du livrable) : chaque `role_id` de `commission_plan.json` résolu dans `rbac_50_roles.json`, appartenance **singleton** au portail `ventes` nommé au README == un `portails_business` (séparation des pouvoirs · invariant #5), `erpnext_role_name` == contrat (nom Frappe résolu · invariant #6), `MANIFEST.roles_rbac_utilises` recoupé) **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph` — `Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) **et** du catalogue de projets du DocType `crm/dossier_vente` (le Select `projet` — les 9 projets `P01..P09` avec libellés — **ancré sur** la section « ## Projets » de `CLAUDE.md`, recomputé de CLAUDE.md : ensemble + ordre == options du DocType byte-gaté, plage `P01..P09` + mot d'ancrage dans le README, catalogue contigu/sans-doublon en bonus) **et** du catalogue des DocTypes `custom` à créer du portail `frontend/portails` (2e surface : le §hand-off VPS énumère `CONFOTUR Application`/`Faisabilité`/`Publiciste Log` == `MANIFEST.doctypes_custom_a_creer` recomputé, set-diff normalisé + cohérences croisées : catalogue trié/sans-doublon/non-vide · égal à l'UNION des `doctypes_custom` par workspace) **et** du classement métier/technique des `Role Profile` de `rbac/roleprofile_gen` (2e surface : la colonne « Type » de la table — auparavant un WILDCARD — recompute le flag `metier` par profil de `MANIFEST.profiles` + prose « N profils (X métier + Y technique) » == `counts.role_profiles/portails_metier/portails_techniques` + identité d'ensemble : lignes de profil de la table == `|profiles|` (aucun profil fantôme) + cohérences croisées : `portails_metier + portails_techniques == role_profiles` et recomptent les flags réels) **et** des paramètres canoniques du générateur de faisabilité `faisabilite/generator` (**ancrés sur** CLAUDE.md #9/#10 : les 6 valeurs `3 %`/`8.5 %`/`52 %`/`USD+DOP`/`Letter US`/`Cardnet` recomputées des contraintes #9/#10 de `CLAUDE.md` puis exigées EXACTES dans `genlib/model.py::CANONICAL` — le dict émis dans chaque rendu, hors `out/` donc invisible à check_artifacts — ET dans la prose README **ET dans les 3 copies aval : `renderer.py::_CANONICAL_MARKERS` l'oracle d'enforcement de `check_canonical`, les 2 docstrings de `scorer.py`, et l'oracle du test `test_generator.py`**, set-diff normalisé) **et** de la table des scénarios de démo `demo/scenarios` (2e surface : les trois colonnes d'identité `id`/`projet+libellé`/`audience` recomputées de `out/run_sheet.json` — libellés projets **ancrés sur** CLAUDE.md §Projets — + identité d'ensemble : ids des lignes == ids du run_sheet == `counts.scenarios` (aucun scénario fantôme/manquant) + cohérences croisées : `run_sheet`↔`MANIFEST` d'accord sur `(id,projet,audience)` · `id == S-{projet}-{AUDIENCE}`) **et** de la cross-cohérence PERMISSIONS du DocType CONFOTUR `legal/confotur` (2e surface : l'IDENTITÉ des 3 rôles + leur jeu d'ACTIONS exact + leur portail — le §Permissions énumérait `ventes-confotur`→read/write/create/print · `legal-onapi`→read/write/create · `legal-directeur`→read/write/**submit**/report — recomputés de `MANIFEST.roles_rbac_utilises[]` (role_id · portail · `permissions`), set-diff normalisé + identité d'ensemble README⇔MANIFEST (aucun rôle fantôme/manquant) + cohérences croisées : MANIFEST ⇄ DocType d'accord sur le jeu d'actions par rôle · séparation des pouvoirs `submit` singleton et `is_submittable` déduit de sa présence) **et** de l'identité de la table run-book de `rbac/apply_plan` (2e surface : la table « ## Run-book généré (SPEC §7) » — le graphe d'application ordonné — dont les colonnes `#`/`Responsable`/`Dépend de` étaient un WILDCARD : `#`=`order`, `Responsable`=`responsable` et `Dépend de`=les n°s d'ordre des `depends_on` recomputés de `out/apply_plan.json` byte-gaté, exacts par étape + identité d'ensemble lignes==étapes (aucune fantôme/manquante) + cohérences croisées : ordres contigus `1..N` sans doublon · `responsable` ∈ {worker,vps,worker+vps} · toute dépendance pointe en arrière) **et** des gardes RBAC du pipeline vente `crm/workflow_vente` (3e surface : la table « Rôles = contrat RBAC » — le rôle qui GARDE chaque transition — recomputée de `{allowed}` de `workflow.json` : identité d'ensemble colonne Rôle == gardes, cohérence artefacts `{allowed}` == `MANIFEST.roles_rbac_utilises`, ensemble non vide, + cross-cohérence par pas sur les étapes sensibles `Confirmer réservation`/`Signer contrat`/`Approuver CONFOTUR` — mord la réaffectation de privilège vers un rôle déjà présent que le set-diff seul manquerait) **et** de l'identité de la table run-book de `devops/deploy_runbook` (2e surface : la table « ## Plan de phases généré » — le graphe de portage VPS ordonné — dont les colonnes `#`/`Responsable`/`Modules`/`Dépend de` étaient un WILDCARD : recomputées de `out/deploy_runbook.json` byte-gaté, exactes par phase, + identité d'ensemble lignes==phases ET union des cellules Modules == les 22 modules de l'artefact — a capté un défaut réel : la table omettait `mobile/app_config` (phase 5) et `qa/acceptance` (phase 7), 20/22 modules listés pendant que la couverture prouvée était bijective 22/22 — + cohérences croisées : ordres contigus `1..N` · `responsable` ∈ {worker,vps,worker+vps} · dépendances en arrière) **et** du mapping d'enforcement row-level de `rbac/userperm_gen` (la table « Mapping `scope_donnees` → mécanisme » — la FONCTION qui associe chaque portée de données à son mécanisme Frappe natif + le verdict « Template émis ? », auparavant non gatée hors sa ventilation `28/16/2/4` — recomputée de `out/user_permission_plan.json` byte-gaté : par portée le `mechanism` (1er token backtické de la col.) & le verdict « Template émis ? » exacts, + identité d'ensemble portées de la table == portées de l'artefact (ni fantôme ni manquante) + cohérences croisées : 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 ») **et** des figures « Génération réelle (fixture) » de `faisabilite/bancable` (module **hors** check_artifacts — son `out/` n'est pas commité — dont le README §« Vérification en-repo » recopie 4 chiffres data-derived `40 unités`/`USD 8,560,000`/`DOP 505,040,000`/`21 unités (⌈52 % × 40⌉)` : recomputés `Σ quantités`/`Σ(qté×prix_usd)`/`Σ(qté×prix_dop)`/`⌈52 % × Σ unités⌉` de la fixture COMMITÉE `brief_bancable.json`, **pct 52 % ancré sur CLAUDE.md #9**, confirmés par la GÉNÉRATION RÉELLE `bancable_gen validate` — mord une fixture éditée, un générateur/manifeste incohérent, un séparateur de milliers manquant ou une formule désalignée ; un claim absent échoue AUSSI) **et** de la fiche d'identité du rôle RBAC mobile `03_agents/mobile/AGENT.md` (le SEUL ancrage in-repo de l'agent mobile — builds/stores hors-repo #8 — dont la ligne « Points de contact réellement commités » recopie du contrat `rbac_50_roles.json` TOUS les attributs data-derived du rôle `plateforme-mobile` : `erpnext_role_name`/`nom_fr`/`famille`/`portail`/`entite_principale`/`niveau`/`scope_donnees`/`modules`/permission `API Access` custom R/W (sigle des verbes canonique C/R/W/D/S/X/A — élargir/rétrécir la perm change le sigle attendu) + la `description` recoupée verbatim, emphase markdown & point final neutralisés — recomputés du contrat, prose EXACTE exigée par attribut, jamais gatés avant hors les COMPTES agrégés de `mobile/app_config` ; cohérences croisées : `famille == portail` (bijection portail), permission `custom`, `role_id` présent une seule fois dans le contrat) **et** des fiches d'identité des rôles RBAC faisabilité `03_agents/rendu/AGENT.md` & `03_agents/ifc_speckle/AGENT.md` (même classe que la fiche mobile : chaque ligne « Contact in-repo » recopie du contrat `rbac_50_roles.json` les attributs data-derived des rôles `faisabilite-rendu-3d` / `faisabilite-ifc-speckle` — `erpnext_role_name`/`nom_fr` (rendu)/`portail`/`entite_principale`/`niveau`/`modules` (rendu)/les deux `permissions_cibles` `File` R/W/create + `Faisabilité` R ou R/W (verbes rendus en ORDRE CANONIQUE read→R·write→W·create/delete/… en toutes lettres — élargir/rétrécir une perm change la chaîne attendue) — recomputés du contrat, prose EXACTE exigée par attribut RESTITUÉ, jamais gatés avant hors les COMPTES de rôles par Workspace de `frontend/portails` ; NB la DESCRIPTION de ces deux fiches est une PARAPHRASE éditoriale donc NON gatée verbatim (ne rien réécrire de correct pour un gate · #6), seuls les attributs structurés sont contraints ; un claim absent échoue AUSSI) **et** du rattachement Workspace (`Has Role`) des fiches agents `03_agents/{rendu,ifc_speckle,mobile}/AGENT.md` (surface data-derived DISTINCTE du contrat : QUELLE CONSOLE — Workspace ERPNext natif — le rôle peut atteindre, membership qui vit dans `frontend/portails/out/workspace.json` byte-gaté, jamais gatée hors le COMPTE de rôles par workspace aveugle à QUEL rôle — on recompute l'ensemble des Workspaces dont le `Has Role` porte l'`erpnext_role_name` du rôle (résolu du contrat par id) et on confronte l'assertion de la fiche : rendu affirme rattaché au Workspace NOMMÉ `OTO Construction` (in_named : membership == {ce Workspace} et la prose le cite) · ifc_speckle affirme rattaché à UN Workspace (in_any : membership ≥ 1) · mobile affirme VOLONTAIREMENT PAS rattaché au Workspace NOMMÉ `OTO Ventes` (not_in_named, assertion NÉGATIVE #6 : rôle ABSENT du `Has Role` de ce Workspace) — mord l'ajout du rôle mobile à `OTO Ventes` (SUR-EXPOSITION console), le retrait/déplacement du rôle rendu de `OTO Construction`, le renommage du Workspace dans la prose, un Workspace nommé fantôme (anti-typo) ou une assertion absente) **et** des tokens design canoniques `publiciste/branding` (**ancrés sur CLAUDE.md #4** : les 4 valeurs de la marque luxury `#0a0a12`/`#f0b429`/`Fraunces`/`Cormorant Garamond` recopiées à QUATRE endroits jamais gatés — (a) les CONSTANTES `lib/branding.py` `COLOR_BG`/`COLOR_ACCENT`/`FONT_DISPLAY`/`FONT_BODY`, hors `out/` donc invisibles à check_artifacts, importées par le générateur & substituées dans le gabarit `{{COLOR_BG}}…` ; (b) le docstring du fichier ; (c) le README ; (d) le tuple d'oracle HARDCODÉ de `tests/test_publiciste.py` que le seul test existant assert sans jamais le comparer à CLAUDE.md — recomputés de la ligne #4 de `CLAUDE.md` puis exigés EXACTS : (a) constantes == #4 (hex insensible à la casse, fontes à l'exact), (b) docstring cite les 4, (c) README cite les 2 fontes, (d) oracle du test == #4 ; même patron que les paramètres canoniques `faisabilite/generator`, un changement de #4 mord SIMULTANÉMENT les 3 copies aval ; un claim absent échoue AUSSI) **et** de l'identité du RÔLE émetteur e-CF `fiscal/ecf_dgii` (3e surface du MÊME README : le seul rôle habilité à ÉMETTRE un e-CF — bullet `role_id` README:51-54 — nommé par `role_id` « `compta-fiscaliste-ecf` » = celui que TOUS les `emission_events` d'`ecf_plan.json` byte-gaté référencent, `erpnext_role_name` « _OTO Compta Fiscaliste eCF_ », et `portail` « `compta` » résolu du contrat `rbac_50_roles.json` ; recomputés des deux sources — zéro duplication — prose EXACTE exigée ; réaffecter l'émission à un autre rôle, le renommer, ou DÉPLACER ce rôle hors du portail `compta` (e-CF émis HORS Compta, séparation cassée) mord ; cohérences croisées : `emission_events` pointent UN SEUL rôle (fonction · non vide) · `role_id` unique au contrat · nom artefact == nom contrat · `portail == compta` ; un claim absent échoue AUSSI) **et** de la colonne « Job CI » des fiches agents `03_agents/*/AGENT.md` (chaque token `*-tests` cité entre backticks — le job Gitea Actions qui exécute la suite du module — recomputé de la section `jobs:` de `.gitea/workflows/ci.yml`, jamais gaté avant : `check_ci_integrity` prouve que chaque job est CÂBLÉ dans `gate.needs` mais RIEN ne liait le NOM écrit dans la fiche au job réel ; on exige que chaque token de fiche DÉSIGNE un job existant — renommer un job dans ci.yml ou le mal recopier laisse la fiche pointer un job FANTÔME pendant que le CI reste vert ; direction fiche→ci.yml, ci.yml peut définir des jobs non cités mais une fiche ne peut citer un job absent ; la disparition de la colonne échoue AUSSI) **et** de la colonne « Entrée CLI » des fiches agents `03_agents/*/AGENT.md` (le jeu de verbes cité entre backticks — `publiciste.py parse\|validate\|generate\|run`, `faisabilite_gen.py score\|scaffold\|generate\|batch`, `rbac_apply_plan.py build\|validate`, … 16 cellules — recomputé **par AST** de l'ensemble ORDONNÉ des `subparsers.add_parser("")` du script réel, jamais gaté avant : les blocs amont couvrent les colonnes « Tests » et « Job CI » mais RIEN ne liait les VERBES écrits aux sous-commandes que le CLI EXPOSE ; script résolu par le chemin de module de la MÊME ligne de table + basename ; égalité EXACTE ensemble ET ordre exigée — renommer/ajouter/retirer/réordonner une sous-commande dans le `.py` (ou la mal recopier dans la fiche) laisse la fiche documenter une surface d'invocation FANTÔME pendant que le module tourne sous le vrai verbe ; direction fiche→CLI ; script introuvable ou disparition de la colonne échoue AUSSI) **et** de l'IDENTITÉ du DocType porteur `crm/dossier_vente` (le NOM `OTO Dossier Vente` — point d'attache du pipeline — recopié à la main dans le titre & la ligne « Nom du DocType » du README `dossier_vente`, la fiche `03_agents/crm/AGENT.md:25` et la fiche `03_agents/onapi_legal/AGENT.md:50`, jamais gaté sur le NOM (le bloc pipeline ne recompute que le COMPTE d'états) : recomputé du champ `name` de `out/doctype_oto_dossier_vente.json` byte-gaté, chaque prose exigée EXACTE + cohérences croisées `DocType.name == MANIFEST.doctype_name` & `== workflow.document_type` (l'attache porteur↔workflow — sinon le Workflow vise un DocType FANTÔME) ; renommer le DocType dans le spec reconstruit les 2 artefacts cohéremment mais périme la prose en silence ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ du nœud racine `Organization` du graphe schema.org SEO (**ancrée sur CLAUDE.md §Entités** : le nom de marque `Helios RD` (marque publique) + son `url` == base_url — le bloc composition amont ne gate que les `@type`/bijection, jamais l'IDENTITÉ de marque que Google indexe sous `vente.otov7.com` ; recomputés de `seo_schema_org.json` byte-gaté + `MANIFEST.base_url` + le `country_code` du spec : (a) `Organization.name` == marque re-dérivée de CLAUDE.md §Entités — renommer la marque dans le spec ou dans CLAUDE.md émet un JSON-LD public qui CONTREDIT le mandat tout en restant byte-vert (l'artefact se reconstruit fidèlement depuis un spec dérivé), même « vert trompeur » que les tokens branding ancrés sur #4 — (b) `Organization.url` == base_url & `@id` sous base_url, (c) chaque listing rattaché à CETTE Organization (`brand.@id`) sous base_url, (d) `addressCountry` UNIFORME == `country_code` du spec, (e) le spec DÉCLARE l'ancrage (`organization.source` cite CLAUDE.md)) **et** des entités porteuses du DocType CONFOTUR `legal/confotur` (**ancrées sur CLAUDE.md §Entités** : le champ Select `entite_porteuse` offre les 7 entités canoniques `WAF`/`WA SRL`/`AC Arias Cuevas`/`Consortium ECR DR`/`Helios RD`/`Ploutos`/`9060 QC`, dérivées via `options_source` de `confotur_spec.json[entites]` et byte-gatées pour la reproductibilité mais jamais ancrées au mandat — les blocs CONFOTUR amont ne gatent que le COMPTE de champs/rôles et la cross-cohérence des permissions ; recomputées des tokens en gras de la section « ## Entités » de CLAUDE.md : (a) options 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"`, (e) README cite l'ancre CLAUDE.md + nomme chaque entité par nom complet OU 1er token — renommer une entité dans CLAUDE.md seul mord SIMULTANÉMENT artefact/spec/déclaration/README, l'ancre est vive ; un claim absent échoue AUSSI) **et** de la cross-cohérence PERMISSIONS CONFOTUR répliquée dans la FICHE AGENT `03_agents/onapi_legal/AGENT.md` (3e surface : la §Permissions de la fiche recopie la MÊME table role→portail→actions que le README du module — `ventes-confotur` (Ventes)→read/write/create/print · `legal-onapi` (Direction)→read/write/create · `legal-directeur` (Direction)→read/write/**submit**/report — sous une convention DIFFÉRENTE (` / ` espacé · gras `**submit**`), gatée par RIEN alors que le gate README ne voit QUE le README ; recomputées de `MANIFEST.roles_rbac_utilises[]` (role_id · portail · `permissions`, byte-gaté), gras/casse normalisés, set-diff par rôle + identité d'ensemble fiche⇔MANIFEST — promouvoir un rôle à `submit`, retirer une action, réaffecter un portail, renommer/ajouter un rôle fantôme dans la fiche mord ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ de la persona & des capabilities OTOIA du Chat par portail `frontend/chat_otoia` (**ancrées sur CLAUDE.md §Architecture cible** : la persona `Amélie` / voix `multilingual_v2` + les 4 capabilities `aec.py`+`knowledge.py`+`prompt_engine.py`+`chat.py`, portées par `out/chat_mount.json` (5 mounts) & `out/MANIFEST.json` byte-gatés pour la reproductibilité mais jamais ancrées au mandat — le bloc chat_otoia amont ne gate que la COMPOSITION (portails métier · 1 block⇔1 mount) et le SEUL contrôle d'identité `tests/` a un ORACLE HARDCODÉ jamais comparé à CLAUDE.md ; recomputées de CLAUDE.md §Architecture cible (« Voix Amélie QC (multilingual_v2) » · « OTOIA capabilities : … ») : (a) `chat_spec.json` persona/capabilities == CLAUDE.md, (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) le README cite l'ancre §Architecture cible + nomme persona + chaque capability, (e) l'ORACLE du test == CLAUDE.md — renommer la persona/voix ou renommer/retirer une capability dans le spec (ou dans CLAUDE.md) mord tout en restant byte-vert ET tests-verts, même « vert trompeur » que les tokens branding ancrés sur #4 ; un claim absent échoue AUSSI) **et** du SDK MAJEUR Expo de l'app mobile `mobile/app_config` (**ancré sur la roadmap Sprint 5 l.56** « Rebuild Expo 54 » : la version majeure `expoSdkMajor` que l'app cible — recopiée CINQ fois dans le README (`Expo 54`/`Expo SDK **54**`) — jamais gatée hors le quadruplet onglets/rôles/langues/identifiants ; recomputée du champ `expo.extra.expoSdkMajor` de `out/app_config.json` byte-gaté : (a) cohérence interne artefact ⇔ `MANIFEST.app.expo_sdk_major` ⇔ `mobile_spec.json[app].expo_sdk_major`, (b) ANCRAGE roadmap `Rebuild Expo ` == artefact (ancre vive : bumper le SDK dans l'artefact ou dans la roadmap sans l'autre mord), (c) le spec DÉCLARE l'ancrage (`expo_sdk_source` cite roadmap + Expo N), (d) TOUTE mention prose « Expo [SDK] N » == artefact (aucune périmée), (e) la ligne de source du tableau cite l'ancre `roadmap` ; un claim absent échoue AUSSI) **et** du jeu de DEVISES `moneda` de l'e-CF `fiscal/ecf_dgii` (**ancré sur CLAUDE.md #10** : les options `USD`/`DOP` du champ `TipoMoneda`, byte-gatées dans `out/ecf_plan.json` mais jamais ancrées au mandat — les blocs fiscaux amont gatent le FormaPago (Cardnet) et les états/champs émetteurs, aveugles au jeu de devises, et le bloc « Fiches #10 » ancre les fiches ERPNext/CRM, aveugle au module fiscal ; recomputées de la devise en gras de la contrainte #10 (`USD + DOP`, réutilise l'`exp["devises"]` déjà parsé · zéro duplication) : (a) `spec[moneda].options` == #10 ORDRE exact (mord ajout/retrait/réordonnancement), (b) l'artefact byte-gaté idem, (c) le spec DÉCLARE l'ancrage via un champ DISTINCT `options_source` — `source` restant legit. `null` (taux TipoCambio à confirmer · #8) — qui cite `CLAUDE.md #10` + chaque devise, (d) la prose README qui se réclame de #10 cite le jeu EXACT + toute mention `moneda`/`devise` d'une paire de devises concordante — étendre/renommer une devise dans #10 ET le spec de façon cohérente reste byte-vert tout en faisant émettre un `TipoMoneda` hors mandat, même « vert trompeur » que la marque SEO §Entités ou les tokens branding #4 ; un claim absent échoue AUSSI) **et** de l'IMPUTABILITÉ des confirmations préalables VPS `devops/deploy_runbook` (3e surface : la ligne « Confirmations préalables VPS » du README annote chaque confirmation d'un couple `nom` (**owner** · **réf-contrôle**) — ex. `taux_commission` (Direction · audit_5d D1.1) — data-derived des `confirmations` de `out/deploy_runbook.json` byte-gaté CROISÉES avec le rapport `qa/audit_5d` byte-gaté ; les blocs DevOps amont ne gatent que le COMPTE et l'ENSEMBLE des noms, aveugles à QUI porte chaque confirmation et à QUEL contrôle la trace ; cross-anchor à trois : owner run-book ⊆ owner du contrôle audit_5d cité (tolère le suffixe « / UAF ») · owner README ⊆ owner run-book (tolère l'abréviation « Fiscaliste eCF » ⊂ « Compta Fiscaliste eCF ») · réf README == contrôle réel présent dans audit_5d ET dans la source — réaffecter un owner, misattribuer le contrôle (`D1.1`→`D2.1`) ou citer un contrôle FANTÔME (`D9.9`) mord ; une confirmation tracée DOIT rester annotée, une annotation d'un nom fantôme échoue AUSSI) **et** du COMPTE CANONIQUE de rôles RBAC (**ancré sur CLAUDE.md §Architecture cible** « RBAC N rôles » : le nombre `50` recopié dans le contrat `rbac_50_roles.json` — le champ déclaré `cible_rbac_roles` ET la taille réelle du catalogue `len(roles)` — dans les schémas (`const: 50`) et dans la fiche `03_agents/erpnext_backend/AGENT.md` (« RBAC **50 rôles** exactement » + 3 autres mentions), sans jamais être re-dérivé du mandat ; les gates RBAC amont ne prouvent que la cohérence INTERNE contrat↔MANIFEST↔READMEs de volet, aveugles à l'ancrage au mandat — changer « RBAC 50 rôles » dans CLAUDE.md ou faire dévier le catalogue de façon interne-cohérente (cible ET len alignés ailleurs) laisserait contrat & fiche affirmer « 50 exactement » en CONTREDISANT le mandat, en silence ; recomputé de §Architecture cible : (a) contrat `cible_rbac_roles` == N, (b) taille réelle `len(roles)` == N, (c) cible == réel (mandat atteint), (d) la fiche porte l'invariant « RBAC **N rôles** exactement » + aucune mention « M rôles » périmée ; même patron que les tokens branding #4 / la persona OTOIA §Architecture cible ; un claim absent échoue AUSSI) **et** des comptes du run-book d'application `rbac/RBAC_50_ROLES_SPEC.md` §7 (la NOTE « ✅ … livré en-repo » restitue sur une surface DISTINCTE des READMEs userperm_gen/roleprofile_gen déjà gatés trois comptes data-derived — « 28 templates `Company` (portée `entite`) », « **6 profils** (5 portails métier + console technique `plateforme`) » et « **50 rôles de façon bijective** » — jamais gatés hors le §3 (ventilation par portail) ; recomputés du contrat `rbac_50_roles.json` byte-gaté : 28 == |scope==entite|, 6 == |portails distincts| (1 Role Profile par portail), 5 == portails métier hors `plateforme`, 50 == |rôles| ; bumper une portée `entite`→`groupe` ou ajouter/retirer un portail dans le contrat périme la note en silence pendant que les READMEs de volet restent verts ; le blockquote `> ` est neutralisé avant match ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ des configs runtime du Chat OTOIA dans la cellule « Métrique vérifiée » de la fiche `03_agents/frontend_console/AGENT.md` (restitue TROIS attributs data-derived du montage — `persona Amélie` · `langues FR/EN/ES` · `` `endpoint: null` `` — aveugles au bloc chat_otoia amont qui ne gate que le COMPTE blocks/mounts/portails, et NON couverts par le gate persona/capabilities ancré sur CLAUDE.md §Architecture cible (qui vise spec/mounts/MANIFEST/README/oracle du test, jamais cette fiche) ; recomputés des artefacts byte-gatés `out/chat_mount.json` (5 configs) + `out/MANIFEST.json` : persona == `MANIFEST.persona.nom` (le « QC » est le qualificatif de marque, gardé en littéral non gaté), langues `FR/EN/ES` == `MANIFEST.langues` majuscule, `endpoint: null` == **AFFIRMATION D'HONNÊTETÉ #6/#8** exigeant que CHAQUE mount ait `endpoint == null` — un endpoint fabriqué dans l'artefact fait de la fiche « null » un MENSONGE et mord ; cohérences croisées : `MANIFEST.persona.nom`/`langues` == CHAQUE mount (montage désynchronisé mordu) · `endpoint_statut == 'a_confirmer'` ⇔ endpoints TOUS null ; un claim absent échoue AUSSI) **et** des DEUX métriques data-derived du bundle SEO restituées dans la fiche `03_agents/seo/AGENT.md` (le tableau « Livrable réellement produit » recopie, à côté du compte de mots-clés déjà gaté sur la fiche, deux sorties gatées AVANT sur le seul README : L25 « graphe JSON-LD : **N nœuds** (`Organization` racine + M `Residence`) » et L26 « **N pages** × FR/EN/ES + `x-default` » ; recomputées des mêmes artefacts byte-gatés `seo_schema_org.json`/`MANIFEST.json` et `seo_hreflang.json` : L25 total nœuds == `schema_org_nodes`, racine == le `@type` singleton, M == `listings`, type listing == le `@type` uniforme des listings + cohérence arithmétique `total == 1 + M` ; L26 nombre de pages == `|pages|` de la carte + jeu de langues de contenu == celui de l'artefact (set-diff, absent ET en trop) ; RENOMMER le `@type`, AJOUTER/RETIRER un projet ou ÉCHANGER une langue périme la fiche pendant que les artefacts disent autre chose — même classe que la cellule chat_otoia de la fiche Frontend Console (fiche-agent qui restitue des attributs gatés seulement en agrégat) ; un claim absent échoue AUSSI) **et** de la colonne « Entité » de la §4 de `rbac/RBAC_50_ROLES_SPEC.md` (**ancrée sur CLAUDE.md §Entités** : la table « Cartographie rôles ↔ entités » liste en colonne de tête les 7 entités NOMMÉES du mandat `WAF`/`WA SRL`/`AC Arias Cuevas`/`Consortium ECR DR`/`Helios RD`/`Ploutos`/`9060 QC` + le périmètre consolidé « Groupe » — deux sources faisant autorité, CLAUDE.md §Entités pour l'orthographe & le descripteur parenthétique et `rbac_50_roles.json` pour l'ENSEMBLE des `entite_principale` réellement portées, aveugles au §3 (comptes par portail) / §7 (comptes run-book) / blocs fiches (`entite` d'UN rôle) ; (a) ENSEMBLE §4 == distinct `entite_principale` du contrat (set-diff : ni fantôme ni manquante), (b) ANCRAGE chaque entité NOMMÉE de CLAUDE.md est une ligne §4 avec son descripteur parenthétique EXACT (absence comprise), (c) chaque entité nommée du mandat est portée ≥1× par le contrat (aucune décorative) — ajouter au contrat un rôle porté par une entité absente de §4, renommer une entité dans CLAUDE.md §Entités (`9060 QC→9061 QC`) ou dériver un descripteur mord ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ de l'ENSEMBLE des portails métier + l'exclusion nommée de la console technique dans la fiche `03_agents/frontend_console/AGENT.md` (le bloc « triplets par workspace » recompute cartes/liens/rôles PAR portail mais reste aveugle à QUELS portails composent l'ensemble ; la fiche énumère à la main L11 « 5 portails rôle métier (Ventes / Construction / Achat / Compta / Direction) » et affirme L46 que « la console technique `plateforme` (6ᵉ portail RBAC) est **exclue** » ; (a) l'ensemble ORDONNÉ des noms courts == les workspaces métier de `frontend/portails/out/MANIFEST.json` byte-gaté (label « OTO » sans préfixe, comme l'énumération des onglets Mobile) ET, DOUBLE ANCRAGE, leurs ids == `portails_business` du contrat RBAC (l'« égalité stricte » que la fiche revendique elle-même) ; (b) `plateforme` == l'UNIQUE portail RBAC hors métier (∈ portails distincts des rôles ∖ `portails_business`), c'est le 6ᵉ (total distinct == 6 = 5 métier + 1) — un échange (Achat→Marketing), un retrait, un réordre, un renommage de la console technique dans le contrat (`plateforme`→`console`) ou un contrat qui PROMEUT `plateforme` en métier mord ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ de l'endpoint GPU RunPod ComfyUI (l'id du pod `t68akvx80wv092` + le tag « Flux dev fp8 » cités dans les fiches BIM/Rendu + le master prompt, recomputés du bullet §1 de `AGENTS_EXISTING_ASSETS.md` ; toute fiche mentionnant RunPod doit citer l'id EXACT — token backtické de forme pod == canonique — + le tag EXACT ; un pod re-créé mord les 3 fiches, la disparition totale échoue AUSSI) **et** des évènements d'émission e-CF (ENSEMBLE des évènements émetteurs « réservation/contrat » ⨯ ensemble exclu des brouillons `lead/visite/devis` **disjoint** des émetteurs — jamais de facture sur brouillon — ⨯ ENSEMBLE des `base_field` ⨯ rôle unique `compta-fiscaliste-ecf` double-ancré RBAC portail compta ⨯ FormaPago défaut `3`/Tarjeta/Cardnet #10, recomputés de `ecf_plan.json`) **et** de l'identité d'infrastructure VPS (IP `153.75.250.214` + les DEUX conteneurs Docker `erpnext-backend-1`/`erpnext-frontend-1` recomputés de CLAUDE.md §VPS ; direction copie→source — toute doc `.md` du mandat, hors journaux `05_activity_log`/`daily_reports`, citant une IPv4 ou un conteneur de forme `--1` doit == la valeur canonique : une migration serveur ou un renommage du projet compose mord toutes les copies périmées, disparition totale de la surface échoue AUSSI) == artefact cité (#6) | ✅ oui | +| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes de tests par suite écrits HORS de cette colonne (prose libre seo « 36 tests »/frontend_console « 19 »&« 31 »/onapi_legal « 44 » + cellule de table à suffixe devops « 29 (dont 14…) », recomputés de `plan.suites[].test_methods` — surfaces que row_re et l'agrégat en prose ne balaient pas) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items` + IDENTITÉ ordonnée des 5 dimensions `id→titre→reference` recomputée de `audit_report.dimensions`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence e-CF DGII `fiscal/ecf_dgii` (2e surface : états émetteurs recomputés de `{emission_events[].update_value}` + champs de base recomputés de `{emission_events[].base_field}` + FormaPago défaut « 3 (Tarjeta) » recomputée de `forma_pago_defaut` **ancrée sur CLAUDE.md #10** (Cardnet) + cohérences croisées : `forma_pago_defaut.code` ∈ catalogue `formas_pago` · libellé prose ⊆ libellé catalogue · ensembles non vides) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis » + **promesse anti-invention #6** `taux_pct/source/a_confirmer` tout-`null` confrontée à `commission_plan.json` dans les deux sens, invariant #7 « aucun taux sans source », `MANIFEST.counts` recoupé + **rôles payés** (le « à quel rôle » du livrable) : chaque `role_id` de `commission_plan.json` résolu dans `rbac_50_roles.json`, appartenance **singleton** au portail `ventes` nommé au README == un `portails_business` (séparation des pouvoirs · invariant #5), `erpnext_role_name` == contrat (nom Frappe résolu · invariant #6), `MANIFEST.roles_rbac_utilises` recoupé) **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph` — `Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) **et** du catalogue de projets du DocType `crm/dossier_vente` (le Select `projet` — les 9 projets `P01..P09` avec libellés — **ancré sur** la section « ## Projets » de `CLAUDE.md`, recomputé de CLAUDE.md : ensemble + ordre == options du DocType byte-gaté, plage `P01..P09` + mot d'ancrage dans le README, catalogue contigu/sans-doublon en bonus) **et** du catalogue des DocTypes `custom` à créer du portail `frontend/portails` (2e surface : le §hand-off VPS énumère `CONFOTUR Application`/`Faisabilité`/`Publiciste Log` == `MANIFEST.doctypes_custom_a_creer` recomputé, set-diff normalisé + cohérences croisées : catalogue trié/sans-doublon/non-vide · égal à l'UNION des `doctypes_custom` par workspace) **et** du classement métier/technique des `Role Profile` de `rbac/roleprofile_gen` (2e surface : la colonne « Type » de la table — auparavant un WILDCARD — recompute le flag `metier` par profil de `MANIFEST.profiles` + prose « N profils (X métier + Y technique) » == `counts.role_profiles/portails_metier/portails_techniques` + identité d'ensemble : lignes de profil de la table == `|profiles|` (aucun profil fantôme) + cohérences croisées : `portails_metier + portails_techniques == role_profiles` et recomptent les flags réels) **et** des paramètres canoniques du générateur de faisabilité `faisabilite/generator` (**ancrés sur** CLAUDE.md #9/#10 : les 6 valeurs `3 %`/`8.5 %`/`52 %`/`USD+DOP`/`Letter US`/`Cardnet` recomputées des contraintes #9/#10 de `CLAUDE.md` puis exigées EXACTES dans `genlib/model.py::CANONICAL` — le dict émis dans chaque rendu, hors `out/` donc invisible à check_artifacts — ET dans la prose README **ET dans les 3 copies aval : `renderer.py::_CANONICAL_MARKERS` l'oracle d'enforcement de `check_canonical`, les 2 docstrings de `scorer.py`, et l'oracle du test `test_generator.py`**, set-diff normalisé) **et** de la table des scénarios de démo `demo/scenarios` (2e surface : les trois colonnes d'identité `id`/`projet+libellé`/`audience` recomputées de `out/run_sheet.json` — libellés projets **ancrés sur** CLAUDE.md §Projets — + identité d'ensemble : ids des lignes == ids du run_sheet == `counts.scenarios` (aucun scénario fantôme/manquant) + cohérences croisées : `run_sheet`↔`MANIFEST` d'accord sur `(id,projet,audience)` · `id == S-{projet}-{AUDIENCE}`) **et** de la cross-cohérence PERMISSIONS du DocType CONFOTUR `legal/confotur` (2e surface : l'IDENTITÉ des 3 rôles + leur jeu d'ACTIONS exact + leur portail — le §Permissions énumérait `ventes-confotur`→read/write/create/print · `legal-onapi`→read/write/create · `legal-directeur`→read/write/**submit**/report — recomputés de `MANIFEST.roles_rbac_utilises[]` (role_id · portail · `permissions`), set-diff normalisé + identité d'ensemble README⇔MANIFEST (aucun rôle fantôme/manquant) + cohérences croisées : MANIFEST ⇄ DocType d'accord sur le jeu d'actions par rôle · séparation des pouvoirs `submit` singleton et `is_submittable` déduit de sa présence) **et** de l'identité de la table run-book de `rbac/apply_plan` (2e surface : la table « ## Run-book généré (SPEC §7) » — le graphe d'application ordonné — dont les colonnes `#`/`Responsable`/`Dépend de` étaient un WILDCARD : `#`=`order`, `Responsable`=`responsable` et `Dépend de`=les n°s d'ordre des `depends_on` recomputés de `out/apply_plan.json` byte-gaté, exacts par étape + identité d'ensemble lignes==étapes (aucune fantôme/manquante) + cohérences croisées : ordres contigus `1..N` sans doublon · `responsable` ∈ {worker,vps,worker+vps} · toute dépendance pointe en arrière) **et** des gardes RBAC du pipeline vente `crm/workflow_vente` (3e surface : la table « Rôles = contrat RBAC » — le rôle qui GARDE chaque transition — recomputée de `{allowed}` de `workflow.json` : identité d'ensemble colonne Rôle == gardes, cohérence artefacts `{allowed}` == `MANIFEST.roles_rbac_utilises`, ensemble non vide, + cross-cohérence par pas sur les étapes sensibles `Confirmer réservation`/`Signer contrat`/`Approuver CONFOTUR` — mord la réaffectation de privilège vers un rôle déjà présent que le set-diff seul manquerait) **et** de l'identité de la table run-book de `devops/deploy_runbook` (2e surface : la table « ## Plan de phases généré » — le graphe de portage VPS ordonné — dont les colonnes `#`/`Responsable`/`Modules`/`Dépend de` étaient un WILDCARD : recomputées de `out/deploy_runbook.json` byte-gaté, exactes par phase, + identité d'ensemble lignes==phases ET union des cellules Modules == les 22 modules de l'artefact — a capté un défaut réel : la table omettait `mobile/app_config` (phase 5) et `qa/acceptance` (phase 7), 20/22 modules listés pendant que la couverture prouvée était bijective 22/22 — + cohérences croisées : ordres contigus `1..N` · `responsable` ∈ {worker,vps,worker+vps} · dépendances en arrière) **et** du mapping d'enforcement row-level de `rbac/userperm_gen` (la table « Mapping `scope_donnees` → mécanisme » — la FONCTION qui associe chaque portée de données à son mécanisme Frappe natif + le verdict « Template émis ? », auparavant non gatée hors sa ventilation `28/16/2/4` — recomputée de `out/user_permission_plan.json` byte-gaté : par portée le `mechanism` (1er token backtické de la col.) & le verdict « Template émis ? » exacts, + identité d'ensemble portées de la table == portées de l'artefact (ni fantôme ni manquante) + cohérences croisées : 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 ») **et** des figures « Génération réelle (fixture) » de `faisabilite/bancable` (module **hors** check_artifacts — son `out/` n'est pas commité — dont le README §« Vérification en-repo » recopie 4 chiffres data-derived `40 unités`/`USD 8,560,000`/`DOP 505,040,000`/`21 unités (⌈52 % × 40⌉)` : recomputés `Σ quantités`/`Σ(qté×prix_usd)`/`Σ(qté×prix_dop)`/`⌈52 % × Σ unités⌉` de la fixture COMMITÉE `brief_bancable.json`, **pct 52 % ancré sur CLAUDE.md #9**, confirmés par la GÉNÉRATION RÉELLE `bancable_gen validate` — mord une fixture éditée, un générateur/manifeste incohérent, un séparateur de milliers manquant ou une formule désalignée ; un claim absent échoue AUSSI) **et** de la fiche d'identité du rôle RBAC mobile `03_agents/mobile/AGENT.md` (le SEUL ancrage in-repo de l'agent mobile — builds/stores hors-repo #8 — dont la ligne « Points de contact réellement commités » recopie du contrat `rbac_50_roles.json` TOUS les attributs data-derived du rôle `plateforme-mobile` : `erpnext_role_name`/`nom_fr`/`famille`/`portail`/`entite_principale`/`niveau`/`scope_donnees`/`modules`/permission `API Access` custom R/W (sigle des verbes canonique C/R/W/D/S/X/A — élargir/rétrécir la perm change le sigle attendu) + la `description` recoupée verbatim, emphase markdown & point final neutralisés — recomputés du contrat, prose EXACTE exigée par attribut, jamais gatés avant hors les COMPTES agrégés de `mobile/app_config` ; cohérences croisées : `famille == portail` (bijection portail), permission `custom`, `role_id` présent une seule fois dans le contrat) **et** des fiches d'identité des rôles RBAC faisabilité `03_agents/rendu/AGENT.md` & `03_agents/ifc_speckle/AGENT.md` (même classe que la fiche mobile : chaque ligne « Contact in-repo » recopie du contrat `rbac_50_roles.json` les attributs data-derived des rôles `faisabilite-rendu-3d` / `faisabilite-ifc-speckle` — `erpnext_role_name`/`nom_fr` (rendu)/`portail`/`entite_principale`/`niveau`/`modules` (rendu)/les deux `permissions_cibles` `File` R/W/create + `Faisabilité` R ou R/W (verbes rendus en ORDRE CANONIQUE read→R·write→W·create/delete/… en toutes lettres — élargir/rétrécir une perm change la chaîne attendue) — recomputés du contrat, prose EXACTE exigée par attribut RESTITUÉ, jamais gatés avant hors les COMPTES de rôles par Workspace de `frontend/portails` ; NB la DESCRIPTION de ces deux fiches est une PARAPHRASE éditoriale donc NON gatée verbatim (ne rien réécrire de correct pour un gate · #6), seuls les attributs structurés sont contraints ; un claim absent échoue AUSSI) **et** du rattachement Workspace (`Has Role`) des fiches agents `03_agents/{rendu,ifc_speckle,mobile}/AGENT.md` (surface data-derived DISTINCTE du contrat : QUELLE CONSOLE — Workspace ERPNext natif — le rôle peut atteindre, membership qui vit dans `frontend/portails/out/workspace.json` byte-gaté, jamais gatée hors le COMPTE de rôles par workspace aveugle à QUEL rôle — on recompute l'ensemble des Workspaces dont le `Has Role` porte l'`erpnext_role_name` du rôle (résolu du contrat par id) et on confronte l'assertion de la fiche : rendu affirme rattaché au Workspace NOMMÉ `OTO Construction` (in_named : membership == {ce Workspace} et la prose le cite) · ifc_speckle affirme rattaché à UN Workspace (in_any : membership ≥ 1) · mobile affirme VOLONTAIREMENT PAS rattaché au Workspace NOMMÉ `OTO Ventes` (not_in_named, assertion NÉGATIVE #6 : rôle ABSENT du `Has Role` de ce Workspace) — mord l'ajout du rôle mobile à `OTO Ventes` (SUR-EXPOSITION console), le retrait/déplacement du rôle rendu de `OTO Construction`, le renommage du Workspace dans la prose, un Workspace nommé fantôme (anti-typo) ou une assertion absente) **et** des tokens design canoniques `publiciste/branding` (**ancrés sur CLAUDE.md #4** : les 4 valeurs de la marque luxury `#0a0a12`/`#f0b429`/`Fraunces`/`Cormorant Garamond` recopiées à QUATRE endroits jamais gatés — (a) les CONSTANTES `lib/branding.py` `COLOR_BG`/`COLOR_ACCENT`/`FONT_DISPLAY`/`FONT_BODY`, hors `out/` donc invisibles à check_artifacts, importées par le générateur & substituées dans le gabarit `{{COLOR_BG}}…` ; (b) le docstring du fichier ; (c) le README ; (d) le tuple d'oracle HARDCODÉ de `tests/test_publiciste.py` que le seul test existant assert sans jamais le comparer à CLAUDE.md — recomputés de la ligne #4 de `CLAUDE.md` puis exigés EXACTS : (a) constantes == #4 (hex insensible à la casse, fontes à l'exact), (b) docstring cite les 4, (c) README cite les 2 fontes, (d) oracle du test == #4 ; même patron que les paramètres canoniques `faisabilite/generator`, un changement de #4 mord SIMULTANÉMENT les 3 copies aval ; un claim absent échoue AUSSI) **et** de l'identité du RÔLE émetteur e-CF `fiscal/ecf_dgii` (3e surface du MÊME README : le seul rôle habilité à ÉMETTRE un e-CF — bullet `role_id` README:51-54 — nommé par `role_id` « `compta-fiscaliste-ecf` » = celui que TOUS les `emission_events` d'`ecf_plan.json` byte-gaté référencent, `erpnext_role_name` « _OTO Compta Fiscaliste eCF_ », et `portail` « `compta` » résolu du contrat `rbac_50_roles.json` ; recomputés des deux sources — zéro duplication — prose EXACTE exigée ; réaffecter l'émission à un autre rôle, le renommer, ou DÉPLACER ce rôle hors du portail `compta` (e-CF émis HORS Compta, séparation cassée) mord ; cohérences croisées : `emission_events` pointent UN SEUL rôle (fonction · non vide) · `role_id` unique au contrat · nom artefact == nom contrat · `portail == compta` ; un claim absent échoue AUSSI) **et** de la colonne « Job CI » des fiches agents `03_agents/*/AGENT.md` (chaque token `*-tests` cité entre backticks — le job Gitea Actions qui exécute la suite du module — recomputé de la section `jobs:` de `.gitea/workflows/ci.yml`, jamais gaté avant : `check_ci_integrity` prouve que chaque job est CÂBLÉ dans `gate.needs` mais RIEN ne liait le NOM écrit dans la fiche au job réel ; on exige que chaque token de fiche DÉSIGNE un job existant — renommer un job dans ci.yml ou le mal recopier laisse la fiche pointer un job FANTÔME pendant que le CI reste vert ; direction fiche→ci.yml, ci.yml peut définir des jobs non cités mais une fiche ne peut citer un job absent ; la disparition de la colonne échoue AUSSI) **et** de la colonne « Entrée CLI » des fiches agents `03_agents/*/AGENT.md` (le jeu de verbes cité entre backticks — `publiciste.py parse\|validate\|generate\|run`, `faisabilite_gen.py score\|scaffold\|generate\|batch`, `rbac_apply_plan.py build\|validate`, … 16 cellules — recomputé **par AST** de l'ensemble ORDONNÉ des `subparsers.add_parser("")` du script réel, jamais gaté avant : les blocs amont couvrent les colonnes « Tests » et « Job CI » mais RIEN ne liait les VERBES écrits aux sous-commandes que le CLI EXPOSE ; script résolu par le chemin de module de la MÊME ligne de table + basename ; égalité EXACTE ensemble ET ordre exigée — renommer/ajouter/retirer/réordonner une sous-commande dans le `.py` (ou la mal recopier dans la fiche) laisse la fiche documenter une surface d'invocation FANTÔME pendant que le module tourne sous le vrai verbe ; direction fiche→CLI ; script introuvable ou disparition de la colonne échoue AUSSI) **et** de l'IDENTITÉ du DocType porteur `crm/dossier_vente` (le NOM `OTO Dossier Vente` — point d'attache du pipeline — recopié à la main dans le titre & la ligne « Nom du DocType » du README `dossier_vente`, la fiche `03_agents/crm/AGENT.md:25` et la fiche `03_agents/onapi_legal/AGENT.md:50`, jamais gaté sur le NOM (le bloc pipeline ne recompute que le COMPTE d'états) : recomputé du champ `name` de `out/doctype_oto_dossier_vente.json` byte-gaté, chaque prose exigée EXACTE + cohérences croisées `DocType.name == MANIFEST.doctype_name` & `== workflow.document_type` (l'attache porteur↔workflow — sinon le Workflow vise un DocType FANTÔME) ; renommer le DocType dans le spec reconstruit les 2 artefacts cohéremment mais périme la prose en silence ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ du nœud racine `Organization` du graphe schema.org SEO (**ancrée sur CLAUDE.md §Entités** : le nom de marque `Helios RD` (marque publique) + son `url` == base_url — le bloc composition amont ne gate que les `@type`/bijection, jamais l'IDENTITÉ de marque que Google indexe sous `vente.otov7.com` ; recomputés de `seo_schema_org.json` byte-gaté + `MANIFEST.base_url` + le `country_code` du spec : (a) `Organization.name` == marque re-dérivée de CLAUDE.md §Entités — renommer la marque dans le spec ou dans CLAUDE.md émet un JSON-LD public qui CONTREDIT le mandat tout en restant byte-vert (l'artefact se reconstruit fidèlement depuis un spec dérivé), même « vert trompeur » que les tokens branding ancrés sur #4 — (b) `Organization.url` == base_url & `@id` sous base_url, (c) chaque listing rattaché à CETTE Organization (`brand.@id`) sous base_url, (d) `addressCountry` UNIFORME == `country_code` du spec, (e) le spec DÉCLARE l'ancrage (`organization.source` cite CLAUDE.md)) **et** des entités porteuses du DocType CONFOTUR `legal/confotur` (**ancrées sur CLAUDE.md §Entités** : le champ Select `entite_porteuse` offre les 7 entités canoniques `WAF`/`WA SRL`/`AC Arias Cuevas`/`Consortium ECR DR`/`Helios RD`/`Ploutos`/`9060 QC`, dérivées via `options_source` de `confotur_spec.json[entites]` et byte-gatées pour la reproductibilité mais jamais ancrées au mandat — les blocs CONFOTUR amont ne gatent que le COMPTE de champs/rôles et la cross-cohérence des permissions ; recomputées des tokens en gras de la section « ## Entités » de CLAUDE.md : (a) options 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"`, (e) README cite l'ancre CLAUDE.md + nomme chaque entité par nom complet OU 1er token — renommer une entité dans CLAUDE.md seul mord SIMULTANÉMENT artefact/spec/déclaration/README, l'ancre est vive ; un claim absent échoue AUSSI) **et** de la cross-cohérence PERMISSIONS CONFOTUR répliquée dans la FICHE AGENT `03_agents/onapi_legal/AGENT.md` (3e surface : la §Permissions de la fiche recopie la MÊME table role→portail→actions que le README du module — `ventes-confotur` (Ventes)→read/write/create/print · `legal-onapi` (Direction)→read/write/create · `legal-directeur` (Direction)→read/write/**submit**/report — sous une convention DIFFÉRENTE (` / ` espacé · gras `**submit**`), gatée par RIEN alors que le gate README ne voit QUE le README ; recomputées de `MANIFEST.roles_rbac_utilises[]` (role_id · portail · `permissions`, byte-gaté), gras/casse normalisés, set-diff par rôle + identité d'ensemble fiche⇔MANIFEST — promouvoir un rôle à `submit`, retirer une action, réaffecter un portail, renommer/ajouter un rôle fantôme dans la fiche mord ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ de la persona & des capabilities OTOIA du Chat par portail `frontend/chat_otoia` (**ancrées sur CLAUDE.md §Architecture cible** : la persona `Amélie` / voix `multilingual_v2` + les 4 capabilities `aec.py`+`knowledge.py`+`prompt_engine.py`+`chat.py`, portées par `out/chat_mount.json` (5 mounts) & `out/MANIFEST.json` byte-gatés pour la reproductibilité mais jamais ancrées au mandat — le bloc chat_otoia amont ne gate que la COMPOSITION (portails métier · 1 block⇔1 mount) et le SEUL contrôle d'identité `tests/` a un ORACLE HARDCODÉ jamais comparé à CLAUDE.md ; recomputées de CLAUDE.md §Architecture cible (« Voix Amélie QC (multilingual_v2) » · « OTOIA capabilities : … ») : (a) `chat_spec.json` persona/capabilities == CLAUDE.md, (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) le README cite l'ancre §Architecture cible + nomme persona + chaque capability, (e) l'ORACLE du test == CLAUDE.md — renommer la persona/voix ou renommer/retirer une capability dans le spec (ou dans CLAUDE.md) mord tout en restant byte-vert ET tests-verts, même « vert trompeur » que les tokens branding ancrés sur #4 ; un claim absent échoue AUSSI) **et** du SDK MAJEUR Expo de l'app mobile `mobile/app_config` (**ancré sur la roadmap Sprint 5 l.56** « Rebuild Expo 54 » : la version majeure `expoSdkMajor` que l'app cible — recopiée CINQ fois dans le README (`Expo 54`/`Expo SDK **54**`) — jamais gatée hors le quadruplet onglets/rôles/langues/identifiants ; recomputée du champ `expo.extra.expoSdkMajor` de `out/app_config.json` byte-gaté : (a) cohérence interne artefact ⇔ `MANIFEST.app.expo_sdk_major` ⇔ `mobile_spec.json[app].expo_sdk_major`, (b) ANCRAGE roadmap `Rebuild Expo ` == artefact (ancre vive : bumper le SDK dans l'artefact ou dans la roadmap sans l'autre mord), (c) le spec DÉCLARE l'ancrage (`expo_sdk_source` cite roadmap + Expo N), (d) TOUTE mention prose « Expo [SDK] N » == artefact (aucune périmée), (e) la ligne de source du tableau cite l'ancre `roadmap` ; un claim absent échoue AUSSI) **et** du jeu de DEVISES `moneda` de l'e-CF `fiscal/ecf_dgii` (**ancré sur CLAUDE.md #10** : les options `USD`/`DOP` du champ `TipoMoneda`, byte-gatées dans `out/ecf_plan.json` mais jamais ancrées au mandat — les blocs fiscaux amont gatent le FormaPago (Cardnet) et les états/champs émetteurs, aveugles au jeu de devises, et le bloc « Fiches #10 » ancre les fiches ERPNext/CRM, aveugle au module fiscal ; recomputées de la devise en gras de la contrainte #10 (`USD + DOP`, réutilise l'`exp["devises"]` déjà parsé · zéro duplication) : (a) `spec[moneda].options` == #10 ORDRE exact (mord ajout/retrait/réordonnancement), (b) l'artefact byte-gaté idem, (c) le spec DÉCLARE l'ancrage via un champ DISTINCT `options_source` — `source` restant legit. `null` (taux TipoCambio à confirmer · #8) — qui cite `CLAUDE.md #10` + chaque devise, (d) la prose README qui se réclame de #10 cite le jeu EXACT + toute mention `moneda`/`devise` d'une paire de devises concordante — étendre/renommer une devise dans #10 ET le spec de façon cohérente reste byte-vert tout en faisant émettre un `TipoMoneda` hors mandat, même « vert trompeur » que la marque SEO §Entités ou les tokens branding #4 ; un claim absent échoue AUSSI) **et** de l'IMPUTABILITÉ des confirmations préalables VPS `devops/deploy_runbook` (3e surface : la ligne « Confirmations préalables VPS » du README annote chaque confirmation d'un couple `nom` (**owner** · **réf-contrôle**) — ex. `taux_commission` (Direction · audit_5d D1.1) — data-derived des `confirmations` de `out/deploy_runbook.json` byte-gaté CROISÉES avec le rapport `qa/audit_5d` byte-gaté ; les blocs DevOps amont ne gatent que le COMPTE et l'ENSEMBLE des noms, aveugles à QUI porte chaque confirmation et à QUEL contrôle la trace ; cross-anchor à trois : owner run-book ⊆ owner du contrôle audit_5d cité (tolère le suffixe « / UAF ») · owner README ⊆ owner run-book (tolère l'abréviation « Fiscaliste eCF » ⊂ « Compta Fiscaliste eCF ») · réf README == contrôle réel présent dans audit_5d ET dans la source — réaffecter un owner, misattribuer le contrôle (`D1.1`→`D2.1`) ou citer un contrôle FANTÔME (`D9.9`) mord ; une confirmation tracée DOIT rester annotée, une annotation d'un nom fantôme échoue AUSSI) **et** du COMPTE CANONIQUE de rôles RBAC (**ancré sur CLAUDE.md §Architecture cible** « RBAC N rôles » : le nombre `50` recopié dans le contrat `rbac_50_roles.json` — le champ déclaré `cible_rbac_roles` ET la taille réelle du catalogue `len(roles)` — dans les schémas (`const: 50`) et dans la fiche `03_agents/erpnext_backend/AGENT.md` (« RBAC **50 rôles** exactement » + 3 autres mentions), sans jamais être re-dérivé du mandat ; les gates RBAC amont ne prouvent que la cohérence INTERNE contrat↔MANIFEST↔READMEs de volet, aveugles à l'ancrage au mandat — changer « RBAC 50 rôles » dans CLAUDE.md ou faire dévier le catalogue de façon interne-cohérente (cible ET len alignés ailleurs) laisserait contrat & fiche affirmer « 50 exactement » en CONTREDISANT le mandat, en silence ; recomputé de §Architecture cible : (a) contrat `cible_rbac_roles` == N, (b) taille réelle `len(roles)` == N, (c) cible == réel (mandat atteint), (d) la fiche porte l'invariant « RBAC **N rôles** exactement » + aucune mention « M rôles » périmée ; même patron que les tokens branding #4 / la persona OTOIA §Architecture cible ; un claim absent échoue AUSSI) **et** des comptes du run-book d'application `rbac/RBAC_50_ROLES_SPEC.md` §7 (la NOTE « ✅ … livré en-repo » restitue sur une surface DISTINCTE des READMEs userperm_gen/roleprofile_gen déjà gatés trois comptes data-derived — « 28 templates `Company` (portée `entite`) », « **6 profils** (5 portails métier + console technique `plateforme`) » et « **50 rôles de façon bijective** » — jamais gatés hors le §3 (ventilation par portail) ; recomputés du contrat `rbac_50_roles.json` byte-gaté : 28 == |scope==entite|, 6 == |portails distincts| (1 Role Profile par portail), 5 == portails métier hors `plateforme`, 50 == |rôles| ; bumper une portée `entite`→`groupe` ou ajouter/retirer un portail dans le contrat périme la note en silence pendant que les READMEs de volet restent verts ; le blockquote `> ` est neutralisé avant match ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ des configs runtime du Chat OTOIA dans la cellule « Métrique vérifiée » de la fiche `03_agents/frontend_console/AGENT.md` (restitue TROIS attributs data-derived du montage — `persona Amélie` · `langues FR/EN/ES` · `` `endpoint: null` `` — aveugles au bloc chat_otoia amont qui ne gate que le COMPTE blocks/mounts/portails, et NON couverts par le gate persona/capabilities ancré sur CLAUDE.md §Architecture cible (qui vise spec/mounts/MANIFEST/README/oracle du test, jamais cette fiche) ; recomputés des artefacts byte-gatés `out/chat_mount.json` (5 configs) + `out/MANIFEST.json` : persona == `MANIFEST.persona.nom` (le « QC » est le qualificatif de marque, gardé en littéral non gaté), langues `FR/EN/ES` == `MANIFEST.langues` majuscule, `endpoint: null` == **AFFIRMATION D'HONNÊTETÉ #6/#8** exigeant que CHAQUE mount ait `endpoint == null` — un endpoint fabriqué dans l'artefact fait de la fiche « null » un MENSONGE et mord ; cohérences croisées : `MANIFEST.persona.nom`/`langues` == CHAQUE mount (montage désynchronisé mordu) · `endpoint_statut == 'a_confirmer'` ⇔ endpoints TOUS null ; un claim absent échoue AUSSI) **et** des DEUX métriques data-derived du bundle SEO restituées dans la fiche `03_agents/seo/AGENT.md` (le tableau « Livrable réellement produit » recopie, à côté du compte de mots-clés déjà gaté sur la fiche, deux sorties gatées AVANT sur le seul README : L25 « graphe JSON-LD : **N nœuds** (`Organization` racine + M `Residence`) » et L26 « **N pages** × FR/EN/ES + `x-default` » ; recomputées des mêmes artefacts byte-gatés `seo_schema_org.json`/`MANIFEST.json` et `seo_hreflang.json` : L25 total nœuds == `schema_org_nodes`, racine == le `@type` singleton, M == `listings`, type listing == le `@type` uniforme des listings + cohérence arithmétique `total == 1 + M` ; L26 nombre de pages == `|pages|` de la carte + jeu de langues de contenu == celui de l'artefact (set-diff, absent ET en trop) ; RENOMMER le `@type`, AJOUTER/RETIRER un projet ou ÉCHANGER une langue périme la fiche pendant que les artefacts disent autre chose — même classe que la cellule chat_otoia de la fiche Frontend Console (fiche-agent qui restitue des attributs gatés seulement en agrégat) ; un claim absent échoue AUSSI) **et** de la colonne « Entité » de la §4 de `rbac/RBAC_50_ROLES_SPEC.md` (**ancrée sur CLAUDE.md §Entités** : la table « Cartographie rôles ↔ entités » liste en colonne de tête les 7 entités NOMMÉES du mandat `WAF`/`WA SRL`/`AC Arias Cuevas`/`Consortium ECR DR`/`Helios RD`/`Ploutos`/`9060 QC` + le périmètre consolidé « Groupe » — deux sources faisant autorité, CLAUDE.md §Entités pour l'orthographe & le descripteur parenthétique et `rbac_50_roles.json` pour l'ENSEMBLE des `entite_principale` réellement portées, aveugles au §3 (comptes par portail) / §7 (comptes run-book) / blocs fiches (`entite` d'UN rôle) ; (a) ENSEMBLE §4 == distinct `entite_principale` du contrat (set-diff : ni fantôme ni manquante), (b) ANCRAGE chaque entité NOMMÉE de CLAUDE.md est une ligne §4 avec son descripteur parenthétique EXACT (absence comprise), (c) chaque entité nommée du mandat est portée ≥1× par le contrat (aucune décorative) — ajouter au contrat un rôle porté par une entité absente de §4, renommer une entité dans CLAUDE.md §Entités (`9060 QC→9061 QC`) ou dériver un descripteur mord ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ de l'ENSEMBLE des portails métier + l'exclusion nommée de la console technique dans la fiche `03_agents/frontend_console/AGENT.md` (le bloc « triplets par workspace » recompute cartes/liens/rôles PAR portail mais reste aveugle à QUELS portails composent l'ensemble ; la fiche énumère à la main L11 « 5 portails rôle métier (Ventes / Construction / Achat / Compta / Direction) » et affirme L46 que « la console technique `plateforme` (6ᵉ portail RBAC) est **exclue** » ; (a) l'ensemble ORDONNÉ des noms courts == les workspaces métier de `frontend/portails/out/MANIFEST.json` byte-gaté (label « OTO » sans préfixe, comme l'énumération des onglets Mobile) ET, DOUBLE ANCRAGE, leurs ids == `portails_business` du contrat RBAC (l'« égalité stricte » que la fiche revendique elle-même) ; (b) `plateforme` == l'UNIQUE portail RBAC hors métier (∈ portails distincts des rôles ∖ `portails_business`), c'est le 6ᵉ (total distinct == 6 = 5 métier + 1) — un échange (Achat→Marketing), un retrait, un réordre, un renommage de la console technique dans le contrat (`plateforme`→`console`) ou un contrat qui PROMEUT `plateforme` en métier mord ; un claim absent échoue AUSSI) **et** de l'IDENTITÉ de l'endpoint GPU RunPod ComfyUI (l'id du pod `t68akvx80wv092` + le tag « Flux dev fp8 » cités dans les fiches BIM/Rendu + le master prompt, recomputés du bullet §1 de `AGENTS_EXISTING_ASSETS.md` ; toute fiche mentionnant RunPod doit citer l'id EXACT — token backtické de forme pod == canonique — + le tag EXACT ; un pod re-créé mord les 3 fiches, la disparition totale échoue AUSSI) **et** des évènements d'émission e-CF (ENSEMBLE des évènements émetteurs « réservation/contrat » ⨯ ensemble exclu des brouillons `lead/visite/devis` **disjoint** des émetteurs — jamais de facture sur brouillon — ⨯ ENSEMBLE des `base_field` ⨯ rôle unique `compta-fiscaliste-ecf` double-ancré RBAC portail compta ⨯ FormaPago défaut `3`/Tarjeta/Cardnet #10, recomputés de `ecf_plan.json`) **et** de l'identité d'infrastructure VPS (IP `153.75.250.214` + les DEUX conteneurs Docker `erpnext-backend-1`/`erpnext-frontend-1` recomputés de CLAUDE.md §VPS ; direction copie→source — toute doc `.md` du mandat, hors journaux `05_activity_log`/`daily_reports`, citant une IPv4 ou un conteneur de forme `--1` doit == la valeur canonique : une migration serveur ou un renommage du projet compose mord toutes les copies périmées, disparition totale de la surface échoue AUSSI) == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec @@ -520,6 +520,24 @@ confirmations DevOps ou la carte de renormalisation par archétype 4Big. Cohére croisée en bonus : l'ensemble des `control` == `MANIFEST.open_items` (le manifeste qui résume le rapport) — mord un manifeste désynchronisé de son propre rapport. +Troisième surface du **même README** de l'audit 5D : la **table « ## Les 5 +dimensions »** énumère **à la main**, par ligne, le triplet (`id`, `titre`, +`reference`) de chaque dimension du cadre (`| **D1** | Traçabilité & anti-invention | +ISA 500 — Éléments probants |` …). Le tout **premier** bloc audit 5D ne gate que le +**compte** (« 5 dimensions ») — un **wildcard aveugle à QUELLE dimension** : +**renommer** un libellé (« Traçabilité & anti-invention » → …) ou **permuter une +norme de référence** (D3 fiscal citant la mauvaise `Ley 32-23`, D1 probatoire citant +`ISA 501` au lieu d'`ISA 500`) laisserait le compte à 5 et **passerait en silence** — +alors que c'est l'audit de **second niveau** que l'auditeur/banquier lit : une norme +de conformité périmée est une **affirmation fausse** (l'invention même que #6 +interdit). On RECOMPUTE la liste **ordonnée** `{id:(titre, reference)}` depuis +`qa/audit_5d/out/audit_report.json` (`dimensions[]`, byte-gaté) et on exige que la +table README l'énumère **exactement** : `id` par **ensemble** (absent **et** en trop +mordus), **ordre** préservé (D1→D5), puis `(titre, reference)` **par ligne**. +Cohérence croisée en bonus : les dimensions du rapport == celles de `audit_spec.json` +(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). + Même classe enfin sur le **run-book de déploiement VPS** (livrable `devops/deploy_runbook`) : le compte de **phases** (fiche agent `devops` — « graphe de **7 phases ordonnées** » **et** « exécute les 7 phases sur le VPS ») **et** le diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index fc5f315..139cd44 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -2019,6 +2019,82 @@ if a5_oi_exp is not None: good(f"Audit 5D · énumération open items {sorted(a5_exp_set)} " f"(control→dimension→propriétaire) == audit_report.open_items") +# ============================================================================ +# Audit 5D (3e surface du MÊME README) · IDENTITÉ des 5 DIMENSIONS — le libellé +# (`titre`) ET la NORME de référence (`reference`) de chaque dimension du cadre — +# même classe de « vert trompeur », transcrite À LA MAIN dans un tableau. +# ---------------------------------------------------------------------------- +# Le 1er bloc audit_5d gate le COMPTE de dimensions (« 5 dimensions (5D) ») — un +# WILDCARD AVEUGLE à QUELLE dimension : renommer « Traçabilité & anti-invention » +# ou permuter une norme de référence (D3 fiscal citant la mauvaise Ley, D1 +# probatoire citant `ISA 501` au lieu d'`ISA 500`) laisse le compte à 5 pendant que +# l'artefact dit autre chose. La table « ## Les 5 dimensions » du README +# (05_deliverables_mvp/qa/audit_5d/README.md) énumère PAR LIGNE le triplet +# (`id`, `titre`, `reference`) alors que audit_report.dimensions[] (byte-gaté par +# check_artifacts ⇒ faisant autorité, chaque dimension recomposée du catalogue de +# contrôles à chaque build) fait foi. C'est l'audit de SECOND NIVEAU que +# l'auditeur/banquier lit : une norme de référence périmée (D2 AML citant une +# mauvaise Ley 155-17, D3 fiscal la mauvaise Ley 32-23) est une AFFIRMATION de +# conformité fausse — exactement l'invention que #6 interdit, qu'aucune suite +# tests/ (qui teste des FONCTIONS de contrôle, pas la prose) n'attrape. On RECOMPUTE +# la liste ORDONNÉE {id:(titre, reference)} depuis audit_report.dimensions[] (zéro +# duplication du modèle de l'auditeur · #6) et on exige que la table README +# l'énumère EXACTEMENT : identité par ENSEMBLE d'`id` (absent ET en trop mordus), +# ORDRE préservé (D1→D5), puis (titre, reference) par LIGNE. Même patron que +# l'énumération des open items ci-dessus. Cohérence croisée en bonus : les +# dimensions du rapport == celles de audit_spec.json (l'INPUT dont le rapport +# dérive) — mord un builder qui renommerait/permuterait une dimension en aval du +# spec. Un claim absent (table disparue) échoue AUSSI (traçabilité #6). +# ============================================================================ +try: + a5d_dim = load("qa/audit_5d/out/audit_report.json")["dimensions"] + a5d_exp = {d["id"]: (d["titre"], d["reference"]) for d in a5d_dim} + a5d_order = [d["id"] for d in a5d_dim] + a5d_spec = load("qa/audit_5d/audit_spec.json")["dimensions"] + a5d_spec_map = {d["id"]: (d["titre"], d["reference"]) for d in a5d_spec} + if a5d_spec_map != a5d_exp: + bad(f"Audit 5D · incohérence artefacts : audit_spec dimensions {sorted(a5d_spec_map)} " + f"divergent du rapport {sorted(a5d_exp)} (titre/reference) → le rapport doit " + f"dériver du spec") +except (OSError, KeyError, ValueError) as e: + bad(f"Audit 5D · dimensions illisibles/inattendues : {e}"); a5d_exp = None +if a5d_exp is not None: + try: + a5d_lines = open(f"{D}/qa/audit_5d/README.md", + encoding="utf-8").read().splitlines() + except OSError as e: + bad(f"Audit 5D · README illisible : {e}"); a5d_lines = None + if a5d_lines is not None: + # ligne de table = « | **D1** | | | » (gras + PAS de + # sous-point ⇒ distingue des lignes open items « | D1.1 · … | ») + a5d_row_re = re.compile( + r"^\|\s*\*\*(D\d+)\*\*\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*$") + a5d_seen, a5d_seen_order = {}, [] + for ln in a5d_lines: + m = a5d_row_re.match(ln) + if m: + a5d_seen[m.group(1)] = (m.group(2).strip(), m.group(3).strip()) + a5d_seen_order.append(m.group(1)) + a5d_exp_ids, a5d_got_ids = set(a5d_exp), set(a5d_seen) + if a5d_got_ids != a5d_exp_ids: + bad(f"Audit 5D · IDENTITÉ des dimensions — table README énumère " + f"{sorted(a5d_got_ids)} MAIS audit_report dit {sorted(a5d_exp_ids)} " + f"→ régénérer le README") + elif a5d_seen_order != a5d_order: + bad(f"Audit 5D · IDENTITÉ des dimensions — ORDRE README {a5d_seen_order} " + f"MAIS audit_report dit {a5d_order} → régénérer le README") + else: + a5d_mism = [i for i in a5d_order if a5d_seen[i] != a5d_exp[i]] + if a5d_mism: + for i in a5d_mism: + bad(f"Audit 5D · dimension {i} — README dit " + f"(titre={a5d_seen[i][0]!r}, reference={a5d_seen[i][1]!r}) MAIS " + f"audit_report dit (titre={a5d_exp[i][0]!r}, " + f"reference={a5d_exp[i][1]!r})") + else: + good(f"Audit 5D · IDENTITÉ des 5 dimensions {a5d_order} " + f"(id→titre→reference) == audit_report.dimensions") + # ============================================================================ # DevOps · run-book de déploiement VPS (phases ordonnées / confirmations # préalables) — même classe de « vert trompeur », transcrite À LA MAIN dans