Aller au contenu

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.