ADR-0003 — dynors-entitlements source-agnostique, alimenté par events multi-producteurs¶
Date : 2026-05-07 (rédigée rétroactivement 2026-06-30 — backfill d'une décision déjà actée)
Catégorie : A (modèle d'autorisation transverse)
Décideur : Lead architecte DYNORS
Statut : Acceptée — la 1ʳᵉ phrase du §1 (« il ne décide rien par lui-même ») est
amendée par [[0033-capacites-contextualisees-politique-declarative]] (2026-08-29). Le reste de
la décision est inchangé ; conformément à la convention du dépôt, le texte ci-dessous n'est pas
réécrit.
S'appuie sur : [[0002-jwt-minimal-aud-realm]] (les droits ne sont pas dans le JWT)
Source de vérité : dynors-docs/docs/05-architecture-si/dynors-entitlements.md ; décision modèle entitlements du 2026-05-07
Contexte¶
Une fois acté que le JWT ne porte pas les droits ([[0002-jwt-minimal-aud-realm]]), il faut un endroit qui réponde à la question runtime : « ce sujet a-t-il le droit de faire ça maintenant ? ».
Le réflexe serait de faire de dynors-billing (la facturation B2B SaaS) la source unique des
droits. C'est incorrect pour DYNORS : ELISA onboarde des e-commerçants et des livreurs sans
abonnement mensuel, DAWALALE vend un pack permis à un apprenant qui n'est pas un tenant, un
compte démo n'a aucune facture. Forcer ces quatre modèles commerciaux (B2B SaaS, B2B onboardé,
B2C transactionnel, grant administratif) dans le moule facturation pollue dynors-billing et place
des dépendances critiques au mauvais endroit.
Décision¶
-
dynors-entitlementsest une projection runtime source-agnostique. Il ne décide rien par lui-même : il consomme des events émis par les producteurs légitimes, projette l'état (UPSERTidempotent parevent_id), et expose une API de lecture via SLY (/entitlements/**,SlyTransitFilterHMAC obligatoire — jamais appelé par un frontend). -
Plusieurs producteurs, pas un seul.
dynors-billing(contrats B2B →tenant.contract.*),elisa-backend(onboarding tenants/livreurs),dawalale-backend(achats packs B2C),medisen-backend(consultations B2C),dynors-auth(rôles users),admin-panel(grants).dynors-billingn'est qu'un producteur parmi d'autres, maître du seul B2B SaaS récurrent. -
Modèle de donnée unifié : un entitlement =
(subject_type ∈ {TENANT,USER,CUSTOMER,COURIER}, subject_id, scope, permissions, expires_at, source, source_ref, status). Les quatre modèles commerciaux cohabitent sans conflit car lessubject_typeetscopediffèrent. -
Fraîcheur : cache 60 s côté apps + event
entitlement.invalidatedrepublié à la révocation pour purge immédiate. Latence changement→effet : ~5 s nominal, 60 s en mode dégradé bus. -
Audit : chaque décision tracée (
entitlement_decision_log), conservation 7 ans (obligations banque/assurance Sénégal), archive immuable S3 Object Lock.
Conséquences¶
Positif : dynors-billing reste propre (facturation pure) ; ajouter un nouveau modèle
commercial = ajouter un producteur d'events, sans toucher au cœur ; révocation rapide ; piste
d'audit conforme régulé ; frontière billing / entitlements explicite (§11 du doc source).
Coût : un service de plus à exploiter (PostgreSQL + Redis + consumer bus) ; discipline nécessaire sur le format normalisé des events et l'idempotence ; fail-closed obligatoire côté client (un entitlements indisponible doit refuser, pas autoriser).
Composants impactés : dynors-platform/entitlements, dynors-entitlements-client, tous les
producteurs d'events, dynors-events (transport).
Alternatives écartées¶
dynors-billingsource unique : casse sur ELISA (pas d'abonnement), DAWALALE (pas de tenant), démos (pas de facture). Pollue le modèle de facturation. Écartée.- Droits dans chaque app, en silo : duplication, incohérence, audit impossible à centraliser. Écartée au profit de la projection unique alimentée par events.
Références¶
dynors-docs/docs/05-architecture-si/dynors-entitlements.md(modèle complet, §3 events, §11 frontière billing)dynors-docs/docs/05-architecture-si/dynors-auth.md(JWT minimal en amont)- ADR-0002 (JWT minimal)