From 63760a144b9ce67fa0e7a5fb12c323522f760c76 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 22:40:04 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20?= =?UTF-8?q?=C2=B7=20RBAC/apply=5Fplan=20(2e=20surface)=20:=20la=20TABLE=20?= =?UTF-8?q?=C2=AB=20Run-book=20g=C3=A9n=C3=A9r=C3=A9=20(SPEC=20=C2=A77)=20?= =?UTF-8?q?=C2=BB=20=E2=80=94=20le=20GRAPHE=20d'application=20ordonn=C3=A9?= =?UTF-8?q?=20que=20l'agent=20ERPNext=20suit=20pas=20=C3=A0=20pas=20?= =?UTF-8?q?=E2=80=94=20avait=20ses=20colonnes=20d'IDENTIT=C3=89=20(#=20?= =?UTF-8?q?=C2=B7=20Responsable=20=C2=B7=20D=C3=A9pend=20de)=20NON=20GAT?= =?UTF-8?q?=C3=89ES.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le bloc « apply_plan » existant de check_readme_claims.sh (bloc 2) ne gate QUE la ligne AGRÉGÉE « Génération réelle » (8 comptes consolidés). Les 3 colonnes d'identité de la table run-book (README:38-46) sont data-derived de out/apply_plan.json (byte-gaté par check_artifacts) : # = order · Responsable = responsable (worker/vps/worker+vps) · Dépend de = les n°s d'ordre des depends_on. Elles étaient un WILDCARD. PIÈGE : check_artifacts ne prouve QUE apply_plan==build (byte-for-byte) et le bloc de comptes est aveugle à l'identité des lignes → passer le Responsable de l'étape 4 (worker+vps→vps) ou retirer une dépendance de l'étape 6 (4,5→4) laissait la prose périmée pendant que l'artefact dit autre chose. Un responsable périmé (étape VPS attribuée au worker) ou une dépendance périmée (Role Profile importé AVANT les Role) est un hazard réel — le risque même que le run-book veut prévenir — qu'aucune suite tests/ (qui teste des fonctions, pas la table commitée) n'attrape. Même classe que la colonne « Type » de roleprofile_gen et la table demo/scenarios. Nouveau sous-bloc « 2bis) apply_plan — TABLE Run-book » : (1) IDENTITÉ par étape — chaque ligne porte EXACTEMENT #=order + Responsable=responsable + Dépend de=n°s d'ordre des depends_on (deps extraits par digits ⇒ robuste au séparateur/em-dash) ; colonne « Étape » libre (paraphrase). (2) IDENTITÉ d'ensemble — {ordres des lignes} == {ordres artefact} sans doublon (aucune ligne FANTÔME, aucune étape MANQUANTE). Cohérences croisées (mordent un plan INTERNEMENT incohérent) : ordres contigus 1..N · responsable ∈ {worker,vps,worker+vps} · toute dépendance pointe en ARRIÈRE (n° < n° de l'étape). Un claim absent échoue AUSSI. 8 morsures vérifiées : README étape4 responsable worker+vps→vps (câblage) · README étape6 Dépend de 4,5→4 (dep manquante) · README étape3 1,2→1,2,5 (dep fantôme) · ligne FANTÔME #7 (7≠6) · étape 5 SUPPRIMÉE (INTROUVABLE + 5≠6) · artefact étape2 responsable vps→worker (README stale) · artefact étape2 dep en AVANT (graphe incohérent) · artefact étape1 responsable hors domaine (robot) ; restauré = green : 6 lignes == 6 étapes · responsables/deps == artefact · ordres 1..6 · exit 0. État courant : aucune valeur périmée (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail « 2ᵉ surface rbac/apply_plan ») mis à jour · working tree byte-restauré · 7 gates re-verts. Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-07-31.md | 46 ++++++++++++++++++++++ ci/README.md | 20 +++++++++- ci/check_readme_claims.sh | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index ef58af8..575cb0d 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -2502,3 +2502,49 @@ verrouillés) · `guard_constraints` · `validate_json`. `check_regression` · `guard_constraints` · `validate_json`). - **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). - **Auto-score 4Big** : 96/100. + +## Sprint 8 · buffer · RBAC/apply_plan (2e surface) · la TABLE « Run-book généré (SPEC §7) » · le GRAPHE d'application ordonné (# · Responsable · Dépend de) +- **Constat** : le README `rbac/apply_plan` porte la table « ## Run-book généré + (SPEC §7 · ordre d'application VPS) » (README:38-46) — les **6 étapes ordonnées** + que l'agent ERPNext suit **pas à pas** sur le VPS. Ses trois colonnes d'identité + sont data-derived de `out/apply_plan.json` : `#` = `order`, `Responsable` = + `responsable` (`worker`/`vps`/`worker+vps`), `Dépend de` = les **n°s d'ordre** des + `depends_on`. Le bloc « apply_plan » existant de `check_readme_claims.sh` (bloc 2) + ne gate QUE la ligne AGRÉGÉE « Génération réelle » (8 comptes consolidés) — ces + trois colonnes étaient un **wildcard**. +- **Data-derived** : `out/apply_plan.json` (liste de 6 étapes · `order`/`id`/ + `responsable`/`depends_on`), byte-gaté par `check_artifacts`. Ce dernier ne prouve + QUE `apply_plan==build` (byte-for-byte) ; l'identité des LIGNES de la table (qui + fait quoi · dans quel ordre) restait non gatée. +- **Preuve reproduite** : passer `Responsable` de l'étape 4 (`worker+vps`) à `vps`, + ou retirer une dépendance de l'étape 6 (`4, 5`→`4`) dans le README → + `check_readme_claims` **exit 0** (le bloc 2 ne voyait que les comptes agrégés). Un + responsable périmé (étape VPS attribuée au worker) ou une dépendance périmée + (`Role Profile` importé **avant** les `Role`) est un hazard réel — le risque même + que le run-book veut prévenir — qu'aucune suite `tests/` (qui teste des fonctions, + pas la table commitée) n'attrape. +- **Gate ajouté** (`ci/check_readme_claims.sh`, sous-bloc « 2bis) apply_plan — TABLE + Run-book ») : (1) **IDENTITÉ par étape** — chaque ligne porte EXACTEMENT + `#`=`order` + `Responsable`=`responsable` + `Dépend de`=n°s d'ordre des + `depends_on` (deps extraits par digits ⇒ robuste au séparateur / à l'em-dash) ; + la colonne « Étape » reste libre (paraphrase). (2) **IDENTITÉ d'ensemble** — + {ordres des lignes} == {ordres de l'artefact}, sans doublon (aucune ligne FANTÔME, + aucune étape MANQUANTE). Cohérences croisées en bonus (mordent un plan INTERNEMENT + incohérent) : ordres contigus `1..N` · `responsable` ∈ {worker, vps, worker+vps} · + toute dépendance pointe en **arrière** (n° < n° de l'étape ⇒ pas de cycle/renvoi + avant). Un claim absent échoue AUSSI. +- **8 morsures vérifiées** : README étape4 responsable `worker+vps`→`vps` (câblage) · + README étape6 `Dépend de 4,5`→`4` (dépendance manquante) · README étape3 + `1,2`→`1,2,5` (dépendance fantôme) · ligne FANTÔME `#7` (7≠6) · étape 5 SUPPRIMÉE + (INTROUVABLE + 5≠6) · artefact étape2 responsable `vps`→`worker` (README stale) · + artefact étape2 dep en AVANT (`verify-http-qa` · graphe incohérent) · artefact + étape1 responsable `robot` hors domaine. Restauré = green : 6 lignes == 6 étapes · + responsables/deps == artefact · ordres 1..6 contigus · exit 0. +- **État courant** : aucune valeur périmée (anti-invention #6, rien à réécrire) — le + défaut est la surface **ungated** (colonnes wildcardées). `ci/README.md` (table + + détail « 2ᵉ surface `rbac/apply_plan` ») mis à jour · working tree byte-restauré + (arbre propre · `git clean` cité ici · ci-allow) · **7 gates re-verts** (`check_readme_claims` · `check_artifacts` · + `check_docs` · `check_ci_integrity` · `check_regression` · `guard_constraints` · + `validate_json`). +- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). +- **Auto-score 4Big** : 96/100. diff --git a/ci/README.md b/ci/README.md index e7c7d78..63af57b 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) == 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) == 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 @@ -183,7 +183,23 @@ exige que le **nombre de lignes de profil** de la table == `|profiles|` (aucune ligne fantôme d'un profil inventé absent du contrat), et en bonus deux cohérences croisées qui mordent un MANIFEST **internement** incohérent : `portails_metier + portails_techniques == role_profiles` **et** ces comptes -recomptent les flags `metier` réels des profils. +recomptent les flags `metier` réels des profils. **2ᵉ surface `rbac/apply_plan`** : +le bloc ci-dessus ne gatait que la ligne **agrégée** de comptes ; la table +« ## Run-book généré (SPEC §7) » — le **graphe d'application ordonné** que l'agent +ERPNext suit pas à pas — laissait ses colonnes `#`/`Responsable`/`Dépend de` en +**wildcard**. Or ces trois colonnes sont data-derived de `out/apply_plan.json` +(byte-gaté par `check_artifacts`) : le `#` = `order`, `Responsable` = +`responsable` (`worker`/`vps`/`worker+vps`), `Dépend de` = les n°s d'ordre des +`depends_on`. Un responsable périmé (étape VPS attribuée au worker) ou une +dépendance périmée (Role Profile importé **avant** les Role) est un hazard réel +qu'aucune suite `tests/` (qui teste des fonctions, pas la table commitée) n'attrape +et que le bloc de comptes ignore. On recompute `#`/`responsable`/`deps` depuis +`apply_plan.json` et on exige que la table les énonce **exactement** (la colonne +« Étape » reste libre — paraphrase) ; l'**identité d'ensemble** (lignes de la table +== étapes de l'artefact) mord toute ligne fantôme ou étape manquante, et trois +cohérences croisées mordent un plan **internement** incohérent : ordres contigus +`1..N` sans doublon · `responsable` ∈ {`worker`, `vps`, `worker+vps`} · toute +dépendance pointe en **arrière** (n° < n° de l'étape). Même classe enfin côté **SEO** : le total et la ventilation par langue des mots-clés (« **258** mots-clés (fr=87 · en=87 · es=84) ») étaient transcrits **à diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index aa73b40..c7695d9 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -780,6 +780,80 @@ if ap_man is not None and ap_rd is not None: bad(f"RBAC apply_plan · {AP_RD} dit {'/'.join(ma.groups())} MAIS MANIFEST " f"dit {'/'.join(ap_exp)} ({ap_src}) → régénérer le README") +# 2bis) apply_plan — IDENTITÉ de la TABLE « Run-book généré » (SPEC §7) ---------- +# Le bloc 2 ci-dessus ne gate que la ligne AGRÉGÉE « Génération réelle » (comptes). +# La table « ## Run-book généré » énumère, PAR étape, trois données data-derived de +# apply_plan.json (byte-gaté par check_artifacts) : le n° d'ordre (#), le +# RESPONSABLE (worker/vps/worker+vps) et la colonne « Dépend de » (les n°s d'ordre +# des `depends_on`). C'est le GRAPHE D'APPLICATION ORDONNÉ que l'agent ERPNext suit +# pas à pas — un responsable ou une dépendance périmés (ex. Role Profile importé +# AVANT les Role, ou une étape VPS attribuée au worker) sont un hazard réel, +# invisible à check_artifacts (qui ne prouve QUE apply_plan==build) ET au bloc de +# comptes (aveugle à l'identité des lignes ; ces colonnes étaient un WILDCARD). +# Même classe que la colonne « Type » de roleprofile_gen et la table demo/scenarios. +# On RECOMPUTE #/responsable/deps depuis apply_plan.json (zéro duplication) et on +# exige que la table les énonce EXACTEMENT. Une étape MANQUANTE ou une ligne FANTÔME +# échoue AUSSI (identité d'ensemble). La colonne « Étape » reste libre (paraphrase). +# Cohérences croisées en bonus (mordent un plan INTERNEMENT incohérent) : ordres +# contigus 1..N sans doublon · responsable ∈ {worker, vps, worker+vps} · toute +# dépendance pointe en ARRIÈRE (n° < n° de l'étape ⇒ pas de cycle/renvoi avant). +try: + ap_plan = load("rbac/apply_plan/out/apply_plan.json") +except (OSError, KeyError, ValueError) as e: + bad(f"RBAC apply_plan · run-book illisible : {e}"); ap_plan = None +if ap_plan is not None: + id2order = {s["id"]: s["order"] for s in ap_plan} + ALLOWED_RESP = {"worker", "vps", "worker+vps"} + orders = sorted(s["order"] for s in ap_plan) + if orders == list(range(1, len(ap_plan) + 1)): + good(f"RBAC apply_plan · run-book — ordres contigus 1..{len(ap_plan)} " + f"(aucun trou/doublon)") + else: + bad(f"RBAC apply_plan · run-book — ordres NON contigus {orders} " + f"(attendu 1..{len(ap_plan)}) → plan incohérent") + # Lignes de la table lues en RAW : la colonne 1 ENTIÈRE ⇒ scope la table + # run-book (seule table du README à 1ʳᵉ colonne numérique · vérifié). + try: + ap_lines = open(AP_RD, encoding="utf-8").read().splitlines() + except OSError as e: + bad(f"RBAC apply_plan · {AP_RD} illisible : {e}"); ap_lines = [] + table_orders, rows_by_order = [], {} + for ln in ap_lines: + cells = [c.strip() for c in ln.strip().strip("|").split("|")] + if len(cells) == 4 and re.fullmatch(r"\d+", cells[0]): + o = int(cells[0]); table_orders.append(o); rows_by_order[o] = cells + if sorted(table_orders) == orders and len(table_orders) == len(set(table_orders)): + good(f"RBAC apply_plan · run-book — {len(table_orders)} lignes == " + f"{len(ap_plan)} étapes (aucune FANTÔME/MANQUANTE)") + else: + bad(f"RBAC apply_plan · run-book — lignes table {sorted(table_orders)} ≠ " + f"étapes artefact {orders} (ligne en trop/manquante ou doublon)") + for s in sorted(ap_plan, key=lambda x: x["order"]): + o, resp = s["order"], s["responsable"] + deps = sorted(id2order[x] for x in s["depends_on"]) + if resp not in ALLOWED_RESP: + bad(f"RBAC apply_plan · étape {o} — responsable artefact « {resp} » " + f"hors {sorted(ALLOWED_RESP)} (plan incohérent)") + if any(d >= o for d in deps): + bad(f"RBAC apply_plan · étape {o} — dépendance en AVANT {deps} " + f"(≥ {o}) : graphe incohérent") + row = rows_by_order.get(o) + if row is None: + bad(f"RBAC apply_plan · étape {o} — ligne INTROUVABLE dans {AP_RD} " + f"(attendu responsable={resp} deps={deps or '—'})"); continue + got_resp = row[1] + got_deps = sorted(int(x) for x in re.findall(r"\d+", row[3])) + if got_resp == resp: + good(f"RBAC apply_plan · étape {o} — responsable « {got_resp} » == artefact") + else: + bad(f"RBAC apply_plan · étape {o} — {AP_RD} dit responsable « {got_resp} » " + f"MAIS artefact dit « {resp} » → régénérer le README") + if got_deps == deps: + good(f"RBAC apply_plan · étape {o} — Dépend de {got_deps or '—'} == artefact") + else: + bad(f"RBAC apply_plan · étape {o} — {AP_RD} « Dépend de » = {got_deps or '—'} " + f"MAIS artefact dit {deps or '—'} → régénérer le README") + # 3) userperm — ventilation par mécanisme d'enforcement ------------------------- UP_RD = f"{D}/rbac/userperm_gen/README.md" try: