[DTP-Worker] Sprint 8 · buffer · CRM/Commissions : gate d'IDENTITÉ des RÔLES PAYÉS — le « à quel rôle » du barème vendeurs — appartenance SINGLETON au portail ventes (séparation des pouvoirs · invariant #5) double-ancrée contrat rbac_50_roles.json ⨯ littéral README ⨯ portails_business · nom Frappe erpnext_role_name RÉSOLU == contrat (invariant #6, jamais figé en dur) · role_id ∈ contrat (jamais fantôme) · MANIFEST.roles_rbac_utilises recoupé — recomputés de commission_plan.json + contrat + MANIFEST byte-gatés

This commit is contained in:
Claude Code DTP Worker
2026-08-01 15:44:53 +00:00
parent 290bd2ec02
commit 28e5e2145b
3 changed files with 180 additions and 1 deletions
+60
View File
@@ -1708,3 +1708,63 @@ conteneur) · exit 0. **7 gates re-verts** (`check_readme_claims`/`check_artifac
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la migration
serveur réelle + le renommage compose restent côté DevOps/VPS production).
- **Auto-score 4Big** : 96/100.
## Session `20260801_153345` · Buffer S8 · Domaine CRM/Commissions : l'**IDENTITÉ des RÔLES PAYÉS** du barème vendeurs — le « **à quel rôle** » de la question fondatrice (« quel évènement paie, à quel RÔLE, sur quel montant », README:8-9) — était HORS de tout gate. La §Cross-cohérence promet (README:51-53) que chaque `role_id` commissionné « doit être résolu depuis `rbac_50_roles.json` … **ET appartenir au portail `ventes`** » (invariant CLI #5) avec un `erpnext_role_name` « cohérent avec RBAC » (invariant #6 · le « nom Frappe résolu » du README:36). C'est une **SÉPARATION DES POUVOIRS** : on ne commissionne QUE des rôles du portail ventes. Les DEUX blocs commissions existants (cross-cohérence états/base_field + anti-invention taux) NE couvraient PAS cette appartenance-portail ni la résolution du nom.
**Tâche** : **Sprint 8 · buffer** (ERPNext Backend / CRM · #6). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive. **Troisième surface data-derived** du même
livrable `crm/commissions` (après états/base_field et anti-taux) — le troisième tiers
de la question fondatrice du module (« à quel rôle »), le seul jamais confronté à
l'artefact. Classe [[agent-fiche-role-attrs-ungated]] : une propriété-rôle
data-derived affirmée en prose mais gatée par rien.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/commissions/README.md:51-53` — « `role_id` … résolu depuis
`rbac_50_roles.json` … ET appartenir au portail `ventes` » + README:36 « nom Frappe
résolu ».
- Sources faisant autorité (toutes byte-gatées par `check_artifacts`) :
`out/commission_plan.json[evenements[].{role_id,erpnext_role_name}]` (les rôles
RÉELLEMENT payés) · `rbac/rbac_50_roles.json[roles[].{id,portail,erpnext_role_name}]`
+ `[portails_business]` (le contrat) · `out/MANIFEST.json[roles_rbac_utilises,counts.roles]`
(le récap).
- Piège #6, les dérives les plus graves ici : **(a)** commissionner un rôle d'un
AUTRE portail (payer un fiscaliste `compta` → la prose « portail `ventes` » devient
un MENSONGE, pouvoirs mélangés) ; **(b)** un `role_id` FANTÔME absent du contrat
(rôle inventé — l'invention même que #6 interdit) ; **(c)** un `erpnext_role_name`
figé en dur dans le plan qui dérive quand le contrat renomme le rôle (nom Frappe
périmé → `Has Role` importé sur un rôle INEXISTANT côté VPS). Aucune suite `tests/`
(FONCTIONS de résolution/calcul, jamais la prose vs l'artefact) ne l'attrape ; ce
n'est ni un lien (`check_docs`), ni un terme interdit (`guard_constraints`), ni un
`out/` dérivé (`check_artifacts`) → invisible aux trois.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM/Commissions ·
IDENTITÉ des RÔLES PAYÉS » après le bloc anti-invention) : RE-DÉRIVE tout des
artefacts byte-gatés (zéro dup du contrat du générateur · #6). **(a)** chaque
`role_id` du plan ∈ contrat (sinon fantôme) ; **(b)** `erpnext_role_name` du plan ==
contrat pour chaque évènement (nom Frappe résolu · invariant #6) ; **(c)** le portail
des rôles payés est un **SINGLETON** ∈ `portails_business`, ET == le portail nommé au
README (`appartenir au portail \`([a-z-]+)\`` — le littéral `ventes`) : payer un rôle
d'un autre portail casse le singleton, renommer le portail au contrat désaccorde le
littéral README ; **(d)** `MANIFEST.roles_rbac_utilises` == l'ensemble distinct
`(role_id, nom)` réellement payé + `counts.roles` recoupé.
**6 morsures vérifiées** (mutations sur fichiers DONNÉES uniquement — plan/contrat/
MANIFEST/README — restaurées par `git checkout -- <path>` **ciblé** ; **JAMAIS**
`git checkout .`/large avec le gate décommité en cours d'édition, interdit absolu +
incident process en mémoire) : (M1) `role_id` fantôme dans le plan → absent du contrat ·
(M2) `erpnext_role_name` dérivé dans le plan → ≠ contrat · (M3) rôle **`compta`**
commissionné → portails `{compta, ventes}`, séparation des pouvoirs rompue (+ cascade
MANIFEST) · (M4) littéral portail README `ventes→sales` → dit `sales` MAIS rôles du
portail `ventes` · (M5) `roles_rbac_utilises` amputé → ≠ rôles payés · (M6) renommage
du rôle DANS le contrat (`ventes-conseiller`) → cascade double (nom plan≠contrat +
MANIFEST périmé). Restauré = vert : 4 assertions vertes · exit 0. **7 gates re-verts**
(`check_readme_claims`/`check_artifacts`/`check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_regression`/`validate_json`).
- `ci/README.md` (ligne récap du pipeline `check-readme-claims` : sous-clause « rôles
payés » ; + paragraphe prose « troisième surface du même livrable `crm/commissions` »)
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 ; la confirmation
des taux réels + le câblage du calcul restent côté ERPNext Backend/VPS production).
- **Auto-score 4Big** : 96/100.