diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index f3e2ae9..ba3d7a5 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -2606,3 +2606,61 @@ verrouillés) · `guard_constraints` · `validate_json`. `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 · DevOps/deploy_runbook (2e surface) · la TABLE « Plan de phases généré » · le GRAPHE de portage VPS ordonné (# · Responsable · Modules · Dépend de) +- **Constat + DÉFAUT RÉEL capté** : le README `devops/deploy_runbook` porte la table + « ## Plan de phases généré » (README:45-53) — les **7 phases ordonnées** que + l'agent DevOps suit phase par phase au portage VPS. Ses colonnes d'identité sont + data-derived de `out/deploy_runbook.json` : `#`=`order`, `Responsable`=`responsable` + (`worker`/`vps`/`worker+vps`), **Modules**=l'ensemble des modules gated déployés, + `Dépend de`=les n°s d'ordre des `depends_on`. Le bloc DevOps existant de + `check_readme_claims.sh` ne gate QUE **deux comptes agrégés** (phases ×2 dans la + fiche `devops` · confirmations dans le README) — ces quatre colonnes étaient un + **WILDCARD**. Contrairement aux surfaces buffer précédentes (« aucune valeur + périmée »), ici la table AVAIT **DÉRIVÉ** : elle **omettait** `mobile/app_config` + (phase 5) **ET** `qa/acceptance` (phase 7) — **20 modules listés sur 22** — pendant + que le `MANIFEST` prouvait la couverture **bijective 22/22 vs CI** (`counts.modules + == modules_mapped == 22`). Vert trompeur caractéristique : l'agent DevOps suivant la + table aurait **sauté 2 modules** au déploiement. +- **Data-derived** : `out/deploy_runbook.json` (7 phases · `order`/`id`/`responsable`/ + `depends_on`/`modules[]`) et sa source `deploy_spec.json.module_phase` (22 modules, + autorité byte-gatée par `check_artifacts` · couverture bijective prouvée vs jobs CI). + `check_artifacts` ne prouve QUE `deploy_runbook==build` — l'identité des LIGNES de la + table (qui déploie quoi · dans quel ordre · après quoi) restait non gatée. +- **Preuve reproduite** : retirer `mobile/app_config` de la phase 5 du README → + `check_readme_claims` **exit 0** avant le gate (le bloc ne voyait que phases=7 & + confirmations=7). Un responsable périmé (phase VPS attribuée au worker) ou une + dépendance périmée (Frontend avant RBAC) est un hazard réel qu'aucune suite `tests/` + (qui teste des fonctions, pas la table commitée) n'attrape. +- **Correction d'abord (artefact = autorité · #6)** : la table étant STALE, README + corrigé pour matcher l'artefact byte-gaté — phase 5 `+mobile/app_config` (& libellé + « + config mobile »), phase 7 `+qa/acceptance`. Consommateur régénéré : le + `qa/audit_4big/out/quality_report.json` enregistre la taille en octets de chaque + README en `evidence` (5414→5468 pour deploy_runbook) — rebuild `audit_4big` (score/ + verdict/totaux INCHANGÉS, seule l'évidence-taille bouge · reproducibility gate). +- **Gate ajouté** (`ci/check_readme_claims.sh`, sous-bloc « 2bis) deploy_runbook — + TABLE Plan de phases ») : (1) **IDENTITÉ par phase** — chaque ligne porte EXACTEMENT + `#`=`order` + `Responsable`=`responsable` + **Modules** (ensemble de code-spans) == + modules de la phase + `Dépend de`=n°s d'ordre des `depends_on` (deps extraits par + digits ⇒ robuste au séparateur) ; colonne « Phase » libre (paraphrase). (2) + **IDENTITÉ d'ensemble** — {ordres des lignes} == {ordres de l'artefact} (aucune + phase FANTÔME/MANQUANTE) ET l'**UNION** des cellules Modules == les **22** modules de + l'artefact (aucun module OUBLIÉ/en trop — le défaut même capté). 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 la phase ⇒ pas de cycle). Un claim absent échoue AUSSI. +- **8 morsures vérifiées** : README phase5 omet `mobile/app_config` (défaut original, + `manquants=[mobile/app_config]`) · README phase7 omet `qa/acceptance` (défaut + original) · README phase5 responsable `worker+vps`→`vps` (câblage) · README phase6 + `Dépend de 4,5`→`4` (dépendance manquante) · module FANTÔME `ghost/mod` en phase5 + (`en_trop`) · ligne FANTÔME `#8` (8≠7) · phase 6 SUPPRIMÉE (INTROUVABLE + 6≠7) · + artefact phase2 responsable `vps`→`worker` (README stale). Restauré = green : + 7 lignes == 7 phases · union Modules 22==22 · responsables/deps/modules == artefact · + ordres 1..7 contigus · exit 0. +- **État courant** : le défaut STALE est **corrigé** (README byte-aligné sur + l'artefact) ET la surface est désormais **gatée**. `ci/README.md` (ligne synthèse + + détail « 2ᵉ surface `devops/deploy_runbook` ») mis à jour · **7 gates 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/05_deliverables_mvp/devops/deploy_runbook/README.md b/05_deliverables_mvp/devops/deploy_runbook/README.md index 8f6c8cd..50e8aff 100644 --- a/05_deliverables_mvp/devops/deploy_runbook/README.md +++ b/05_deliverables_mvp/devops/deploy_runbook/README.md @@ -48,9 +48,9 @@ chiffrée dans le catalogue de confirmations. | 2 | vps | DocTypes custom | `crm/dossier_vente`, `legal/confotur` | 1 | | 3 | worker+vps | RBAC (Role + DocPerm + UP + Role Profile) | `rbac/fixtures_gen`, `rbac/userperm_gen`, `rbac/roleprofile_gen`, `rbac/apply_plan` | 2 | | 4 | worker+vps | Workflow vente + règles métier | `crm/workflow_vente`, `crm/commissions`, `fiscal/ecf_dgii` | 2, 3 | -| 5 | worker+vps | Frontend desk (Workspaces + chat OTOIA) | `frontend/portails`, `frontend/chat_otoia` | 3 | +| 5 | worker+vps | Frontend desk (Workspaces + chat OTOIA) + config mobile | `frontend/portails`, `frontend/chat_otoia`, `mobile/app_config` | 3 | | 6 | worker+vps | Contenu & publication | `publiciste`, `faisabilite/generator`, `faisabilite/bancable`, `seo`, `demo/scenarios` | 4, 5 | -| 7 | vps | Vérification QA post-déploiement | `qa/audit_5d`, `qa/audit_4big`, `qa/regression` | 4, 5, 6 | +| 7 | vps | Vérification QA post-déploiement | `qa/acceptance`, `qa/audit_5d`, `qa/audit_4big`, `qa/regression` | 4, 5, 6 | **Confirmations préalables VPS** (consolidées, sourcées) : `custom_modules`, `custom_doctypes`, `taux_commission` (Direction · audit_5d D1.1), `rnc_emisor` diff --git a/05_deliverables_mvp/qa/audit_4big/out/quality_report.json b/05_deliverables_mvp/qa/audit_4big/out/quality_report.json index 50e7a8e..d228dde 100644 --- a/05_deliverables_mvp/qa/audit_4big/out/quality_report.json +++ b/05_deliverables_mvp/qa/audit_4big/out/quality_report.json @@ -816,7 +816,7 @@ "criterion": "DOC", "weight": 20, "passed": true, - "evidence": "README.md (5414 octets)" + "evidence": "README.md (5468 octets)" }, { "criterion": "CONTRAT", diff --git a/ci/README.md b/ci/README.md index 1827fa8..45a157b 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) == 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) == 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 @@ -417,6 +417,24 @@ confirmations du README (les tokens `snake_case`) doit être l'ensemble **exact* `confirmations_ouvertes` — mord l'**échange** d'un nom de confirmation là où le seul compte (reste 7) resterait aveugle. +**2ᵉ surface `devops/deploy_runbook`** : la **table « Plan de phases généré »** du +MÊME README (« # | Responsable | Phase | Modules | Dépend de ») porte, par phase, +quatre données data-derived de `out/deploy_runbook.json` (byte-gaté) — le n° d'ordre +(`#`), le `Responsable` (worker/vps/worker+vps), l'ensemble des **Modules** gated +déployés dans la phase, et « Dépend de » (les n°s d'ordre des `depends_on`) — que le +bloc de comptes ci-dessus ne couvrait pas (colonnes **wildcard**). Défaut réel +reproduit : la table **omettait** `mobile/app_config` (phase 5) **et** `qa/acceptance` +(phase 7) — **20 modules listés sur 22** — pendant que le MANIFEST prouvait la +couverture **bijective 22/22 vs CI** : vert trompeur, l'agent DevOps aurait sauté 2 +modules au portage. Le gate recompute `#`/`Responsable`/`Modules`/`Dépend de` depuis +`deploy_runbook.json` et exige que la table les énonce **exactement** par phase ; +identité d'ensemble : {ordres des lignes} == {ordres de l'artefact} (aucune phase +fantôme/manquante) **et** l'**union** des cellules Modules == les 22 modules de +l'artefact (aucun module oublié/en trop). La colonne « Phase » reste libre +(paraphrase). Cohérences croisées en bonus (mordent un plan interne incohérent) : +ordres contigus `1..N` sans doublon · `responsable` ∈ {worker, vps, worker+vps} · +toute dépendance pointe en **arrière** (n° < n° de la phase ⇒ pas de cycle). + Même classe enfin sur le **run-sheet de démo** (livrable `demo/scenarios`) : le nombre de **modules cités**, transcrit **à la main** dans le diagramme d'architecture du README du module (« out/ des modules livrés (**10 modules cités**) »), pointait diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index c963d9b..742ae7a 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -1560,6 +1560,102 @@ if dr_ph is not None: bad(f"DevOps · {DR_RD} — énumération {listed} ≠ confirmations_ouvertes " f"{sorted(dr_open)} → régénérer le README") +# 2bis) deploy_runbook — IDENTITÉ de la TABLE « Plan de phases généré » ----------- +# Le bloc ci-dessus ne gate que DEUX comptes agrégés (phases · confirmations). La +# table « ## Plan de phases généré » énumère, PAR phase, QUATRE données data-derived +# de out/deploy_runbook.json (byte-gaté par check_artifacts) : le n° d'ordre (#), le +# RESPONSABLE (worker/vps/worker+vps), l'ensemble des MODULES gated déployés dans la +# phase, et « Dépend de » (les n°s d'ordre des `depends_on`). C'est le GRAPHE DE +# PORTAGE VPS ORDONNÉ que l'agent DevOps suit phase par phase — un module OUBLIÉ de +# la colonne Modules (donc jamais déployé), un responsable ou une dépendance périmés +# (ex. Frontend avant RBAC) sont un hazard réel, invisible à check_artifacts (qui ne +# prouve QUE deploy_runbook==build) ET au bloc de comptes (aveugle à l'identité des +# lignes ; ces colonnes étaient un WILDCARD). Même classe que la table run-book de +# rbac/apply_plan et la table demo/scenarios. Preuve reproduite : la table omettait +# `mobile/app_config` (phase 5) ET `qa/acceptance` (phase 7) — 20 modules listés sur +# 22 — pendant que le MANIFEST prouvait la couverture BIJECTIVE 22/22 vs CI : vert +# trompeur, l'agent DevOps aurait sauté 2 modules. On RECOMPUTE #/responsable/ +# modules/deps depuis deploy_runbook.json (zéro duplication) et on exige que la table +# les énonce EXACTEMENT. Identité d'ensemble : {ordres des lignes} == {ordres de +# l'artefact} (aucune phase FANTÔME/MANQUANTE) ET l'UNION des cellules Modules == les +# 22 modules de l'artefact (aucun module OUBLIÉ/en trop). La colonne « Phase » 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 la phase ⇒ pas de +# cycle/renvoi avant). +try: + dr_plan = load("devops/deploy_runbook/out/deploy_runbook.json") +except (OSError, KeyError, ValueError) as e: + bad(f"DevOps · run-book illisible : {e}"); dr_plan = None +if dr_plan is not None: + id2order = {p["id"]: p["order"] for p in dr_plan} + ALLOWED_RESP = {"worker", "vps", "worker+vps"} + orders = sorted(p["order"] for p in dr_plan) + all_modules = sorted(x["module"] for p in dr_plan for x in p["modules"]) + if orders == list(range(1, len(dr_plan) + 1)): + good(f"DevOps · run-book — ordres contigus 1..{len(dr_plan)} (aucun trou/doublon)") + else: + bad(f"DevOps · run-book — ordres NON contigus {orders} " + f"(attendu 1..{len(dr_plan)}) → plan incohérent") + # Lignes lues en RAW : la table « Plan de phases généré » est la SEULE à 5 + # colonnes dont la 1ʳᵉ est numérique (l'autre table est `Fichier | Rôle`). + try: + dr_tbl = open(DR_RD, encoding="utf-8").read().splitlines() + except OSError as e: + bad(f"DevOps · {DR_RD} illisible : {e}"); dr_tbl = [] + table_orders, rows_by_order = [], {} + for ln in dr_tbl: + cells = [c.strip() for c in ln.strip().strip("|").split("|")] + if len(cells) == 5 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"DevOps · run-book — {len(table_orders)} lignes == {len(dr_plan)} phases " + f"(aucune FANTÔME/MANQUANTE)") + else: + bad(f"DevOps · run-book — lignes table {sorted(table_orders)} ≠ phases artefact " + f"{orders} (ligne en trop/manquante ou doublon)") + table_modules = sorted(m for r in rows_by_order.values() + for m in re.findall(r"`([^`]+)`", r[3])) + if table_modules == all_modules: + good(f"DevOps · run-book — union des Modules ({len(table_modules)}) == " + f"{len(all_modules)} modules de l'artefact (aucun OUBLIÉ/en trop)") + else: + miss = sorted(set(all_modules) - set(table_modules)) + extra = sorted(set(table_modules) - set(all_modules)) + bad(f"DevOps · run-book — colonne Modules ≠ artefact : manquants={miss or '—'} " + f"en_trop={extra or '—'} → régénérer le README") + for p in sorted(dr_plan, key=lambda x: x["order"]): + o, resp = p["order"], p["responsable"] + deps = sorted(id2order[x] for x in p["depends_on"]) + mods = sorted(x["module"] for x in p["modules"]) + if resp not in ALLOWED_RESP: + bad(f"DevOps · phase {o} — responsable artefact « {resp} » " + f"hors {sorted(ALLOWED_RESP)} (plan incohérent)") + if any(d >= o for d in deps): + bad(f"DevOps · phase {o} — dépendance en AVANT {deps} (≥ {o}) : graphe incohérent") + row = rows_by_order.get(o) + if row is None: + bad(f"DevOps · phase {o} — ligne INTROUVABLE dans {DR_RD} " + f"(attendu responsable={resp} modules={mods} deps={deps or '—'})"); continue + got_resp = row[1] + got_mods = sorted(re.findall(r"`([^`]+)`", row[3])) + got_deps = sorted(int(x) for x in re.findall(r"\d+", row[4])) + if got_resp == resp: + good(f"DevOps · phase {o} — responsable « {got_resp} » == artefact") + else: + bad(f"DevOps · phase {o} — {DR_RD} dit responsable « {got_resp} » MAIS " + f"artefact dit « {resp} » → régénérer le README") + if got_mods == mods: + good(f"DevOps · phase {o} — Modules ({len(got_mods)}) == artefact") + else: + bad(f"DevOps · phase {o} — {DR_RD} Modules {got_mods} MAIS artefact dit " + f"{mods} → régénérer le README") + if got_deps == deps: + good(f"DevOps · phase {o} — Dépend de {got_deps or '—'} == artefact") + else: + bad(f"DevOps · phase {o} — {DR_RD} « Dépend de » = {got_deps or '—'} MAIS " + f"artefact dit {deps or '—'} → régénérer le README") + # ============================================================================ # Démo · run-sheet de pitch (nombre de modules cités) — même classe de « vert # trompeur », transcrit À LA MAIN dans le diagramme d'architecture du README du