ADR-0032 — Résolution des habilitations : USER, GROUP, et l'ordre des projections¶
Date : 2026-08-29
Catégorie : B (sécurité / autorisation plateforme)
Décideur : Lead architecte DYNORS — à valider CTO
Statut : Proposée — validée architecte le 2026-08-29 (corrections de revue intégrées) ; validation CTO à programmer
Complète : [[0031-habilitation-projet-affectation-projection-locale]] — précise ses trois points ouverts
S'appuie sur : ADR « Bounded contexts » (contexte Delivery & Staffing → TAKKU) ; dynors-platform/entitlements/
Contexte¶
L'ADR-0031 pose que le rôle est strictement attaché au projet et qu'aucune agrégation
transverse n'est admise. Elle laisse trois points ouverts : la granularité du sujet dans
dynors-entitlements, le chemin d'émission vers SIRRAT, et le recouvrement JARAAF ↔ TAKKU.
Trancher le premier ouvre une question que l'ADR-0031 ne traite pas et qui est la plus dangereuse du modèle : quand plusieurs sources accordent des rôles différents sur le même projet, laquelle gouverne ? Une règle mal choisie y recrée exactement l'élévation implicite que l'ADR-0031 vient de fermer.
Décision¶
1. USER et GROUP, dès maintenant¶
| Sujet | Porte |
|---|---|
SubjectType.USER |
droit individuel — affectation nominative ou exception |
SubjectType.GROUP |
droit porté par une équipe métier réelle |
Introduire GROUP évite de créer cinquante octrois individuels identiques quand une équipe
entière partage le même périmètre.
Règle stricte — ce qu'un GROUP ne doit jamais être. Un groupe représente une équipe
métier ou opérationnelle réelle, jamais une combinaison artificielle projet × rôle :
✅ equipe-dawalale-back une équipe qui existe, se réunit, a un responsable
✅ astreinte-plateforme une rotation d'astreinte réelle
❌ DAWALALE_DEVELOPER artefact technique
❌ DAWALALE_OPS artefact technique
❌ DOSALI_DEVELOPER artefact technique
Sans cette règle, on reconstruit les groupes LDAP par projet que l'ADR-0031 a écartés — avec la même explosion à 10 000 projets, simplement déplacée d'un système à l'autre.
Indice de dérive à surveiller : un groupe dont les octrois ne couvrent qu'un seul projet est probablement un artefact. Ce n'est pas un blocage technique — un groupe mono-projet peut être légitime — mais c'est un signal que la revue d'habilitation doit relever.
2. Source de vérité de l'appartenance aux groupes¶
TAKKU, contexte Delivery & Staffing, au même titre que l'affectation individuelle.
TAKKU
├── Projet
├── Mission
├── Affectation individuelle personne ↔ projet ↔ rôle ↔ période
└── Équipe nom, membres, période d'appartenance
↓
dynors-entitlements
├── USER → project:<code> → rôle
└── GROUP → project:<code> → rôle
↓
résolution des droits effectifs
↓
SIRRAT · GitLab
L'appartenance à une équipe porte une période, comme l'affectation : on entre dans une équipe et on en sort, et le droit qui en découle suit.
Ni LDAP, ni un annuaire. Une équipe DYNORS est un fait de staffing, pas une structure
d'annuaire — et c'est ce qui la distingue des neuf groupes fonctionnels existants
(DYNORS_DEV, JARAAF_DAF…), qui restent ce qu'ils sont.
3. Résolution USER + GROUP — la règle¶
Elle s'évalue entièrement côté serveur, dans dynors-entitlements, et elle est
déterministe.
Pour chaque projet, séparément :
1. rassembler les droits GROUP applicables (équipes en cours, octrois valides)
2. s'il y en a plusieurs → RÔLE LE PLUS RESTRICTIF, et conflit signalé
3. s'il existe un droit USER sur CE projet → il REMPLACE le résultat des groupes
Aucune agrégation entre projets, à aucune étape. Chaque projet est résolu isolément.
Seuls les rôles qui accordent quelque chose participent¶
MANAGER ne participe pas à la résolution SIRRAT. L'ADR-0031 pose qu'il est membre du projet
sans aucune capacité SIRRAT ; le faire entrer dans l'ordre de restriction produirait ceci :
GROUP equipe-pilotage → DAWALALE = MANAGER
GROUP equipe-back → DAWALALE = DEVELOPER
avec MANAGER dans l'ordre : DAWALALE = MANAGER → Alice perd TOUT accès SIRRAT
Un fait de staffing non opérationnel annulerait une habilitation opérationnelle légitime. Ce n'est plus une restriction de rôle, c'est un refus produit par une information qui n'avait rien à dire sur SIRRAT.
Règle générale : un rôle qui n'accorde aucune capacité dans une application ne participe pas à la résolution de cette application. Il reste un fait métier dans TAKKU.
Conséquence : une personne dont le seul rôle sur un projet est MANAGER n'apparaît pas dans
la projection SIRRAT pour ce projet. Son appartenance reste visible dans TAKKU, où elle a un sens.
Ordre de restriction déclaré — pour SIRRAT¶
DEVELOPER < OPS
Cet ordre est déclaré et cohérent avec la politique courante : DEVELOPER peut composer,
OPS peut composer et déployer. Il ne doit néanmoins jamais être redéduit automatiquement des
capacités : une évolution de catalogue ne modifie pas silencieusement la règle de conflit.
Si MANAGER obtenait un jour une capacité SIRRAT réelle, son placement dans cet ordre serait
révisé explicitement — pas hérité de sa position dans la hiérarchie métier.
Plusieurs groupes sur le même projet — le plus restrictif l'emporte¶
C'est le point que cette ADR existe pour verrouiller.
Alice ∈ equipe-dawalale-back → DAWALALE = DEVELOPER
Alice ∈ astreinte-plateforme → DAWALALE = OPS
Résultat : DAWALALE = DEVELOPER (le plus restrictif)
+ conflit journalisé et affiché
Pourquoi pas « le plus fort » : ajouter quelqu'un à un groupe élèverait silencieusement ses droits sur tous les projets que ce groupe touche. C'est exactement l'élévation implicite que l'ADR-0031 ferme — la reconstruire ici la rendrait simplement plus difficile à voir.
Pourquoi pas « refuser tout accès en cas de conflit » : une incohérence de configuration retirerait un accès que la personne détient légitimement par au moins un groupe. Le système deviendrait fragile à une erreur de saisie amont.
Pourquoi le conflit doit être signalé : avec « le plus restrictif » seul, l'appartenance à
astreinte-plateforme ne produit rien pour Alice, et personne ne l'apprend jamais. Le conflit
doit donc apparaître au journal d'audit et dans l'écran de revue des habilitations, avec les deux
groupes nommés. Une règle sûre qui masque une erreur reste une erreur masquée.
Et l'échappatoire existe, explicite : si Alice doit réellement être OPS sur DAWALALE, un
droit USER le dit. C'est le point suivant.
Un seul droit USER par projet — invariant d'unicité¶
(USER, subject, project) → AU PLUS UN droit actif
Sans cet invariant, deux producteurs pourraient créer USER Alice → DAWALALE = DEVELOPER et
USER Alice → DAWALALE = OPS, et la règle de résolution ne dirait rien de ce cas. La
surcharge doit être déterministe par construction, pas par arbitrage.
Deux sources qui veulent modifier ce droit mettent à jour la même décision versionnée ; elles ne créent pas deux droits concurrents. À porter par une contrainte d'unicité au schéma, non par une convention.
Le droit USER est une surcharge, pas un maximum¶
GROUP equipe-dawalale → DAWALALE = OPS
USER Alice → DAWALALE = DEVELOPER
Résultat : DAWALALE = DEVELOPER ← le droit USER remplace, il ne se combine pas
Le droit individuel remplace le droit de groupe pour ce projet, dans les deux sens : il peut élargir comme restreindre.
C'est ce qui permet d'exprimer une exception restrictive — « Alice est dans l'équipe ops mais ne doit pas déployer sur ce projet-là ». Avec une règle « le plus fort gagne », cette exception serait tout simplement inexprimable, et on la contournerait en sortant Alice de son équipe, ce qui fausserait le staffing pour régler un problème d'habilitation.
Un droit USER porte sa propre référence de source et sa propre échéance : la surcharge est donc
justifiable et datée, comme tout le reste.
Récapitulatif¶
| Situation sur un projet donné | Résultat |
|---|---|
| aucun droit | aucun accès |
rôles ne conférant aucune capacité SIRRAT (MANAGER seul) |
aucune entrée dans la projection |
| un seul groupe | le rôle du groupe |
| plusieurs groupes, rôles identiques | ce rôle |
| plusieurs groupes, rôles différents | le plus restrictif (DEVELOPER < OPS) + conflit signalé |
droit USER présent |
le droit USER, quel qu'il soit |
droit USER + groupes |
le droit USER ; les groupes sont ignorés pour ce projet |
4. Expiration et révocation¶
| Cause | Effet |
|---|---|
l'octroi GROUP expire |
le droit disparaît pour tous les membres |
| la personne sort de l'équipe | ses droits issus de ce groupe disparaissent, ceux des autres restent |
l'octroi USER expire |
la surcharge tombe — le droit de groupe redevient applicable |
| l'affectation individuelle se termine | le droit USER correspondant disparaît |
Le troisième cas mérite attention : l'expiration d'une surcharge restrictive rend à la personne le droit du groupe, donc élargit. C'est cohérent — la surcharge était l'exception, et une exception qui expire cesse — mais cela doit être visible à la saisie : une exception restrictive doit avoir une échéance choisie en connaissance de cause, pas héritée d'un défaut.
5. Projection vers SIRRAT — aplatie¶
Entitlements résout USER + GROUP et pousse un instantané déjà résolu. SIRRAT ne connaît
ni les groupes, ni les appartenances.
{
"subject": "alice",
"projects": {
"dawalale": { "role": "DEVELOPER", "circles": ["dev", "int"] },
"dosali": { "role": "OPS", "circles": ["dev", "int", "rmoa"] }
},
"source": "dynors-entitlements",
"sourceRef": "...",
"sourceRevision": 47,
"generatedAt": "...",
"expiresAt": "..."
}
Minimisation, y compris entre services internes. SIRRAT n'a pas besoin de savoir qu'Alice
appartient à equipe-dawalale-back pour décider si elle peut composer un fichier. Ne transitent
donc ni identifiant d'entitlement, ni nom de groupe, ni identifiant interne — seulement ce qui
sert à décider.
Correction sur sourceRef. Une projection aplatie peut contenir plusieurs projets issus de
sources différentes — deux groupes ici, une surcharge individuelle là. Un sourceRef unique au
niveau de l'instantané ne peut donc pas expliquer l'origine de chaque droit, et tenter de l'y
encoder reviendrait à faire transiter le mécanisme.
source = dynors-entitlements
sourceRef = référence de CORRÉLATION de la projection
sourceRevision = ...
sourceRef sert à retrouver la décision dans le journal d'entitlements, pas à la résumer.
Contrepartie assumée, et elle est réelle : l'écran « Habilitations reçues » de SIRRAT ne peut plus répondre seul à « pourquoi Alice a ce droit ». Il donne la référence ; la réponse est dans le journal de décision d'entitlements (§9), qui seul connaît les groupes, la surcharge et le conflit résolu. C'est le prix de « SIRRAT reçoit la décision, pas le mécanisme » — et il faut l'accepter plutôt que de le contourner en glissant l'explication dans la projection.
GitLab reçoit une décision distincte, produite des mêmes faits¶
dynors-entitlements est également la source de la décision Git — mais le rôle SIRRAT ne
devient jamais implicitement un rôle GitLab. On avait distingué trois choses, et elles doivent
le rester :
rôle projet DEVELOPER · OPS · MANAGER ← le fait, chez TAKKU
capacité SIRRAT compose · deploy ← une décision
capacité Git READ · WRITE · NONE ← une AUTRE décision
Un DEVELOPER a probablement besoin de WRITE ; un OPS peut n'avoir besoin que de READ ;
un MANAGER peut n'avoir aucun accès Git. Ces correspondances sont des règles de mapping
explicites, tenues dans entitlements — jamais une équivalence tacite OPS ⇒ tel rôle GitLab.
project:dawalale
sirrat = { compose, deploy }
git = WRITE
Deux décisions, un seul fait d'origine, deux mappings écrits.
6. Ordre et idempotence des projections¶
Le modèle combine push à l'écriture, republication périodique et reconstruction complète. Trois émissions concurrentes pour un même sujet : les doublons et les messages hors ordre sont donc certains, pas hypothétiques.
Chaque projection porte une révision monotone, et son domaine de comparaison doit être explicite — « monotone attribuée par la source » ne suffit pas.
projectionKey = source + subject
sourceRevision strictement monotone POUR CETTE CLÉ
Sans domaine, une révision 5000 d'Alice rendrait incomparable une révision 42 de Bob, et une
reconstruction pourrait repartir sur une autre séquence. La comparaison n'a de sens que pour un
sujet donné et un flux donné.
subject · source · sourceRef · sourceRevision · generatedAt · expiresAt · projects[]
La reconstruction complète doit produire, pour chaque sujet, une révision supérieure à la dernière connue — sinon elle serait ignorée par sa propre règle, et la reconstruction ne reconstruirait rien.
Règles appliquées par SIRRAT, sans exception :
pour projectionKey = (source, subject) :
révision > révision locale → appliquer atomiquement
révision = révision locale → idempotent, aucun effet
révision < révision locale → IGNORER et journaliser
Une projection ancienne ne doit jamais écraser une plus récente. Sans cette règle, une republication retardée par le réseau peut rendre à quelqu'un un accès qu'une clôture venait de lui retirer — c'est-à-dire transformer le filet de sécurité en faille.
generatedAt sert au diagnostic (mesurer le retard), jamais à décider : deux horloges de
machines différentes ne se comparent pas.
7. Une seule horloge d'autorisation projet¶
now < expiresAt → projection fraîche → périmètre appliqué
now >= expiresAt → projection périmée → AUCUN droit projet
Aucun second seuil de fraîcheur. Les actes sensibles de l'ADR-0031 §7 (déploiement RMOA, révélation d'un secret) exigent « projection fraîche + capacité requise » — où « fraîche » est exactement cette règle, sans variante.
Avec un TTL de 15 minutes, un seuil supplémentaire à 5 minutes ne ferait qu'ajouter une troisième horloge à côté de la session de 8 heures, et plus personne ne saurait laquelle gouverne quoi.
8. Affectation — TAKKU propriétaire, JARAAF consommateur¶
Le point classé « Inconnu » par l'ADR bounded contexts est tranché :
Pour la relation opérationnelle personne ↔ projet / mission, TAKKU est propriétaire. JARAAF consomme cette information lorsqu'elle lui est nécessaire.
JARAAF peut l'enrichir et l'exploiter — feuilles de temps, charge, facturation du temps — mais ne détient jamais une affectation concurrente. Sans cette règle, on obtient inévitablement « TAKKU dit qu'Alice est sur DAWALALE, JARAAF dit qu'elle n'y est plus », et il faut alors arbitrer au cas par cas ce qui aurait dû être décidé une fois.
9. Audit de la décision effective¶
dynors-entitlements journalise, pour chaque résolution :
- le sujet et le projet ;
- l'origine retenue — quel groupe, ou le droit individuel ;
- les origines écartées et pourquoi (conflit résolu au plus restrictif, surcharge
USER) ; - la révision produite.
C'est ce qui permet de répondre à « pourquoi Alice a-t-elle ce rôle » sans que SIRRAT ait à transporter la structure d'appartenance.
Conséquences¶
Positif. Les équipes évitent la multiplication d'octrois identiques ; aucune élévation implicite n'est possible, ni entre projets ni entre groupes ; l'exception restrictive devient exprimable ; les projections supportent le désordre et les doublons ; une seule horloge gouverne l'autorisation projet.
Coût. dynors-entitlements acquiert SubjectType.GROUP, un résolveur déterministe et un
journal de décision enrichi ; TAKKU acquiert la notion d'équipe avec période d'appartenance ;
SIRRAT acquiert le contrôle de révision.
Risque à surveiller. La règle du plus restrictif rend une appartenance de groupe silencieuse lorsqu'un autre groupe est plus restrictif. La signalisation du conflit n'est donc pas un confort : c'est ce qui empêche l'erreur de rester invisible. Si elle n'est pas implémentée, la règle devient dangereuse par excès de sécurité.
Invariant de sécurité — valable pendant toute la migration¶
Tant qu'une information d'autorisation est absente, ambiguë, périmée ou incohérente, elle ne doit jamais élargir l'accès.