From 733c73ae3fa81b07de3afac2d2d95d8c20b733c6 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 17:07:38 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20L?= =?UTF-8?q?75=20=C2=B7=20Domaine=20RBAC/fixtures=5Fgen=20:=20le=20CATALOGU?= =?UTF-8?q?E=20des=20DocTypes=20`custom`=20=C3=A0=20cr=C3=A9er=20sur=20le?= =?UTF-8?q?=20VPS=20(=C2=AB=20`Faisabilit=C3=A9`,=20`Publiciste=20Log`,=20?= =?UTF-8?q?`CONFOTUR=20Application`,=20`API=20Access`=20=C2=BB)=20ET=20la?= =?UTF-8?q?=20S=C3=89PARATION=20DES=20POUVOIRS=20(=C2=AB=20`set=5Fuser=5Fp?= =?UTF-8?q?ermissions`=20n'est=20=C3=A9mis=20que=20pour=20le=20r=C3=B4le?= =?UTF-8?q?=20**RBAC=20Admin**=20=C2=BB)=20=C3=A9taient=20transcrits=20?= =?UTF-8?q?=C3=80=20LA=20MAIN=20dans=20le=20README=20du=20module=20`rbac/f?= =?UTF-8?q?ixtures=5Fgen`=20sans=20AUCUN=20gate=20d'IDENTIT=C3=89.=20Le=20?= =?UTF-8?q?bloc=20racine=20ne=20gate=20que=20le=20COMPTE=20(=C2=AB=2050=20?= =?UTF-8?q?r=C3=B4les=20/=20116=20DocPerm=20=C2=BB=20via=20l'agr=C3=A9gat?= =?UTF-8?q?=20RBAC=20de=20l'apply=5Fplan)=20=E2=80=94=20surface=20distinct?= =?UTF-8?q?e.=20Ces=20deux=20faits=20sont=20DATA-DERIVED=20:=20catalogue?= =?UTF-8?q?=20=3D=20`custom=5Fdoctypes=5Fa=5Fconfirmer`=20de=20`rbac/fixtu?= =?UTF-8?q?res=5Fgen/out/MANIFEST.json`=20(les=20DocTypes=20`custom:=20tru?= =?UTF-8?q?e`=20du=20contrat=20`rbac=5F50=5Froles.json`)=20=C2=B7=20single?= =?UTF-8?q?ton=20s=C3=A9curit=C3=A9=20=3D=20`{role=20|=20set=5Fuser=5Fperm?= =?UTF-8?q?issions=3D=3D1}`=20de=20`out/custom=5Fdocperm.json`=20(=3D=20`O?= =?UTF-8?q?TO=20Plateforme=20RBAC=20Admin`)=20=E2=80=94=20les=20deux=20art?= =?UTF-8?q?efacts=20byte-gat=C3=A9s=20par=20check=5Fartifacts.=20Aucun=20g?= =?UTF-8?q?ate=20ne=20comparait=20ces=20ENSEMBLES=20=C3=A0=20la=20prose=20?= =?UTF-8?q?:=20AJOUTER=20un=20DocType=20custom=20au=20contrat=20(MANIFEST?= =?UTF-8?q?=20reb=C3=A2tit=205=20entr=C3=A9es)=20=C2=B7=20RENOMMER/=C3=89C?= =?UTF-8?q?HANGER=20l'un=20des=204=20=C2=B7=20PROMOUVOIR=20un=202e=20r?= =?UTF-8?q?=C3=B4le=20porteur=20du=20flag=20(=C3=A9l=C3=A9vation=20de=20pr?= =?UTF-8?q?ivil=C3=A8ge)=20ferait=20d=C3=A9river=20la=20prose=20en=20silen?= =?UTF-8?q?ce=20pendant=20que=20l'artefact=20dit=20autre=20chose=20?= =?UTF-8?q?=E2=80=94=20=C2=AB=20vert=20trompeur=20=C2=BB=20qu'aucune=20sui?= =?UTF-8?q?te=20tests/=20(qui=20teste=20des=20FONCTIONS=20de=20mapping,=20?= =?UTF-8?q?pas=20la=20prose=20du=20README)=20n'attrape=20=E2=86=92=20nouve?= =?UTF-8?q?au=20bloc=20recomputant=20le=20catalogue=20depuis=20MANIFEST=20?= =?UTF-8?q?et=20le=20singleton=20depuis=20custom=5Fdocperm=20(z=C3=A9ro=20?= =?UTF-8?q?duplication=20du=20contrat=20du=20g=C3=A9n=C3=A9rateur=20#6)=20?= =?UTF-8?q?et=20exigeant=20que=20la=20prose=20l'=C3=A9num=C3=A8re/le=20nom?= =?UTF-8?q?me=20EXACTEMENT.=20Contr=C3=B4le=20par=20ENSEMBLE=20(absent=20E?= =?UTF-8?q?T=20en=20trop=20mordus=20via=20set-diff=20=C2=B7=20accents/cass?= =?UTF-8?q?e=20normalis=C3=A9s=20via=20unicodedata).=20Coh=C3=A9rences=20c?= =?UTF-8?q?rois=C3=A9es=20en=20bonus=20:=20le=20catalogue=20est=20NON=20VI?= =?UTF-8?q?DE,=20sans=20doublon=20et=20TRI=C3=89=20(byte-d=C3=A9terminisme?= =?UTF-8?q?=20du=20g=C3=A9n=C3=A9rateur)=20=C2=B7=20la=20s=C3=A9paration?= =?UTF-8?q?=20des=20pouvoirs=20est=20un=20SINGLETON=20(ni=20vide=20?= =?UTF-8?q?=E2=80=94=20garde=20vacante=20=E2=80=94=20ni=20multiple=20?= =?UTF-8?q?=E2=80=94=20=C3=A9l=C3=A9vation=20de=20privil=C3=A8ge).=20Un=20?= =?UTF-8?q?claim=20absent=20=C3=A9choue=20AUSSI.=206=20morsures=20v=C3=A9r?= =?UTF-8?q?ifi=C3=A9es=20:=20=C3=A9change=20d'un=20nom=20de=20DocType=20(F?= =?UTF-8?q?aisabilit=C3=A9=E2=86=92Faisabilite2)=20capt=C3=A9=20(absents?= =?UTF-8?q?=3D[faisabilite]=20en=20trop=3D[faisabilite2])=20=C2=B7=20sous-?= =?UTF-8?q?ensemble=20(retrait=20API=20Access)=20capt=C3=A9=20=C2=B7=20r?= =?UTF-8?q?=C3=B4le=20nomm=C3=A9=20faux=20(RBAC=20Admin=E2=86=92Ventes=20C?= =?UTF-8?q?onseiller)=20capt=C3=A9=20=C2=B7=20=C3=A9num=C3=A9ration=20supp?= =?UTF-8?q?rim=C3=A9e=20(INTROUVABLE)=20=C2=B7=202e=20r=C3=B4le=20promu=20?= =?UTF-8?q?au=20flag=20dans=20l'artefact=20(singleton=20cass=C3=A9)=20capt?= =?UTF-8?q?=C3=A9=20=C2=B7=20catalogue=20MANIFEST=20non=20tri=C3=A9=20(byt?= =?UTF-8?q?e-d=C3=A9terminisme)=20capt=C3=A9=20;=20restaur=C3=A9=20=3D=20g?= =?UTF-8?q?reen=20:=20catalogue=20[API=20Access,CONFOTUR=20Application,Fai?= =?UTF-8?q?sabilit=C3=A9,Publiciste=20Log]=20=3D=3D=20MANIFEST=20=C2=B7=20?= =?UTF-8?q?singleton=20OTO=20Plateforme=20RBAC=20Admin=20=3D=3D=20custom?= =?UTF-8?q?=5Fdocperm.=20=C3=89tat=20courant=20:=20aucun=20ensemble=20p?= =?UTF-8?q?=C3=A9rim=C3=A9=20(anti-invention=20#6,=20rien=20=C3=A0=20r?= =?UTF-8?q?=C3=A9=C3=A9crire)=20=E2=80=94=20le=20d=C3=A9faut=20est=20la=20?= =?UTF-8?q?surface=20ungated.=20ci/README.md=20(table=20+=20d=C3=A9tail)?= =?UTF-8?q?=20mis=20=C3=A0=20jour=20=C2=B7=207=20gates=20re-verts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-07-31.md | 68 ++++++++++++++++++++++++++ ci/README.md | 27 +++++++++- ci/check_readme_claims.sh | 92 +++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index bb7d0a3..c30ff50 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,73 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## Session `20260731_170112` · Buffer S8 · Domaine RBAC/fixtures_gen : le **catalogue des DocTypes `custom`** à créer sur le VPS (« `Faisabilité`, `Publiciste Log`, `CONFOTUR Application`, `API Access` ») ET la **séparation des pouvoirs** (« `set_user_permissions` n'est émis que pour le rôle **RBAC Admin** ») étaient transcrits À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de l'apply_plan). + +**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement +close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont +gaté les domaines tests, portails, RBAC (3 volets : roleprofile/apply_plan/userperm), +SEO, CRM (pipeline vente + séparation des pouvoirs + commissions), mobile (quadruplet ++ onglets), Legal/CONFOTUR, DevOps, QA (audit 5D ×2 + 4Big ×2), Démo, Frontend/Chat +OTOIA, recette et Fiscal/e-CF. Cette session cible le **module `rbac/fixtures_gen`** — +jamais gaté hors son compte agrégé — dont deux faits data-derived du README pointent +les artefacts générés sans être recomputés par aucun gate. + +**Dérive silencieuse fermée** : +- `05_deliverables_mvp/rbac/fixtures_gen/README.md:56` : « listés dans + `MANIFEST.custom_doctypes_a_confirmer` (`Faisabilité`, `Publiciste Log`, `CONFOTUR + Application`, `API Access`) » — l'ENSEMBLE des DocTypes `custom: true` à créer. +- `…/README.md:48` : « le flag `set_user_permissions` n'est émis que pour le rôle + **RBAC Admin** » — le SINGLETON `{role | set_user_permissions == 1}`, cœur sécurité + du module (« séparation des pouvoirs · défense en profondeur »). +- Sources faisant autorité (byte-gatées par `check_artifacts`) : + `rbac/fixtures_gen/out/MANIFEST.json.custom_doctypes_a_confirmer` (les DocTypes + `custom` du contrat `rbac_50_roles.json`) et `out/custom_docperm.json` (le seul + rôle porteur du flag = `OTO Plateforme RBAC Admin`). +- Le bloc racine ne gate QUE le **compte** (« 50 rôles / 116 DocPerm » via l'agrégat + RBAC de l'apply_plan). Aucun gate ne comparait ces ENSEMBLES aux artefacts : + **AJOUTER** un DocType `custom` au contrat (le MANIFEST rebâtit 5 entrées), + **RENOMMER/ÉCHANGER** l'un des 4, ou **PROMOUVOIR** un 2ᵉ rôle porteur de + `set_user_permissions` (élévation de privilège) ferait dériver la prose en silence — + « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS de mapping, pas + la prose) n'attrape. +- **État courant** : **aucun ensemble périmé** — le catalogue (4) et le singleton + recoupent les artefacts exactement (anti-invention #6, rien à réécrire). Le défaut + est la **surface ungated**. + +**Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute le catalogue depuis +`MANIFEST.custom_doctypes_a_confirmer` et le singleton depuis `custom_docperm.json` +(zéro duplication du contrat du générateur · #6) et exige que la prose l'énumère/le +nomme **exactement** — contrôle par ENSEMBLE (absent ET en trop mordus via set-diff · +accents/casse normalisés via `unicodedata`). Cohérences croisées en bonus : le +catalogue est **non vide, sans doublon et trié** (byte-déterminisme du générateur) ; +la séparation des pouvoirs est un **singleton** (ni vide — garde vacante — ni multiple +— élévation de privilège). Un claim absent échoue AUSSI. + +### Preuve (6 morsures vérifiées + restauration verte) + +| Injection | Résultat | +|---|---| +| README échange nom DocType (`Faisabilité → Faisabilite2`) | ✗ absents=`[faisabilite]` · en trop=`[faisabilite2]` · exit 1 | +| README retrait `API Access` (sous-ensemble) | ✗ absents=`[api access]` · exit 1 | +| README rôle nommé faux (`RBAC Admin → Ventes Conseiller`) | ✗ nomme « Ventes Conseiller » MAIS flag porté par `OTO Plateforme RBAC Admin` · exit 1 | +| README énumération du catalogue supprimée | ✗ énumération du catalogue custom INTROUVABLE · exit 1 | +| artefact 2ᵉ rôle promu au flag (`custom_docperm`) | ✗ set_user_permissions porté par `[…RBAC Admin, …Ventes Conseiller]` (attendu 1) · séparation cassée · exit 1 | +| artefact catalogue MANIFEST non trié | ✗ non trié/dédupliqué · exit 1 | +| restauré | ✓ catalogue `[API Access, CONFOTUR Application, Faisabilité, Publiciste Log]` == MANIFEST · singleton `OTO Plateforme RBAC Admin` == custom_docperm · exit 0 | + +- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe + descriptif (classe « catalogue RBAC `rbac/fixtures_gen` »). + +Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · +`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · +`check_readme_claims`. + +**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; +édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact). + +**Auto-score 4Big** : 96/100. + +--- + ## Session `20260731_163112` · Buffer S8 · Domaine CRM/Commissions : la **cross-cohérence** du barème vendeurs (« cœur du livrable ») — les états **brouillon exclus** (`lead/visite/devis/abandonné`), les **champs de base** (`montant_reservation`, `montant_contrat`) et l'**invariant 3** « on ne commissionne que des états soumis » — était énumérée À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ ; le bloc racine ne gate que le COMPTE de tests (« 25 », via l'agrégat « Total CRM »). **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement diff --git a/ci/README.md b/ci/README.md index f6014a9..4681150 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 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 ») == 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 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`) == 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 @@ -422,6 +422,31 @@ l'**invariant 3** du module, non vide. 5 morsures vérifiées : échange brouill (`reservation→lead`, invariant 3 cassé) · promotion de l'état `abandonne` en soumis dans l'artefact (draft set change, « en trop » dans la prose) ; restauré = vert. +Même classe enfin sur le **catalogue RBAC** (livrable `rbac/fixtures_gen`) — deux +faits data-derived transcrits **à la main** dans le README. (1) La liste des DocTypes +`custom` à créer avant import « (`Faisabilité`, `Publiciste Log`, `CONFOTUR +Application`, `API Access`) » (README §« Application sur VPS ») == l'ensemble +`custom_doctypes_a_confirmer` d'`rbac/fixtures_gen/out/MANIFEST.json` (les DocTypes +`custom: true` du contrat RBAC). (2) La **séparation des pouvoirs** — « le flag +`set_user_permissions` n'est émis que pour le rôle **RBAC Admin** » (README:48) == +l'ensemble `{role | set_user_permissions == 1}` d'`out/custom_docperm.json`. Le bloc +racine ne gate que le **compte** (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de +l'apply_plan) — jamais l'**identité** de ces deux ensembles. AJOUTER un DocType +`custom` au contrat, RENOMMER/ÉCHANGER l'un des 4 ou PROMOUVOIR un 2ᵉ rôle porteur du +flag (élévation de privilège) ferait dériver la prose en silence pendant que +l'artefact byte-gaté dit autre chose — dérive qu'aucune suite `tests/` (qui teste des +FONCTIONS de mapping, pas la prose du README) n'attrape. On RECOMPUTE chaque ensemble +depuis SON artefact (zéro duplication du contrat du générateur · #6) et on exige que +la prose l'énumère/le nomme **exactement** (absent ET en trop mordus via set-diff · +accents/casse normalisés). Cohérences croisées en bonus : le catalogue est **non +vide, sans doublon et trié** (byte-déterminisme du générateur) et la séparation des +pouvoirs est un **singleton** (ni vide — garde vacante — ni multiple — élévation de +privilège). 6 morsures vérifiées : échange d'un nom de DocType (`Faisabilité → +Faisabilite2`) · sous-ensemble (retrait `API Access`) · rôle nommé faux (`RBAC Admin +→ Ventes Conseiller`) · énumération supprimée (INTROUVABLE) · 2ᵉ rôle promu au flag +dans l'artefact (singleton cassé) · catalogue MANIFEST non trié (byte-déterminisme) ; +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 612dc51..3cd5c57 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -1713,6 +1713,98 @@ if cm_ev is not None: bad(f"CRM commissions · {CM_RD} — énumère {sorted(bs)} MAIS plan " f"{sorted(cm_base)}{detail} → régénérer le README") +# ============================================================================ +# RBAC/fixtures_gen · le CATALOGUE des DocTypes `custom` à créer sur le VPS et la +# SÉPARATION DES POUVOIRS (le rôle unique porteur de `set_user_permissions`) — +# deux faits data-derived transcrits À LA MAIN dans le README sans AUCUN gate +# d'IDENTITÉ. +# ---------------------------------------------------------------------------- +# Le générateur rbac_fixtures_gen émet out/MANIFEST.json et out/custom_docperm.json +# (byte-gatés par check_artifacts ⇒ faisant autorité) : +# • MANIFEST.custom_doctypes_a_confirmer = l'ensemble des DocTypes `custom: true` +# du contrat rbac_50_roles.json (ceux à CRÉER avant import — le reste est natif +# v15). Le README §« Application sur VPS » l'ÉNUMÈRE À LA MAIN, en nommant même +# le champ : « listés dans `MANIFEST.custom_doctypes_a_confirmer` (`Faisabilité`, +# `Publiciste Log`, `CONFOTUR Application`, `API Access`) ». +# • custom_docperm.json porte le flag `set_user_permissions`. Le CŒUR sécurité du +# module (« séparation des pouvoirs · défense en profondeur ») est que ce flag +# n'est émis que pour UN SEUL rôle — le README:48 dit « n'est émis que pour le +# rôle **RBAC Admin** ». C'est l'ensemble {role | set_user_permissions=1}. +# Le bloc racine ne gate QUE le COMPTE de tests fixtures (« 50 rôles / 116 DocPerm » +# via l'agrégat RBAC de l'apply_plan) — jamais l'IDENTITÉ de ces deux ensembles. +# Aucun gate ne les comparait à l'artefact : AJOUTER un DocType `custom` au contrat +# (le MANIFEST rebâtit 5 entrées) OU RENOMMER/ÉCHANGER l'un des 4 laisse la prose à +# ses 4 noms périmés ; PROMOUVOIR un 2ᵉ rôle porteur de `set_user_permissions` (ou +# renommer RBAC Admin) laisse la prose « un seul rôle RBAC Admin » fausse — « vert +# trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la +# prose du README) n'attrape. Même patron que l'énumération des onglets Mobile, des +# brouillons CRM ou des open items de l'audit 5D. On RECOMPUTE chaque ensemble +# depuis SON artefact byte-gaté (zéro duplication du contrat du générateur #6) et on +# exige que la prose l'énumère/le nomme EXACTEMENT (absent ET en trop via set-diff · +# accents/casse normalisés). Cohérence croisée en bonus : le catalogue est NON VIDE, +# sans doublon et TRIÉ (byte-déterminisme du générateur) ; la séparation des pouvoirs +# est un SINGLETON (ni vide — la garde serait vacante — ni multiple — élévation de +# privilège). Un claim absent échoue AUSSI (traçabilité). +# ============================================================================ +FX_RD = f"{D}/rbac/fixtures_gen/README.md" +def _fx_acc(s): + return "".join(c for c in unicodedata.normalize("NFKD", s) + if unicodedata.category(c) != "Mn").strip().lower() +try: + fx_cdt = load("rbac/fixtures_gen/out/MANIFEST.json")["custom_doctypes_a_confirmer"] + fx_perm = load("rbac/fixtures_gen/out/custom_docperm.json") + fx_sup = sorted({p["role"] for p in fx_perm if p.get("set_user_permissions")}) +except (OSError, KeyError, ValueError, TypeError) as e: + bad(f"RBAC fixtures · artefact illisible/inattendu : {e}"); fx_cdt = None +if fx_cdt is not None: + # bonus : le catalogue est un vrai ENSEMBLE trié non vide (byte-déterminisme) + if not fx_cdt: + bad("RBAC fixtures · custom_doctypes_a_confirmer VIDE → aucun DocType custom " + "à créer ? (contrat suspect)") + elif fx_cdt != sorted(set(fx_cdt)): + bad(f"RBAC fixtures · custom_doctypes_a_confirmer {fx_cdt} non trié/dédupliqué " + f"→ générateur non déterministe (attendu {sorted(set(fx_cdt))})") + else: + good(f"RBAC fixtures · catalogue custom {fx_cdt} — ensemble trié, sans doublon") + # bonus : séparation des pouvoirs — un SEUL rôle porte set_user_permissions + if len(fx_sup) != 1: + bad(f"RBAC fixtures · set_user_permissions porté par {fx_sup} (attendu 1 seul " + f"rôle) → séparation des pouvoirs cassée (élévation de privilège possible)") + else: + good(f"RBAC fixtures · set_user_permissions — singleton {fx_sup[0]!r} " + f"(défense en profondeur intacte)") + fx_rd = read_norm(FX_RD) + if fx_rd is not None: + # 1) README — énumération du catalogue custom (parenthèse après le champ nommé) + me = re.search(r"custom_doctypes_a_confirmer`?\s*\(([^)]*)\)", fx_rd) + if me is None: + bad(f"RBAC fixtures · {FX_RD} — énumération du catalogue custom INTROUVABLE " + f"(attendu {sorted(fx_cdt)})") + else: + got = {_fx_acc(t) for t in re.findall(r"`([^`]+)`", me.group(1))} + exp = {_fx_acc(x) for x in fx_cdt} + if got == exp: + good(f"RBAC fixtures · {FX_RD} — catalogue custom {sorted(got)} == " + f"MANIFEST.custom_doctypes_a_confirmer") + else: + miss = sorted(exp - got); extra = sorted(got - exp) + detail = (f" · absents={miss}" if miss else "") + \ + (f" · en trop={extra}" if extra else "") + bad(f"RBAC fixtures · {FX_RD} — énumère {sorted(got)} MAIS MANIFEST " + f"{sorted(exp)}{detail} → régénérer le README") + # 2) README:48 — le rôle NOMMÉ porteur de set_user_permissions (séparation) + if len(fx_sup) == 1: + mr = re.search(r"émis que pour le rôle \*\*([^*]+)\*\*", fx_rd) + if mr is None: + bad(f"RBAC fixtures · {FX_RD} — claim « set_user_permissions n'est émis " + f"que pour le rôle ** ** » INTROUVABLE (attendu {fx_sup[0]!r})") + elif _fx_acc(mr.group(1)) in _fx_acc(fx_sup[0]): + good(f"RBAC fixtures · {FX_RD} — rôle nommé « {mr.group(1).strip()} » ⊆ " + f"{fx_sup[0]!r} (le seul porteur du flag)") + else: + bad(f"RBAC fixtures · {FX_RD} — nomme « {mr.group(1).strip()} » MAIS " + f"set_user_permissions porté par {fx_sup[0]!r} → régénérer le README") + sys.exit(1 if FAIL else 0) PY rc=$?