[DTP-Worker 20260802_133549] Sprint 8 · buffer · roles_couverts (« 44 ») : enquête anti-dérive → NON-surface prouvée (3 manifestes importent la même fonction roles_by_portail sur l'unique contrat rbac_50_roles.json → transitivement mono-sourcé, un cross-gate serait redondant #5, contra le cross-check RBAC-50 qui re-dérive) + documentation du garde-fou dans ci/README.md pour prévenir un gate N+1 redondant

This commit is contained in:
Claude Code DTP Worker
2026-08-02 13:45:38 +00:00
parent 8dcedb092c
commit 576e78b6a9
2 changed files with 58 additions and 0 deletions
+38
View File
@@ -1371,3 +1371,41 @@ figés à 13 ⇒ les **5 surfaces head-docs+GAP mordent** « dit 13 MAIS 03_agen
(preuve que l'autorité est le git-index, pas la cohérence interne). Arbre restauré. **7 gates
re-joués → exit 0** : check_ci_integrity · check_artifacts · check_docs · check_readme_claims ·
check_regression · guard_constraints · validate_json. Aucune commande touchant au VPS.
## Sprint 8 · buffer · `roles_couverts` (« 44 ») — enquête anti-dérive → **non-surface prouvée** + documentation du garde-fou mono-source (évite un gate N+1 redondant #5)
**Contexte** — Série anti-dérive Sprint 8. Un balayage des comptes numériques a
exhumé un candidat de **cohérence croisée** : le total « **44 rôles couverts par les
5 portails** » apparaît, identique, dans **trois** manifestes byte-gatés
(`frontend/portails`, `frontend/chat_otoia`, `mobile/app_config` ·
`counts.roles_couverts=44`). Chaque « 44 » vif n'est ancré qu'à SON propre manifeste
(mobile README · `demo/scenarios/out/run_sheet.md`). Aucun `ci/*.sh` ne compare les
trois entre eux. Hypothèse initiale : surface non couverte, analogue au cross-check
RBAC « 50 » (`rbac/apply_plan/rbac_apply_plan.py:149` exige
`fixtures==userperm==roleprofile`).
**Enquête — l'analogie RBAC-50 ne tient PAS** (mémoire *fix-vs-gate-transitively-
protected-constant*). Le « 50 » EST cross-gaté car ses trois rendus **re-dérivent**
chacun leur décompte avec leur **propre** logique. Ici, au contraire, les trois
modules **importent la même** fonction de partition : `mobilelib/deps.py:77` et
`chatlib/deps.py:84` font tous deux `roles_by_portail = portails_builder.roles_by_portail`
(unique définition `frontend/portails/wslib/builder.py:36`), appliquée à l'**unique**
contrat `rbac/rbac_50_roles.json` (une seule copie de chaque dans `git ls-files`).
`roles_couverts` est donc **transitivement mono-sourcé** (code partagé + donnée
partagée) : les trois totaux **ne peuvent diverger** que si cette fonction ou ce
contrat change — auquel cas les trois changent ensemble et `check_artifacts`
(reproductibilité byte-à-byte) vérifie chaque rebuild. Un gate « 44==44==44 » serait
**redondant (#5)** et masquerait la vraie garantie (le partage de code), pas une
surface à protéger.
**Livraison — DOCUMENTATION du garde-fou, pas un gate.** Ajout d'un bloc sourcé
« **Non-surface documentée — `roles_couverts` N'EST PAS cross-gaté, à dessein** » dans
`ci/README.md` (entre le bloc mobile Expo et le bloc CONFOTUR) : cite les 3 imports
partagés + le contrat unique + contraste explicite avec le cross-check RBAC-50, pour
qu'une session future ne rajoute PAS le gate redondant. Chemins en backticks (non
link-checkés, cf. mémoire *backtick-path-escapes-check-docs*).
**Vérif** — 4 citations de lignes vérifiées exactes (`deps.py:77`, `deps.py:84`,
`wslib/builder.py:36`, `rbac_apply_plan.py:149`). **7 gates re-joués → exit 0** :
guard_constraints · validate_json · check_docs · check_artifacts · check_regression ·
check_ci_integrity · check_readme_claims. Aucune commande touchant au VPS.
+20
View File
@@ -491,6 +491,26 @@ Marketing), un **retrait** (Compta absent — l'énumération tombe à 4 tandis
est aveugle. Cohérence croisée : `len(role_navigation) == counts.onglets ==
counts.portails` — mord un compteur du manifeste désynchronisé de sa navigation.
**Non-surface documentée — `roles_couverts` (« 44 ») N'EST PAS cross-gaté, à dessein.**
Le total « 44 rôles couverts par les 5 portails » apparaît, identique, dans **trois**
manifestes (`frontend/portails`, `frontend/chat_otoia`, `mobile/app_config` ·
`counts.roles_couverts=44`). On pourrait croire à trois rendus indépendants réclamant
un cross-check façon RBAC « 50 » (où `rbac/apply_plan/rbac_apply_plan.py:149` exige
`fixtures==userperm==roleprofile`, car chacun **re-dérive** son décompte avec sa propre
logique). Ce n'est **pas** le cas ici : les trois modules **importent la même** fonction
de partition — `mobilelib/deps.py:77` et `chatlib/deps.py:84` font tous deux
`roles_by_portail = portails_builder.roles_by_portail` (l'unique définition,
`frontend/portails/wslib/builder.py:36`) — appliquée à l'**unique** contrat
`rbac/rbac_50_roles.json`. `roles_couverts` est donc **transitivement mono-sourcé**
(code partagé + donnée partagée) : les trois totaux ne peuvent diverger que si cette
fonction ou ce contrat change, auquel cas les trois changent ensemble et
`check_artifacts` (reproductibilité byte-à-byte) vérifie chaque rebuild. Un gate
« 44==44==44 » serait **redondant** (#5) — cf. mémoire *fix-vs-gate-transitively-
protected-constant*. Chaque « 44 » vif reste néanmoins ancré à SON manifeste
(mobile README ci-dessus · `demo/scenarios/out/run_sheet.md` sourcé à
`portails/…/roles_couverts`), ce qui suffit : la valeur ne peut mentir que si son
manifeste ment, et le manifeste est byte-gaté.
Même classe enfin sur le **DocType CONFOTUR Application** (livrable `legal/confotur`) :
la ligne de synthèse du README du module (« **18 champs** (14 de donnée) · **4
sections** · **3 rôles** · … · **2 évènements de dépôt** ») **et** la cellule de la