diff --git a/05_activity_log/2026-08-01.md b/05_activity_log/2026-08-01.md index ff84444..1704d26 100644 --- a/05_activity_log/2026-08-01.md +++ b/05_activity_log/2026-08-01.md @@ -183,3 +183,65 @@ absolu) · **7 gates re-verts**. é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. diff --git a/ci/README.md b/ci/README.md index 6b61aa5..5e84f64 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 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 ») **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, 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) == 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 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 ») **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, 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) == 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 @@ -735,6 +735,34 @@ réécrite · contrat rôle renommé · fiche `modules Website→Selling` · fic (sous-revendication) · fiche modules over/under-claim · ligne de rôle supprimée (INTROUVABLE) ; restauré = vert. +Même classe encore sur les **deux fiches d'identité des rôles RBAC faisabilité** — +`03_agents/rendu/AGENT.md` (rôle `faisabilite-rendu-3d`) et +`03_agents/ifc_speckle/AGENT.md` (rôle `faisabilite-ifc-speckle`). Chaque ligne +« Contact in-repo (vérifié) » recopie **à la main**, du contrat `rbac_50_roles.json`, +les attributs data-derived RESTITUÉS par la fiche : `erpnext_role_name`, `portail` +(`construction`), `entite_principale` (`WA SRL`), `niveau` (`2`), plus `nom_fr` & +`modules` (rendu) et surtout les **deux `permissions_cibles`** — `File` R/W/create + +`Faisabilité` R (rendu, lecture seule) ou R/W (ifc). C'est la carte que l'agent ERPNext +Backend lit pour seeder le rôle porteur des rendus / de l'export IFC→GLB. Aucun gate ne +comparait ces valeurs au contrat : le README `frontend/portails` n'est gaté que sur des +**comptes de rôles par Workspace**, aveugles à l'identité de CE rôle. Élargir +`Faisabilité` R→R/W (le rôle Rendu 3D gagnerait l'écriture sur un DocType qu'il ne doit +que LIRE), changer le `niveau`/l'`entite_principale`, renommer le rôle ou permuter un +module laisserait la fiche périmée en silence. On RECOMPUTE chaque attribut du contrat +(zéro duplication · #6) ; les verbes de permission sont rendus en **ordre canonique** +(read→R · write→W · create/delete/… en toutes lettres) de sorte qu'élargir OU rétrécir +change la chaîne attendue (`R/W/create` ne satisfait plus `R/W/create/delete`). +**Différence notable avec la fiche mobile** : la `description` de ces deux fiches est +une **paraphrase éditoriale** (le contrat porte « (… — CLAUDE.md) » / « les modèles » +que la fiche condense) — elle n'est donc **pas** gatée verbatim (ne rien réécrire de +correct pour satisfaire un gate · #6) ; seuls les attributs **structurés**, transcrits à +l'exact, sont contraints. 11 morsures vérifiées (5 côté contrat, 6 côté fiche) : 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` · fiche ifc perm `R/W→R/W/create` · 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é = vert. + Un claim **absent** échoue aussi (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur (bash/git/python3), zéro réseau. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 53d5de8..80d23aa 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -3308,6 +3308,103 @@ if mob_role is not None and mob_txt is not None: bad(f"Fiche Mobile · {MOB} — description « {_plain(md.group(1))} » " f"MAIS contrat « {exp_desc} » → description périmée, régénérer") +# ============================================================================ +# Fiches Faisabilité (03_agents/rendu/AGENT.md · 03_agents/ifc_speckle/AGENT.md) · +# les FICHES D'IDENTITÉ des rôles RBAC `faisabilite-rendu-3d` et +# `faisabilite-ifc-speckle` — même classe de « vert trompeur » que la Fiche Mobile. +# ---------------------------------------------------------------------------- +# La ligne « Contact in-repo (vérifié) » de chaque fiche (rendu:34 · ifc_speckle:34) +# recopie À LA MAIN, DEPUIS le contrat rbac_50_roles.json (source de vérité RBAC, +# schéma-validé · byte-gaté par les 3 MANIFEST recomputés), les attributs data-derived +# du rôle : son `erpnext_role_name`, son `portail`, son `entite_principale`, son +# `niveau`, ses `modules` (rendu), son `nom_fr` (rendu) et ses `permissions_cibles` +# (deux doctypes chacune : `File` R/W/create + `Faisabilité` R ou R/W). C'est la carte +# que l'agent ERPNext Backend lit pour seeder le rôle porteur des rendus / de l'export +# IFC→GLB. AUCUN gate ne comparait ces valeurs au contrat : le README frontend/portails +# n'est gaté que sur des COMPTES de rôles par Workspace, AVEUGLES à l'identité de CE +# rôle. Éditer le contrat — élever le `scope_donnees`, changer le `niveau`, +# l'`entite_principale`, renommer le rôle, ajouter/retirer un module ou élargir la +# permission (`Faisabilité` R→R/W : le rôle Rendu 3D gagnerait l'écriture sur le +# DocType Faisabilité qu'il ne doit que LIRE) — laisse la fiche périmée en silence +# pendant que le contrat dit autre chose ⇒ l'agent câblerait le mauvais rôle +# (l'invention même que #6 interdit). Aucune suite tests/ (qui teste des FONCTIONS de +# résolution RBAC, pas la prose d'une fiche) n'attrape ce « vert trompeur ». On +# RECOMPUTE chaque attribut RESTITUÉ par la fiche depuis le contrat (zéro duplication) +# et on exige que la fiche l'énonce EXACTEMENT. Les verbes de permission sont rendus +# dans un ORDRE CANONIQUE (read→R · write→W · create/delete/submit/… en toutes +# lettres, ordre fixe) : élargir OU rétrécir une permission change la chaîne attendue +# (`R/W` ne satisfait plus `R/W/create`, ni l'inverse) ; verbe inconnu ⇒ ROUGE +# (étendre le gate). Un claim absent échoue AUSSI (traçabilité). NB : contrairement +# à la Fiche Mobile, la DESCRIPTION de ces deux fiches est une PARAPHRASE éditoriale +# (le contrat porte « (… — CLAUDE.md) » / « les modèles » que la fiche condense) — +# elle n'est donc PAS gatée verbatim (ne rien réécrire de correct pour un gate · #6) ; +# seuls les attributs STRUCTURÉS, eux transcrits à l'exact, sont contraints. +# ============================================================================ +_CANON_VERB = [("read", "R"), ("write", "W"), ("create", "create"), + ("delete", "delete"), ("submit", "submit"), ("cancel", "cancel"), + ("amend", "amend"), ("print", "print"), ("report", "report")] +FICHES_FAIS = [ + {"file": "03_agents/rendu/AGENT.md", "rid": "faisabilite-rendu-3d", + "attrs": ["role", "nom_fr", "portail", "entite", "niveau", "modules", "perms"]}, + {"file": "03_agents/ifc_speckle/AGENT.md", "rid": "faisabilite-ifc-speckle", + "attrs": ["role", "portail", "entite", "niveau", "perms"]}, +] +try: + _fais_roles = load("rbac/rbac_50_roles.json")["roles"] +except (OSError, KeyError, ValueError, TypeError) as e: + bad(f"Fiches Faisabilité · rbac_50_roles.json illisible/inattendu : {e}") + _fais_roles = None +if _fais_roles is not None: + for fc in FICHES_FAIS: + rid, tag = fc["rid"], fc["file"].split("/")[1] + cand = [r for r in _fais_roles if r.get("id") == rid] + ftxt = read_norm(fc["file"]) + if ftxt is None: + continue + if len(cand) != 1: + bad(f"Fiche {tag} · le contrat porte {len(cand)} rôle(s) `{rid}` " + f"(attendu exactement 1) → contrat changé de forme, vérifier") + continue + r = cand[0] + seg = re.search(rf"→ rôle `{re.escape(rid)}` \|(.+?)\|", ftxt) + if seg is None: + bad(f"Fiche {tag} · {fc['file']} — ligne de contact du rôle `{rid}` " + f"INTROUVABLE (un claim absent est une régression · #6)") + continue + cell = seg.group(1) + # permissions_cibles → chaîne « `Doctype` R/W/create + `Autre` R », ordre canon + _perm_parts, _unknown = [], set() + for p in (r.get("permissions_cibles") or []): + acts = set(p.get("actions", [])) + _unknown |= acts - {k for k, _ in _CANON_VERB} + sig = "/".join(tok for k, tok in _CANON_VERB if k in acts) + _perm_parts.append(f"`{p.get('doctype')}` {sig}") + perm_str = " + ".join(_perm_parts) + if _unknown: + bad(f"Fiche {tag} · rôle `{rid}` — verbe(s) inconnu(s) " + f"{sorted(_unknown)} dans le contrat → étendre le gate") + mods = r.get("modules") or [] + mod_pat = (rf"module `{re.escape(mods[0])}`(?![+`\w])" if len(mods) == 1 + else r"modules " + r"\+".join(f"`{re.escape(m)}`" for m in mods)) + _PATS = { + "role": (rf"rôle ERPNext \*\*`{re.escape(r['erpnext_role_name'])}`\*\*", + r["erpnext_role_name"]), + "nom_fr": (rf"« {re.escape(r['nom_fr'])} »", r["nom_fr"]), + "portail": (rf"portail {re.escape(r['portail'])}(?![\w])", r["portail"]), + "entite": (rf"(?