[DTP-Worker] Sprint 8 · buffer · CRM/workflow_vente (3e surface) : la TABLE « Rôles = contrat RBAC » — le RÔLE RBAC qui GARDE chaque transition du pipeline vente, CŒUR sécurité du workflow — était transcrite EN PROSE (README:55-62) SANS AUCUN gate d'IDENTITÉ.
Les deux blocs CRM/workflow existants gatent le COMPTE (« 9 états · 11 transitions ») ET l'énumération des transitions à séparation des pouvoirs (allow_self_approval=0), jamais l'identité des GARDES. Sources faisant autorité (byte-gatées par check_artifacts) : out/workflow.json (chaque transition porte `allowed` = le rôle gardien) + out/MANIFEST.json.roles_rbac_utilises[].erpnext_role_name (recomputé du contrat rbac_50_roles.json à chaque build). PIÈGE : les blocs COMPTE/séparation sont AVEUGLES à l'identité des gardes → RÉAFFECTER un pas monétaire (« Confirmer réservation » Réservations→Conseiller = élévation de privilège) · RENOMMER un rôle · AJOUTER un fantôme · en OUBLIER un laissait le README périmé pendant que l'artefact dit autre chose → l'agent ERPNext Backend câblerait le mauvais garde (le risque même que la table veut prévenir) — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de graphe/résolution RBAC, pas la prose) n'attrape. Même patron d'IDENTITÉ que la cross-cohérence PERMISSIONS Legal/CONFOTUR déjà gatée.
Nouveau bloc « CRM gardes » dans ci/check_readme_claims.sh (après le bloc Démo scénarios) : (1) identité d'ensemble — colonne « Rôle » de la table == {allowed} de workflow.json (set-diff · NFKD/casefold) ; (2) cohérence artefacts — {allowed} == roles_rbac_utilises du MANIFEST (aucun garde hors manifeste ni l'inverse) · ensemble NON VIDE ; (3) cross-cohérence par pas — les gardes des étapes SENSIBLES et uniques Confirmer réservation/Signer contrat/Approuver CONFOTUR (recomputés `allowed`, jamais figés) nommés EXACTEMENT dans leur ligne. Le (3) mord la RÉAFFECTATION vers un rôle DÉJÀ présent (rôle servant deux transitions) que le set-diff seul manquerait. Un claim absent échoue AUSSI.
7 morsures vérifiées : README réaffecte Confirmer réservation→Conseiller (absents=[réservations] + pas mordu) · artefact réaffecte Signer contrat→Conseiller (en trop=[contrats] + incohérence MANIFEST + pas mordu) · README rôle fantôme (en trop=[oto fantome]) · README retire un rôle (absents=[direction commerciale]) · artefact MANIFEST perd un garde (garde pas dans MANIFEST=[réservations]) · table supprimée (INTROUVABLE) · artefact réaffecte Approuver CONFOTUR→Directeur (rôle déjà présent, set INCHANGÉ, captée UNIQUEMENT par la cross-cohérence par pas) ; restauré = green : 7 rôles · colonne == {allowed} == roles_rbac_utilises · 3 étapes sensibles pinnées · exit 0. État courant : aucun garde périmé (anti-invention #6, rien à réécrire) — le défaut était la surface ungated. ci/README.md (table + détail « 3e surface workflow_vente ») mis à jour · working tree byte-restauré (git checkout --, JAMAIS git clean) · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,63 @@
|
|||||||
# Activity Log · 2026-07-31 · Claude Code DTP
|
# Activity Log · 2026-07-31 · Claude Code DTP
|
||||||
|
|
||||||
|
## Session `20260731_230154` · Buffer S8 · Domaine CRM/workflow_vente (3e surface) : la **table « Rôles = contrat RBAC »** — le **rôle RBAC qui GARDE chaque transition** du pipeline vente, CŒUR sécurité du workflow (qui a le droit d'exécuter quel pas) — était transcrite EN PROSE (README:55-62) SANS AUCUN gate d'IDENTITÉ. Les deux blocs CRM/workflow existants gatent le COMPTE (« 9 états · 11 transitions ») ET l'énumération des transitions à séparation des pouvoirs (`allow_self_approval=0`), jamais l'identité des gardes.
|
||||||
|
|
||||||
|
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
|
||||||
|
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Même patron d'IDENTITÉ que
|
||||||
|
la cross-cohérence PERMISSIONS Legal/CONFOTUR (`role_id`→actions) ou le catalogue
|
||||||
|
RBAC `fixtures_gen` — appliqué à la **3ᵉ surface data-derived du même README
|
||||||
|
`crm/workflow_vente`**, jamais gatée.
|
||||||
|
|
||||||
|
**Dérive silencieuse fermée** :
|
||||||
|
- `05_deliverables_mvp/crm/workflow_vente/README.md:55-62` — table « Étape → Rôle
|
||||||
|
qui garde la transition » : nomme, PAR pas, le rôle RBAC autorisé (`Planifier
|
||||||
|
visite · Émettre devis`→`OTO Ventes Conseiller` · `Confirmer réservation`→`OTO
|
||||||
|
Ventes Réservations` · `Signer contrat`→`OTO Ventes Contrats` · `Déposer/Approuver
|
||||||
|
CONFOTUR`→`OTO Ventes CONFOTUR` · `Annuler/Résilier`→`OTO Ventes Directeur · OTO
|
||||||
|
Direction Commerciale` · etc.).
|
||||||
|
- Sources faisant autorité (byte-gatées par `check_artifacts`) :
|
||||||
|
`out/workflow.json` — chaque transition porte `allowed` (le rôle qui garde le pas) ;
|
||||||
|
ET `out/MANIFEST.json.roles_rbac_utilises[].erpnext_role_name` — l'ensemble des
|
||||||
|
rôles employés, RECOMPUTÉ du contrat `rbac_50_roles.json` à chaque build.
|
||||||
|
- Piège : les blocs de COMPTE/séparation sont AVEUGLES à l'identité des gardes.
|
||||||
|
RÉAFFECTER un pas monétaire (« Confirmer réservation » — soumission d'argent —
|
||||||
|
`Réservations`→`Conseiller` : **élévation de privilège**), RENOMMER un rôle,
|
||||||
|
AJOUTER un fantôme dans la prose ou en OUBLIER un laisse le README périmé pendant
|
||||||
|
que l'artefact dit autre chose → l'agent ERPNext Backend câblerait le mauvais garde
|
||||||
|
(le risque même que la table veut prévenir). Aucune suite `tests/` (qui teste des
|
||||||
|
FONCTIONS de graphe/résolution RBAC, pas la prose) n'attrape ce « vert trompeur ».
|
||||||
|
- **État courant** : **aucun garde périmé** — les 7 rôles de la table recoupent les
|
||||||
|
artefacts exactement (anti-invention #6, rien à réécrire). Le défaut est la
|
||||||
|
**surface ungated**.
|
||||||
|
|
||||||
|
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM gardes » après le
|
||||||
|
bloc Démo scénarios) : (1) **identité d'ensemble** — colonne « Rôle » de la table ==
|
||||||
|
`{allowed}` de `workflow.json` (set-diff · NFKD/casefold) ; (2) **cohérence
|
||||||
|
artefacts** — `{allowed}` == `roles_rbac_utilises` du MANIFEST (aucun garde hors
|
||||||
|
manifeste, ni l'inverse) · ensemble NON VIDE ; (3) **cross-cohérence par pas** — les
|
||||||
|
gardes des étapes SENSIBLES et uniques `Confirmer réservation`/`Signer contrat`/
|
||||||
|
`Approuver CONFOTUR` (recomputés `allowed`, jamais figés) doivent être nommés
|
||||||
|
EXACTEMENT dans leur ligne. Le (3) mord la **réaffectation vers un rôle DÉJÀ
|
||||||
|
présent** que le set-diff seul manquerait (un rôle servant deux transitions). Un
|
||||||
|
claim absent échoue AUSSI (traçabilité).
|
||||||
|
|
||||||
|
**7 morsures vérifiées** : README réaffecte `Confirmer réservation`→`Conseiller`
|
||||||
|
(absents=[réservations] + cross-cohérence par pas mordue) · artefact réaffecte
|
||||||
|
`Signer contrat`→`Conseiller` (en trop=[contrats] + incohérence MANIFEST + pas
|
||||||
|
mordu) · README ajoute rôle fantôme (en trop=[oto fantome]) · README retire un rôle
|
||||||
|
(absents=[direction commerciale]) · artefact MANIFEST perd un garde (garde pas dans
|
||||||
|
MANIFEST=[réservations]) · table supprimée (INTROUVABLE) · **artefact réaffecte
|
||||||
|
`Approuver CONFOTUR`→`Directeur` — rôle déjà présent, set INCHANGÉ, captée UNIQUEMENT
|
||||||
|
par la cross-cohérence par pas** ; restauré = green : 7 rôles · colonne == `{allowed}`
|
||||||
|
== `roles_rbac_utilises` · 3 étapes sensibles pinnées · exit 0. Working tree
|
||||||
|
byte-restauré (`git checkout --`, JAMAIS `git clean`) · **7 gates re-verts**.
|
||||||
|
|
||||||
|
- `ci/README.md` (table récap + détail « 3ᵉ surface du même README workflow_vente »)
|
||||||
|
mis à jour.
|
||||||
|
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
|
||||||
|
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
|
||||||
|
- **Auto-score 4Big** : 96/100.
|
||||||
|
|
||||||
## Session `20260731_220140` · Buffer S8 · Domaine Legal/CONFOTUR (2e surface) : la **cross-cohérence PERMISSIONS** du DocType — le CŒUR sécurité du livrable — énumérait EN PROSE, PAR rôle, son **portail** ET son **jeu d'actions RBAC** (`ventes-confotur`→read/write/create/print · `legal-onapi`→read/write/create · `legal-directeur`→read/write/**submit**/report) SANS AUCUN gate d'IDENTITÉ. Le bloc CONFOTUR existant ne gate QUE le COMPTE (« 3 rôles »).
|
## Session `20260731_220140` · Buffer S8 · Domaine Legal/CONFOTUR (2e surface) : la **cross-cohérence PERMISSIONS** du DocType — le CŒUR sécurité du livrable — énumérait EN PROSE, PAR rôle, son **portail** ET son **jeu d'actions RBAC** (`ventes-confotur`→read/write/create/print · `legal-onapi`→read/write/create · `legal-directeur`→read/write/**submit**/report) SANS AUCUN gate d'IDENTITÉ. Le bloc CONFOTUR existant ne gate QUE le COMPTE (« 3 rôles »).
|
||||||
|
|
||||||
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
|
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
|
||||||
|
|||||||
+24
-1
File diff suppressed because one or more lines are too long
@@ -2754,6 +2754,115 @@ if d_rs is not None and _proj_map is not None:
|
|||||||
bad(f"Démo scénarios · table — lignes {sorted(row_ids)} MAIS run_sheet "
|
bad(f"Démo scénarios · table — lignes {sorted(row_ids)} MAIS run_sheet "
|
||||||
f"{sorted(want_ids)} (counts.scenarios={d_n}){detail} → régénérer le README")
|
f"{sorted(want_ids)} (counts.scenarios={d_n}){detail} → régénérer le README")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CRM/workflow_vente (3e surface du MÊME README) · la TABLE « Rôles = contrat
|
||||||
|
# RBAC » — le RÔLE RBAC qui GARDE chaque transition du pipeline vente. C'est le
|
||||||
|
# CŒUR sécurité du workflow (qui a le droit d'exécuter quel pas), transcrit À LA
|
||||||
|
# MAIN sans AUCUN gate d'IDENTITÉ.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Les deux blocs CRM/workflow existants gatent (1) le COUPLE de COMPTES du pipeline
|
||||||
|
# (« 9 états · 11 transitions ») et (2) l'ÉNUMÉRATION des transitions à SÉPARATION
|
||||||
|
# DES POUVOIRS (allow_self_approval=0). Restait, dans le MÊME README (l.55-62), une
|
||||||
|
# 3e surface data-derived : la table « Étape → Rôle qui garde la transition » qui
|
||||||
|
# nomme, PAR pas, le rôle RBAC autorisé. Sources faisant autorité (byte-gatées par
|
||||||
|
# check_artifacts) : crm/workflow_vente/out/workflow.json — chaque transition porte
|
||||||
|
# `allowed` (le rôle qui garde le pas) ; ET out/MANIFEST.json.roles_rbac_utilises[]
|
||||||
|
# — l'ensemble des rôles employés, RECOMPUTÉ du contrat rbac_50_roles.json à chaque
|
||||||
|
# build. Les deux blocs de COMPTE/séparation sont AVEUGLES à l'identité de ces rôles :
|
||||||
|
# RÉAFFECTER un pas (« Confirmer réservation » — soumission d'argent — passe de
|
||||||
|
# `OTO Ventes Réservations` à `OTO Ventes Conseiller` : élévation de privilège sur
|
||||||
|
# une étape monétaire), RENOMMER un rôle, en AJOUTER un fantôme dans la prose ou en
|
||||||
|
# OUBLIER un laisse le README périmé pendant que l'artefact dit autre chose →
|
||||||
|
# l'agent ERPNext Backend câblerait le mauvais garde (le risque même que la table
|
||||||
|
# veut prévenir). « Vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS
|
||||||
|
# de graphe/résolution RBAC, pas la prose) n'attrape — même patron d'IDENTITÉ que la
|
||||||
|
# cross-cohérence PERMISSIONS Legal/CONFOTUR ou le catalogue RBAC fixtures_gen.
|
||||||
|
# Bonus (mordent un artefact INTERNEMENT incohérent) : {allowed} de workflow.json
|
||||||
|
# == {erpnext_role_name} de MANIFEST.roles_rbac_utilises (aucun rôle gardant une
|
||||||
|
# transition sans être listé dans le manifeste, ni l'inverse) · ensemble NON VIDE.
|
||||||
|
# Cross-cohérence par pas (mord la réaffectation que le set-diff seul manque quand
|
||||||
|
# un rôle sert DEUX transitions) : pour les pas monétaires/de clôture SENSIBLES et
|
||||||
|
# UNIQUES dans le graphe (recomputés `allowed`, jamais figés), la ligne README qui
|
||||||
|
# les nomme doit citer EXACTEMENT ce rôle.
|
||||||
|
# État courant : aucun garde périmé (anti-invention #6) — le défaut est la surface
|
||||||
|
# ungated. Un claim absent échoue AUSSI (traçabilité).
|
||||||
|
# ============================================================================
|
||||||
|
def _fold(s):
|
||||||
|
s = unicodedata.normalize("NFKD", s)
|
||||||
|
return "".join(c for c in s if not unicodedata.combining(c)).strip().casefold()
|
||||||
|
try:
|
||||||
|
wfv_trans = load("crm/workflow_vente/out/workflow.json")[0]["transitions"]
|
||||||
|
wfv_roles = {t["allowed"] for t in wfv_trans} # gardes du graphe
|
||||||
|
wfv_man = load("crm/workflow_vente/out/MANIFEST.json")["roles_rbac_utilises"]
|
||||||
|
wfv_roles_man = {r["erpnext_role_name"] for r in wfv_man}
|
||||||
|
# rôle par action, UNIQUEMENT quand l'action garde 1 seul rôle (déterministe)
|
||||||
|
wfv_by_act = {}
|
||||||
|
for t in wfv_trans:
|
||||||
|
wfv_by_act.setdefault(t["action"], set()).add(t["allowed"])
|
||||||
|
wfv_uniq = {a: next(iter(v)) for a, v in wfv_by_act.items() if len(v) == 1}
|
||||||
|
except (OSError, KeyError, ValueError, IndexError, TypeError) as e:
|
||||||
|
bad(f"CRM gardes · workflow.json/MANIFEST illisible/inattendu : {e}"); wfv_roles = None
|
||||||
|
if wfv_roles is not None:
|
||||||
|
# bonus 1 : ensemble non vide
|
||||||
|
if not wfv_roles:
|
||||||
|
bad("CRM gardes · aucun rôle ne garde de transition (ensemble VIDE) → contrat cassé")
|
||||||
|
else:
|
||||||
|
good(f"CRM gardes · {len(wfv_roles)} rôles gardent le pipeline (ensemble non vide)")
|
||||||
|
# bonus 2 : workflow.json ⇄ MANIFEST d'accord sur l'ensemble des rôles employés
|
||||||
|
if wfv_roles == wfv_roles_man:
|
||||||
|
good(f"CRM gardes · {{allowed}} workflow.json == roles_rbac_utilises MANIFEST "
|
||||||
|
f"({len(wfv_roles)} rôles · aucun fantôme/manquant entre artefacts)")
|
||||||
|
else:
|
||||||
|
miss = sorted(wfv_roles_man - wfv_roles); extra = sorted(wfv_roles - wfv_roles_man)
|
||||||
|
detail = (f" · dans MANIFEST pas gardien={miss}" if miss else "") + \
|
||||||
|
(f" · gardien pas dans MANIFEST={extra}" if extra else "")
|
||||||
|
bad(f"CRM gardes · incohérence artefacts : gardes workflow.json {sorted(wfv_roles)} "
|
||||||
|
f"MAIS roles_rbac_utilises {sorted(wfv_roles_man)}{detail} → régénérer")
|
||||||
|
wfv_txt = read_norm(WF_RD)
|
||||||
|
if wfv_txt is not None:
|
||||||
|
seg = re.search(r"Rôle qui garde la transition \|(.+?)## Séparation", wfv_txt)
|
||||||
|
if seg is None:
|
||||||
|
bad(f"CRM gardes · {WF_RD} — table « Rôle qui garde la transition » INTROUVABLE "
|
||||||
|
f"(attendu {sorted(wfv_roles)})")
|
||||||
|
else:
|
||||||
|
rows = [(l, r) for l, r in re.findall(r"\| ([^|]+?) \| ([^|]+?) \|", seg.group(1))
|
||||||
|
if set(l.strip()) != {"-"}]
|
||||||
|
# identité d'ensemble : colonne « Rôle » de la table == gardes du graphe
|
||||||
|
rd_roles = set()
|
||||||
|
for _lbl, cell in rows:
|
||||||
|
for tok in cell.split("·"):
|
||||||
|
if tok.strip():
|
||||||
|
rd_roles.add(tok.strip())
|
||||||
|
rd_fold = {_fold(x) for x in rd_roles}; wf_fold = {_fold(x) for x in wfv_roles}
|
||||||
|
if rd_fold == wf_fold:
|
||||||
|
good(f"CRM gardes · {WF_RD} — colonne Rôle {sorted(rd_roles)} == "
|
||||||
|
f"{{allowed}} workflow.json ({len(wfv_roles)} rôles · ni fantôme ni manquant)")
|
||||||
|
else:
|
||||||
|
miss = sorted(wf_fold - rd_fold); extra = sorted(rd_fold - wf_fold)
|
||||||
|
detail = (f" · absents={miss}" if miss else "") + \
|
||||||
|
(f" · en trop={extra}" if extra else "")
|
||||||
|
bad(f"CRM gardes · {WF_RD} — colonne Rôle {sorted(rd_fold)} MAIS "
|
||||||
|
f"{{allowed}} workflow.json {sorted(wf_fold)}{detail} → régénérer le README")
|
||||||
|
# cross-cohérence par pas : les gardes des étapes SENSIBLES (argent/clôture),
|
||||||
|
# uniques dans le graphe, recomputés depuis workflow.json — jamais figés.
|
||||||
|
for act in ("Confirmer réservation", "Signer contrat", "Approuver CONFOTUR"):
|
||||||
|
exp = wfv_uniq.get(act)
|
||||||
|
if exp is None:
|
||||||
|
bad(f"CRM gardes · action sensible « {act} » absente/non-déterministe "
|
||||||
|
f"dans workflow.json → contrat changé de forme, vérifier")
|
||||||
|
continue
|
||||||
|
row = next((c for l, c in rows if _fold(act) in _fold(l)), None)
|
||||||
|
if row is None:
|
||||||
|
bad(f"CRM gardes · {WF_RD} — ligne de l'étape sensible « {act} » "
|
||||||
|
f"INTROUVABLE (attendu garde {exp})")
|
||||||
|
elif _fold(exp) in {_fold(t) for t in row.split("·") if t.strip()}:
|
||||||
|
good(f"CRM gardes · étape sensible « {act} » gardée par « {exp} » "
|
||||||
|
f"== workflow.json (séparation des pouvoirs · #6)")
|
||||||
|
else:
|
||||||
|
bad(f"CRM gardes · {WF_RD} — étape sensible « {act} » : README dit "
|
||||||
|
f"« {row.strip()} » MAIS workflow.json dit garde « {exp} » → "
|
||||||
|
f"réaffectation de privilège non répercutée, régénérer")
|
||||||
|
|
||||||
sys.exit(1 if FAIL else 0)
|
sys.exit(1 if FAIL else 0)
|
||||||
PY
|
PY
|
||||||
rc=$?
|
rc=$?
|
||||||
|
|||||||
Reference in New Issue
Block a user