From 8961e14ff5efef73c7af2cb36fe0974362725710 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 15:37:13 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20L?= =?UTF-8?q?75=20=C2=B7=20Domaine=20CRM=20(2e=20surface)=20:=20la=20phrase?= =?UTF-8?q?=20=C2=AB=20S=C3=89PARATION=20DES=20POUVOIRS=20=C2=BB=20du=20RE?= =?UTF-8?q?ADME=20workflow=5Fvente=20=C3=89NUM=C3=89RAIT=20=C3=80=20LA=20M?= =?UTF-8?q?AIN=20ses=20transitions=20quatre-yeux=20(=C2=AB=20confirmer=20r?= =?UTF-8?q?=C3=A9servation,=20signer=20contrat,=20approuver=20CONFOTUR,=20?= =?UTF-8?q?annuler/r=C3=A9silier=20=E2=80=94=20`allow=5Fself=5Fapproval=20?= =?UTF-8?q?=3D=200`=20=C2=BB)=20sans=20AUCUN=20gate=20d'IDENTIT=C3=89.=20L?= =?UTF-8?q?e=20bloc=20CRM=20existant=20gate=20le=20COMPTE=20=C3=A9tats/tra?= =?UTF-8?q?nsitions=20(9/11=20=C2=B7=20titre=20pipeline=20README=20workflo?= =?UTF-8?q?w=20+=20=C2=AB=20N=20=C3=A9tats=20=C2=BB=20README=20dossier=5Fv?= =?UTF-8?q?ente)=20MAIS=20PAS=20l'IDENTIT=C3=89=20des=20transitions=20marq?= =?UTF-8?q?u=C3=A9es=20separation=5Fof=5Fduties=20=E2=80=94=20surface=20da?= =?UTF-8?q?ta-derived=20distincte=20du=20M=C3=8AME=20README.=20Cet=20ensem?= =?UTF-8?q?ble=20d=C3=A9rive=20de=20crm/workflow=5Fvente/out/workflow.json?= =?UTF-8?q?=20(byte-gat=C3=A9=20par=20check=5Fartifacts)=20:=20chaque=20tr?= =?UTF-8?q?ansition=20porte=20allow=5Fself=5Fapproval=20;=20l'ensemble=20Q?= =?UTF-8?q?UATRE-YEUX=20=3D=20{action=20|=20allow=5Fself=5Fapproval=20=3D?= =?UTF-8?q?=3D=200}=20=E2=80=94=20pr=C3=A9cis=C3=A9ment=20ce=20que=20le=20?= =?UTF-8?q?README=20=C3=A9num=C3=A8re.=20Le=20bloc=20de=20COMPTE=20reste?= =?UTF-8?q?=20AVEUGLE=20au=20contenu=20:=20(a)=20D=C3=89CLASSER=20une=20tr?= =?UTF-8?q?ansition=20sensible=20(ex.=20=C2=AB=20Signer=20contrat=20=C2=BB?= =?UTF-8?q?=20repasse=20=C3=A0=20self=3D1=20dans=20le=20contrat)=20laisse?= =?UTF-8?q?=20le=20compte=20=C3=A0=209/11=20pendant=20que=20l'artefact=20r?= =?UTF-8?q?etire=20l'action=20de=20l'ensemble=20=E2=80=94=20la=20prose=20r?= =?UTF-8?q?este=20=C3=A0=205=20actions=20FAUSSES=20;=20(b)=20PROMOUVOIR=20?= =?UTF-8?q?une=20transition=20(=C2=AB=20D=C3=A9poser=20CONFOTUR=20=C2=BB?= =?UTF-8?q?=20=E2=86=92=20self=3D0)=20l'ajoute=20sans=20que=20le=20compte?= =?UTF-8?q?=20bouge=20et=20la=20prose=20l'OMET=20;=20(c)=20un=20=C3=89CHAN?= =?UTF-8?q?GE/RENOMMAGE=20d'action=20passe=20aussi=20=E2=80=94=20=C2=AB=20?= =?UTF-8?q?vert=20trompeur=20=C2=BB=20qu'aucune=20suite=20tests/=20(qui=20?= =?UTF-8?q?teste=20des=20FONCTIONS=20de=20graphe,=20pas=20la=20prose)=20n'?= =?UTF-8?q?attrape=20=E2=86=92=20nouveau=20bloc=20dans=20check=5Freadme=5F?= =?UTF-8?q?claims=20recomputant=20l'ENSEMBLE=20des=20actions=20quatre-yeux?= =?UTF-8?q?=20depuis=20workflow.json=20(z=C3=A9ro=20duplication=20du=20fla?= =?UTF-8?q?g=20du=20g=C3=A9n=C3=A9rateur)=20et=20exigeant=20que=20la=20phr?= =?UTF-8?q?ase=20l'=C3=A9num=C3=A8re=20EXACTEMENT.=20Contr=C3=B4le=20par?= =?UTF-8?q?=20ENSEMBLE=20(absent=20ET=20en=20trop=20mordus=20via=20set-dif?= =?UTF-8?q?f),=20normalisation=20casse=20ignor=C3=A9e=20+=20suffixe=20?= =?UTF-8?q?=C2=AB=20(perdu)=20=C2=BB=20=C3=A9lid=C3=A9=20(les=20deux=20tra?= =?UTF-8?q?nsitions=20vers=20l'=C3=A9tat=20Perdu=20=E2=80=94=20Annuler/R?= =?UTF-8?q?=C3=A9silier=20=E2=80=94=20que=20la=20prose=20groupe=20=C2=AB?= =?UTF-8?q?=20annuler/r=C3=A9silier=20=C2=BB).=20M=C3=AAme=20patron=20que?= =?UTF-8?q?=20l'=C3=A9num=C3=A9ration=20des=20onglets=20Mobile,=20des=20op?= =?UTF-8?q?en=20items=20de=20l'audit=205D=20ou=20de=20la=20carte=20de=20re?= =?UTF-8?q?normalisation=204Big.=20Coh=C3=A9rence=20crois=C3=A9e=20en=20bo?= =?UTF-8?q?nus=20:=200=20<=20|quatre-yeux|=20<=20|transitions|=20(l'ensemb?= =?UTF-8?q?le=20sensible=20est=20NON=20VIDE=20=E2=80=94=20sinon=20la=20gar?= =?UTF-8?q?de=20est=20vacante=20=E2=80=94=20et=20STRICT=20=E2=80=94=20sino?= =?UTF-8?q?n=20tout=20le=20graphe=20serait=20bloqu=C3=A9=20en=20quatre-yeu?= =?UTF-8?q?x).=20Un=20claim=20absent=20=C3=A9choue=20AUSSI=20(4=20morsures?= =?UTF-8?q?=20v=C3=A9rifi=C3=A9es=20:=20d=C3=A9classement=20Signer=20contr?= =?UTF-8?q?at=20capt=C3=A9=20l=C3=A0=20o=C3=B9=20le=20compte=20reste=209/1?= =?UTF-8?q?1=20=C2=B7=20promotion=20D=C3=A9poser=20CONFOTUR=20capt=C3=A9e?= =?UTF-8?q?=20=C2=B7=20=C3=A9change=20prose=20signer=20contrat=E2=86=92sig?= =?UTF-8?q?ner=20bail=20capt=C3=A9=20=C2=B7=20=C3=A9num=C3=A9ration=20supp?= =?UTF-8?q?rim=C3=A9e=20=3D=20INTROUVABLE=20;=20restaur=C3=A9=20=3D=20gree?= =?UTF-8?q?n=20:=20{annuler,=20approuver=20confotur,=20confirmer=20r=C3=A9?= =?UTF-8?q?servation,=20r=C3=A9silier,=20signer=20contrat}=20=3D=3D=20work?= =?UTF-8?q?flow.json).=20=C3=89tat=20courant=20:=20aucune=20action=20p?= =?UTF-8?q?=C3=A9rim=C3=A9e=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 | 18 +++++++++ ci/README.md | 20 +++++++++- ci/check_readme_claims.sh | 70 +++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index d04aa75..6cbd852 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1812,3 +1812,21 @@ verrouillés) · `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 · CRM séparation des pouvoirs (2e surface workflow_vente) +- **Constat** : le README `crm/workflow_vente` énumérait À LA MAIN les transitions + quatre-yeux (« confirmer réservation, signer contrat, approuver CONFOTUR, + annuler/résilier — `allow_self_approval = 0` »). Le bloc CRM existant ne gatait + que le COMPTE états/transitions (9/11) — aveugle à l'IDENTITÉ des transitions à + `separation_of_duties`. +- **Gate ajouté** (`ci/check_readme_claims.sh`) : recompute l'ENSEMBLE + `{action | allow_self_approval == 0}` depuis `out/workflow.json` (byte-gaté) et + exige que la phrase README l'énumère EXACTEMENT (set-diff absent ET en trop ; + casse ignorée + suffixe « (perdu) » élidé). Bonus : `0 < |quatre-yeux| < |trans|`. +- **4 morsures vérifiées** : déclassement `Signer contrat` (self 0→1) capté là où + le compte reste 9/11 · promotion `Déposer CONFOTUR` (self 1→0) captée · échange + prose `signer contrat`→`signer bail` capté · énumération supprimée = INTROUVABLE ; + restauré = green (`{annuler, approuver confotur, confirmer réservation, résilier, + signer contrat}` == workflow.json). +- État courant : aucune action périmée (anti-invention #6) — le défaut est la + surface ungated. `ci/README.md` (table + détail) mis à jour · **7 gates re-verts**. diff --git a/ci/README.md b/ci/README.md index c468e45..b17ba02 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) **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`) == 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`) == 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 @@ -196,7 +196,23 @@ recomptés du `workflow_vente_spec.json` à chaque build, byte-gaté par `check_artifacts`). Recomputer **transitions** en plus de **states** mord une dérive du 2ᵉ nombre là où le compte d'états seul (repris dans `dossier_vente`) resterait aveugle — ajouter un état/une transition ferait sinon dériver **les -deux** docs en silence. +deux** docs en silence. **2ᵉ surface du même README** : la phrase « **séparation +des pouvoirs** » énumère **à la main** les transitions quatre-yeux (« confirmer +réservation, signer contrat, approuver CONFOTUR, annuler/résilier — … `allow_self_approval += 0` »). Le bloc de compte est **aveugle** à leur identité : **déclasser** une +transition sensible (`allow_self_approval` 0→1 dans le contrat) laisse le compte à +9/11 tandis que l'artefact la retire de l'ensemble ; **promouvoir** une transition +(ex. `Déposer CONFOTUR` → self=0) l'y ajoute sans que le compte bouge et la prose +l'**omet** ; un **échange** d'action passe aussi. On recompute l'**ensemble** des +actions quatre-yeux depuis `crm/workflow_vente/out/workflow.json` (`{action | +allow_self_approval == 0}`, byte-gaté) et on exige que la phrase l'énumère +**exactement** (absent **et** en trop mordus par set-diff ; casse ignorée + suffixe +« (perdu) » élidé — les deux transitions vers l'état *Perdu*, `Annuler`/`Résilier`, +que la prose groupe « annuler/résilier »). Cohérence croisée : `0 < |quatre-yeux| < +|transitions|` (l'ensemble sensible est non vide **et** strict). 4 morsures +vérifiées (déclassement `Signer contrat` capté là où le compte reste 9/11 · +promotion `Déposer CONFOTUR` captée · échange prose `signer contrat`→`signer bail` +capté · énumération supprimée = INTROUVABLE ; restauré = green). Même classe encore sur l'**app mobile Expo** : la ligne récap du README du module `mobile/app_config` (« **5 onglets** · **44 rôles** couverts · **3 langues** · diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 219429d..a84c7f0 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -841,6 +841,76 @@ if wf_states is not None: bad(f"CRM pipeline · {DV_RD} — dit états={m.group(1)} MAIS MANIFEST dit " f"états={wf_states} → régénérer le README") +# ============================================================================ +# CRM (2e surface du MÊME README) · ÉNUMÉRATION des transitions à SÉPARATION +# DES POUVOIRS — la phrase « engagent de l'argent ou clôturent — confirmer +# réservation, signer contrat, approuver CONFOTUR, annuler/résilier ». +# ---------------------------------------------------------------------------- +# Le bloc CRM ci-dessus recompute le COMPTE d'états/transitions mais PAS +# l'IDENTITÉ des transitions marquées `separation_of_duties`. Or cette liste est +# DATA-DERIVED : le générateur émet out/workflow.json (byte-gaté par +# check_artifacts ⇒ artefact faisant autorité) dont chaque transition porte +# `allow_self_approval` ; l'ensemble des transitions QUATRE-YEUX = {action | +# allow_self_approval == 0} — précisément ce que le README énumère À LA MAIN +# comme « interdisent l'auto-approbation (`allow_self_approval = 0`) ». Le bloc de +# COMPTE reste AVEUGLE au contenu : (a) DÉCLASSER une transition sensible (ex. +# « Signer contrat » repasse à allow_self_approval=1 dans le contrat) laisse le +# compte d'états/transitions à 9/11 pendant que l'artefact retire l'action de +# l'ensemble QUATRE-YEUX — la prose reste à 5 actions FAUSSES ; (b) PROMOUVOIR une +# transition (« Déposer CONFOTUR » → self=0) l'ajoute à l'ensemble sans que le +# compte bouge, et la prose l'OMET ; (c) RÉORDRE/RENOMMAGE d'action passe aussi — +# « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de graphe, pas +# la prose) n'attrape — même patron que l'énumération des onglets Mobile, des open +# items de l'audit 5D ou de la carte de renormalisation 4Big. On RECOMPUTE ici +# l'ENSEMBLE des actions QUATRE-YEUX depuis workflow.json (zéro duplication du flag +# du générateur) et on exige que la phrase l'énumère EXACTEMENT (absent ET en trop +# mordus via set-diff). Normalisation : casse ignorée + suffixe « (perdu) » élidé +# (les deux transitions vers l'état Perdu — Annuler/Résilier — que la prose groupe +# « annuler/résilier »). Cohérence croisée en bonus : 0 < |quatre-yeux| < |trans| +# (l'ensemble sensible est NON VIDE — sinon la garde est vacante — et STRICT — sinon +# tout le graphe serait bloqué en quatre-yeux, ni l'un ni l'autre n'étant le +# contrat). Un claim absent échoue AUSSI (traçabilité). +# ============================================================================ +try: + wf_trans_list = load("crm/workflow_vente/out/workflow.json")[0]["transitions"] + # ensemble QUATRE-YEUX = actions dont l'auto-approbation est interdite (self=0), + # normalisées (casse ignorée · suffixe « (perdu) » élidé — cf. groupage prose) + def _sod_norm(a): + return re.sub(r"\s*\(perdu\)\s*", "", a).strip().lower() + sod_set = {_sod_norm(t["action"]) for t in wf_trans_list + if str(t["allow_self_approval"]) == "0"} + n_trans_tot = len(wf_trans_list) +except (OSError, KeyError, ValueError, IndexError, TypeError) as e: + bad(f"CRM séparation · workflow.json illisible/inattendu : {e}"); sod_set = None +if sod_set is not None: + # bonus : l'ensemble sensible est non vide ET strictement inclus dans le graphe + if not (0 < len(sod_set) < n_trans_tot): + bad(f"CRM séparation · ensemble quatre-yeux dégénéré : |sod|={len(sod_set)} " + f"vs |transitions|={n_trans_tot} (attendu 0 < sod < total) → vérifier le contrat") + else: + good(f"CRM séparation · ensemble quatre-yeux non vide et strict : " + f"0 < {len(sod_set)} < {n_trans_tot}") + # énumération de la phrase : capturer le segment entre « clôturent** — » et « — sont marquées » + wf_txt3 = read_norm(WF_RD) + if wf_txt3 is not None: + ms = re.search(r"engagent de l'argent ou clôturent\*\* — (.+?) — sont marquées", + wf_txt3) + if ms is None: + bad(f"CRM séparation · {WF_RD} — énumération des transitions quatre-yeux " + f"INTROUVABLE (attendu {sorted(sod_set)})") + else: + prose_set = {t.strip().lower() for t in re.split(r"[,/]", ms.group(1)) if t.strip()} + if prose_set == sod_set: + good(f"CRM séparation · {WF_RD} — énumération quatre-yeux " + f"{sorted(prose_set)} == workflow.json (allow_self_approval=0)") + else: + miss = sorted(sod_set - prose_set) + extra = sorted(prose_set - sod_set) + detail = (f" · absents={miss}" if miss else "") + \ + (f" · en trop={extra}" if extra else "") + bad(f"CRM séparation · {WF_RD} — énumère {sorted(prose_set)} MAIS " + f"workflow.json dit {sorted(sod_set)}{detail} → régénérer le README") + # ============================================================================ # Mobile · récap de l'app Expo (onglets / rôles couverts / langues / identifiants # a_confirmer) — même classe de « vert trompeur », transcrite À LA MAIN.