ADR-0009 — Taxonomie des templates de déploiement : audience × tenancy × scenario × gouvernance¶
Date : 2026-06-10 (amendée 2026-06-21)
Catégorie : A (convention d'outillage SIRRAT/TAKKU, roadmap plateforme validée)
Décideur : Lead architecte DYNORS
Statut : Acceptée (avec amendements 2026-06-21 — voir §Amendements)
Sprint d'implémentation : amorcé (CODEX_SCENARIO, CODEX_DEPLOY_TARGET déjà constants ; reste schéma catalog + colonnes profil + élargissement catalogue + gate isolation)
Contexte¶
Le catalogue de templates SIRRAT (sirrat_template_catalog + sirrat_template_variable, Codex SirratCodexConstants) définit aujourd'hui quatre familles :
dynors-spring-saas@2.0.0dynors-angular-portal@2.0.0dynors-pos@2.0.0dynors-worker@2.0.0
Ces familles décrivent la forme technique d'un composant (backend Spring, front Angular, POS, worker). Mais elles ne capturent pas trois dimensions qui changent réellement le déploiement, la surface d'exposition et les gates :
- Audience — l'app est-elle un SaaS exposé à des clients externes (DAWALALE, SuperGest, Mediconnect, ELISA) ou un outil à usage interne DYNORS sur domaine
dynors.com(JARAAF, BOOKS, RAGNAR, HEISENBERG) ? Aujourd'hui JARAAF et BOOKS reçoivent le même templatespring-saasqu'un SaaS client, alors qu'ils n'ont ni le même besoin d'isolation, ni les mêmes cercles d'environnement, ni le même domaine. - Tenancy — base partagée multi-tenant (
dynors.db.multitenant+ RLS) ou instance dédiée par client. DAWALALE est en SHARED ; un déploiement on-premise client est de facto DEDICATED. - Scenario — le champ existe déjà dans
sirrat.config.yml(managed | on-premise | cloud-client | regie) mais ne pilote ni le choix de template, ni la convention d'URL, ni les gates. Il est descriptif, pas normatif.
Deux réalités du portefeuille montrent en outre que ces dimensions ne sont pas des propriétés figées de l'app :
- FISCAL est un SaaS vendu à des clients, mais DYNORS l'utilise aussi pour lui-même. La même instance sert les deux usages : DYNORS est simplement un tenant de son propre SaaS (« tenant zéro », dogfooding). Classer FISCAL « interne » ou « saas » de façon exclusive serait faux dans les deux cas.
- DAWALALE est aujourd'hui un SaaS multi-tenant opéré par DYNORS, mais sa filialisation est prévue. Filialisée avec un seul tenant, son déploiement devient plus simple (mono-tenant de fait), et sa gouvernance change (entité légale propre, compte propre).
Résultat : la distinction SaaS / interne / on-premise vit dans la tête des gens, pas dans le catalogue, et le catalogue ne sait pas représenter une app qui change de profil dans le temps ou qui en cumule deux. C'est une source de friction et d'erreur exactement là où on veut industrialiser.
Décision¶
Faire des familles de templates le produit cartésien d'axes explicites, faire de scenario une variable normative qui conditionne la résolution, et attacher le profil au déploiement, pas à l'app.
Principe fondateur — le profil est une propriété du déploiement¶
Une app n'est pas « SaaS » ou « interne » dans l'absolu : chaque déploiement (instance × cercle) porte son propre profil, et ce profil peut évoluer sans changer l'identité de l'app. Conséquences :
- FISCAL : un seul déploiement
saas · shared · managed · dynors. L'usage interne DYNORS n'est pas une seconde instance « internal » — DYNORS est le tenantdynorsde son propre SaaS. Interdiction de dériver une instance interne séparée (double maintenance, divergence). - DAWALALE : aujourd'hui
saas · shared · managed · dynors; à la filialisation, le déploiement cible devientsaas · dedicated · managed · filiale(mono-tenant de fait). Le changement de profil est un événement de gouvernance tracé (nouvelle ligne de mapping, datée), pas une réécriture de l'app. Le code gardeTenantContextet untenantCodeunique — retirer le multi-tenant coûterait plus cher que de le laisser dormant, et préserve un retour au multi-tenant (réseau d'auto-écoles) sans migration.
Les quatre axes¶
| Axe | Constante Codex | Valeurs | Pilote |
|---|---|---|---|
deployment_audience |
CODEX_DEPLOYMENT_AUDIENCE |
internal | saas |
domaine, cercles d'environnement, exposition réseau |
tenancy |
CODEX_TENANCY |
shared | dedicated |
isolation DB (RLS vs instance), gate d'isolation tenant |
scenario |
CODEX_SCENARIO (existant) |
managed | on-premise | cloud-client | regie |
topologie de déploiement, origine des secrets, qui opère |
gouvernance |
CODEX_GOUVERNANCE |
dynors | filiale | client |
entité légale propriétaire : compte de reversement (ADR-0010), contrats, domaine de marque, qui arbitre |
Amendement 2026-06-21 — collision sémantique évitée. L'axe est nommé
deployment_audience(constanteCODEX_DEPLOYMENT_AUDIENCE) et nonaudience, pour éviter la confusion avecTAIL_AUTH_AUDIENCEqui désigne le realm JWT (ADR-0011). Les deux concepts coexistent : l'un est un profil de déploiement, l'autre est un claim de token. Tout code, seed Liquibase ou schema JSON qui mentionneaudiencesans préfixe doit être considéré comme un défaut de conformité.
La forme technique (spring-saas, angular-portal, pos, worker) reste orthogonale : un template effectif = forme technique + profil (audience, tenancy, scenario, gouvernance).
L'axe gouvernance ne change pas la topologie technique à lui seul — il pilote ce qui relève de DYNORS Groupe : vers quel compte SATELLITE part le reversement (la filiale a son entité légale et son compte propre), qui signe les contrats, qui possède le domaine de marque, et qui arbitre les évolutions. Une app gouvernance = filiale reste hébergée sur les plateformes DYNORS (SLY, PAIEMENT, FISCAL) tant que la doctrine Groupe ne décide pas autrement.
Conséquences normatives par profil (extraits)¶
| Profil | Domaine | Cercles | Exposition API | Secrets |
|---|---|---|---|---|
internal · shared · managed (JARAAF, BOOKS) |
*.{app}.dynors.com |
dev, rmoa-dynors, prod | API interne (pas de cercle client) | Vault DYNORS |
saas · shared · managed (DAWALALE, SuperGest) |
domaine propre en prod, *.dynors.com hors-prod |
dev, rmoa-dynors, rmoa-client, prod | API exposée + WAF/CORS strictes | Vault DYNORS |
saas · dedicated · on-premise (client souverain) |
domaine client | dev, rmoa-client, prod client | API exposée chez le client | Ansible Vault / secrets client |
saas · dedicated · cloud-client |
domaine client, infra client | idem | idem | ESO sur cluster client |
Cible de déploiement par cercle — socle mutualisé vs pod dédié¶
À l'intérieur d'un cercle, chaque déploiement a une cible : un socle technique mutualisé (un hôte du cercle, plusieurs containers en cohabitation, ports distincts — le terme interne peut varier, ex. « omnical ») ou un pod dédié (hôte/namespace propre, isolation réseau). C'est la 5ᵉ propriété du profil, dérivée des 4 autres et de la sensibilité de l'app — pas une décision indépendante.
Règle de dérivation (vérifiée par SIRRAT Gate 0) :
| Condition | Cible |
|---|---|
App de sensibilité élevée : paiement, fiscal |
pod (toujours, tous cercles) |
scenario ∈ {on-premise, cloud-client} |
pod (infra séparée par définition) |
cercle ∈ {rmoa-{client}, production} ET audience = saas |
pod |
tenancy = dedicated |
pod |
Tout le reste (internal ou saas-shared-managed hors prod) |
socle |
Conséquence : les apps internes (JARAAF, BOOKS, RAGNAR, HEISENBERG) et les SaaS partagés en dev/rmoa-dynors tournent sur un même socle par cercle (économie + base unique à durcir). DYNORS PAIEMENT et FISCAL sont toujours en pod (argent, blast-radius ADR-0008, surface réglementaire DGI). Les cercles client et la prod SaaS sont en pod (isolation).
Conséquences techniques :
- Codex
CODEX_DEPLOY_TARGET∈ {socle,pod} — résolu au manifeste, jamais saisi à la main. - Nginx (rôle
dynors-nginx-proxy) : l'upstreamapi_upstream_hostvautsocle-{circle}.intranet.dynors.compour la ciblesocle,{app}-api-{circle}.pods.dynors.com(ou nom de service K8s) pourpod. Conventions de nommage par cercle à acter avec l'infra (DNS interne). - L'app ne sait rien de sa cible : la convention URL publique (
api.{app}{suffixe}.dynors.com/v{n}) reste identique. Seul l'edge nginx connaît l'upstream — c'est précisément ce qu'on veut.
Règles dérivées¶
- Une app
internaln'a pas de cerclermoa-clientni de domaine propre — TAKKU ne les génère pas, SIRRAT Gate 0 les refuse. - Une app
saasenshareddoit passer un gate d'isolation tenant (test prouvant qu'un tenant ne lit pas les données d'un autre) avant promotion vers un cercle exposé. Ce gate n'existe pas aujourd'hui — il devient obligatoire pour ce profil à partir du 2026-09-30 (cf. §Gate isolation — date butoir). scenario = on-premise | cloud-clientimpose une origine de secrets non-DYNORS (Ansible Vault ou ESO client) — SIRRAT refuse un manifeste qui pointerait le Vault DYNORS depuis un déploiement client.- L'axe
tenancy = dedicatedse combine aveclane(ADR-0017/0019) : un déploiement dédié à un client peut être réalisé soit par un couloir dédié dans un cercle (cas standard hors-prod), soit par une stack distincte (cercle client enscenario=on-premise|cloud-client). SIRRAT Forge refuse une combinaison(tenancy=dedicated, scenario=managed, lane=ref)en cercle exposé — il faut soit nommer le couloir, soit changer de scénario.
Gate d'isolation tenant — date butoir et waiver¶
- À partir du 2026-09-30 : SIRRAT Gate 2 (avant promotion vers
production) refuse toute promotion d'un déploiementdeployment_audience=saas · tenancy=sharedsans rapport de gate d'isolation tenant vert daté de moins de 90 jours. - Avant le 2026-09-30 : promotion autorisée avec waiver explicite (
sirrat.config.yml: gates.tenant_isolation.waiver_until: 2026-09-30) signé par le lead architecte ; le waiver est tracé dans le journal de gouvernance et un ticket de mise en conformité est ouvert automatiquement. - Périmètre minimal du gate (à outiller) : test automatisé multi-tenant qui (i) crée deux tenants distincts, (ii) écrit des données dans chacun, (iii) tente une lecture cross-tenant via toutes les routes API publiées, (iv) échoue si au moins une route retourne 200 avec des données du second tenant. Le rapport est stocké dans
sirrat_gate_reportavec digest signé.
Conséquences¶
Bénéfices¶
- Le catalogue encode la réalité : plus de confusion SaaS / interne, plus de cercle client fantôme sur une app interne.
- La surface d'exposition devient une propriété déclarée et vérifiée, pas une convention orale : une app interne n'expose pas d'API publique par défaut.
- L'isolation tenant des SaaS partagés devient un gate, pas une supposition.
- TAKKU peut générer le bon squelette du premier coup (cercles, domaine, secrets) — moins de reprise manuelle.
Coûts / contraintes induites¶
- Migration du catalogue : ajouter les axes aux templates existants et reclasser les apps actuelles (mapping ci-dessous à acter).
- Le gate d'isolation tenant doit être conçu et outillé (test type « cross-tenant read » automatisé).
- TAKKU et SIRRAT Gate 0 doivent lire et valider les quatre axes — évolution de leur logique de validation.
- Tout changement de profil d'un déploiement (ex. filialisation DAWALALE) est un événement de gouvernance : nouvelle ligne datée du mapping, validation CTO si l'axe
gouvernancechange.
Composants impactés¶
dynors-internal/applications/sirrat—SirratTemplateCatalog(schéma : ajoutaudience/tenancy/gouvernance,scenariorendu normatif),EnvironmentManifestService, validations Gate 0.- TAKKU — génération conditionnée par profil.
SirratCodexConstants— constantes d'axes (CODEX_AUDIENCE,CODEX_TENANCY, déjàCODEX_SCENARIO).- Convention URL (handoff) — formaliser « domaine propre = profil
saas, prod uniquement ».
Mapping initial des déploiements (à acter)¶
Le mapping est par déploiement et daté — une ligne nouvelle (jamais une réécriture) quand un profil évolue :
| Déploiement | audience | tenancy | scenario | gouvernance | Note |
|---|---|---|---|---|---|
| JARAAF, BOOKS, RAGNAR, HEISENBERG | internal | shared | managed | dynors | usage interne, domaines *.dynors.com |
| FISCAL | saas | shared | managed | dynors | DYNORS = tenant dynors de son propre SaaS — pas d'instance interne séparée |
| DAWALALE (aujourd'hui) | saas | shared | managed | dynors | — |
| DAWALALE (cible filialisation) | saas | dedicated | managed | filiale | mono-tenant de fait ; compte SATELLITE de la filiale (ADR-0010) ; date à acter par le CTO |
| SuperGest, Mediconnect, TRACIUM | saas | shared | managed | dynors | — |
| Déploiement client souverain (cas par cas) | saas | dedicated | on-premise / cloud-client | client | secrets client, gate 3 + PV |
Alternatives considérées¶
Alternative A — Garder les 4 familles techniques uniquement. Écartée : ne capture pas l'audience ni la tenancy, laisse la distinction SaaS/interne hors du système. C'est l'état actuel, qui produit la friction.
Alternative B — Une famille par app (templates dédiés). Écartée : explosion combinatoire, duplication, perte de la mutualisation qui est la raison d'être du catalogue.
Alternative C — Encoder seulement audience (binaire interne/saas). Insuffisant : on-premise et dedicated changent l'origine des secrets et la topologie indépendamment de l'audience. Les axes sont nécessaires et non redondants ; gouvernance est le seul qui ne change pas la topologie technique mais détermine les flux d'argent et de contrats (ADR-0010).
Plan d'exécution¶
Sprint 1 (immédiat, 2026-06 → 2026-07)¶
- Schéma
sirrat_template_catalog— publier une nouvelle versionv2.1.0de chaque template existant (dynors-spring-saas,dynors-angular-portal,dynors-pos,dynors-worker) plutôt que muterschema_jsondev2.0.0. Bénéfice : les manifestes pinnés surv2.0.0ne cassent pas ; la migration est explicite (takku upgrade-template). Les colonnesdeployment_audience,tenancy,gouvernancesont ajoutées au catalog mais seules les versions ≥ 2.1.0 les exigent (validation conditionnelle dansEnvironmentManifestService). - Constantes Codex : ajouter
CODEX_DEPLOYMENT_AUDIENCE,CODEX_TENANCY,CODEX_GOUVERNANCE(en cohérence avecCODEX_SCENARIOetCODEX_DEPLOY_TARGETdéjà existants). - Acter le mapping initial des déploiements (tableau ci-dessous) en seed Liquibase (
sirrat_deployment_profile), avec colonnes datées (valid_from,valid_until). - Faire lire les axes par Gate 0 : refuser cercle client sur
internal, refuser Vault DYNORS sur scénario client, refuser combinaisons interdites. - Documenter dans le Codex de référence (
SIRRAT_CODEX_REFERENCE.md) et la convention URL (handoff).
Sprint 2 (2026-07 → 2026-09)¶
- Élargir le catalogue : publier les nouveaux templates listés au §Catalogue élargi.
- Adapter TAKKU pour générer cercles/domaine/secrets selon le profil et la famille étendue (mobile, doc-site, landing…).
- Concevoir et outiller le gate d'isolation tenant pour
deployment_audience=saas · tenancy=shared. Outil : module test transverse dansdynors-extensions/dynors-tenant-isolation-check. - Filer le waiver
tenant_isolationpour les SaaS existants (DAWALALE, SuperGest, Mediconnect, ELISA) jusqu'au 2026-09-30 ; ticket de mise en conformité par app.
Sprint 3 (2026-09 → 2026-10)¶
- Activer le refus Gate 2 sans rapport vert au 2026-09-30 (kill switch piloté par config SIRRAT, pas par code).
- ADR-0010 si accepté : câbler
gouvernancesur le routage payouts DYNORS PAIEMENT (compte SATELLITE filiale).
Catalogue élargi — nouveaux templates à publier (Sprint 2)¶
Les 4 familles existantes (spring-saas, angular-portal, pos, worker) ne couvrent pas l'ensemble des composants réels du portefeuille. Familles à ajouter, chacune avec ses variables minimales et son schéma schema_json distinct :
| Template (nouveau) | Famille | Cible (exemples du portefeuille) | Variables clés additionnelles | Cible socle/pod par défaut |
|---|---|---|---|---|
dynors-mobile-rn@1.0.0 |
mobile-rn |
Mediconnect mobile, SuperGest mobile, DAWALALE mobile | MOBILE_API_URL, MOBILE_DEEP_LINK_SCHEME, MOBILE_BUNDLE_ID, MOBILE_STORE_TARGETS (ios/android), MOBILE_PUSH_PROVIDER |
n/a (artifact store) |
dynors-mobile-flutter@1.0.0 |
mobile-flutter |
apps Flutter (alt RN) | idem + FLUTTER_CHANNEL |
n/a |
dynors-bff@1.0.0 |
bff |
BFF dédié front (composition d'appels FISCAL+PAIEMENT+TRACIUM par cas d'usage métier) | BFF_UPSTREAMS (liste), BFF_CACHE_TTL, BFF_AGGREGATION_TIMEOUT_MS |
socle |
dynors-adapter@1.0.0 |
adapter |
TaxAuthorityAdapter (DGI), adapter PSP, adapter ELISA | ADAPTER_PARTNER_CODE, ADAPTER_CIRCUIT_BREAKER_THRESHOLD, ADAPTER_RETRY_POLICY, ADAPTER_HMAC_SECRET_REF |
pod (sensibilité argent/légal) |
dynors-webhook-receiver@1.0.0 |
webhook-receiver |
Réception callbacks PSP, callbacks partenaires DGI, callbacks ELISA | WEBHOOK_HMAC_SECRET_REF, WEBHOOK_REPLAY_WINDOW_MS, WEBHOOK_IDEMPOTENCY_TTL |
pod |
dynors-cron-batch@1.0.0 |
cron-batch |
Jobs périodiques (rapprochement paie, clôture comptable, génération factures programmées) — distinct du worker Kafka qui écoute en continu |
CRON_SCHEDULE, CRON_JOB_TIMEOUT, CRON_FAILURE_POLICY |
socle |
dynors-migration-job@1.0.0 |
migration-job |
Liquibase one-shot, backfill, RGPD purge ponctuelle | MIGRATION_TARGET_DB, MIGRATION_DRY_RUN, MIGRATION_LOCK_TIMEOUT |
n/a (Job K8s ou tâche Ansible) |
dynors-gateway-portal@1.0.0 |
gateway-portal |
SLY lui-même (apps internes / extension SLY), Portal Edge — routage YAML, pas de DB applicative | GATEWAY_ROUTES_FILE, GATEWAY_RATE_LIMIT_DEFAULT, GATEWAY_TLS_MODE |
pod (toujours) |
dynors-doc-site@1.0.0 |
doc-site |
dynors-docs (MkDocs), docs satellites |
DOC_SITE_THEME, DOC_SITE_BASE_URL, DOC_SITE_SEARCH_PROVIDER |
socle (statique) |
dynors-landing-static@1.0.0 |
landing-static |
landing dawalale.com, landing supergest.com, page institutionnelle | LANDING_DOMAIN, LANDING_ANALYTICS_ID, LANDING_CONTACT_FORM_ENDPOINT |
socle (CDN possible) |
dynors-monitoring-stack@1.0.0 |
monitoring-stack |
Stack Loki+Prometheus+Grafana+Tempo (ops/monitoring) — un déploiement par cercle | MONITORING_RETENTION_LOKI, MONITORING_RETENTION_PROM, MONITORING_TEMPO_ENABLED, MONITORING_ALERT_CHANNELS |
pod |
dynors-shared-infra@1.0.0 |
shared-infra |
Postgres mutualisé, Redis, Kafka — services techniques du cercle (jamais d'app applicative dessus) | INFRA_KIND (postgres/redis/kafka), INFRA_VOLUME_SIZE, INFRA_BACKUP_POLICY |
pod |
Principe : un nouveau composant DYNORS choisit une famille de template ; il ne peut pas mixer (ex. pas de spring-saas qui sert aussi de webhook-receiver — c'est deux apps, deux déploiements, deux profils).
Vérification SIRRAT : Gate 0 valide que le composant choisi correspond à une family du catalog active et que sa version est ≥ 2.1.0 pour bénéficier des axes de profil. Les v2.0.0 restent valides mais ne sont pas promues au-delà de dev.
Suivi¶
Critère de succès : créer une nouvelle app interne via TAKKU ne génère aucun cercle rmoa-client ni domaine propre ; créer une app SaaS partagée bloque la promotion tant que le gate d'isolation tenant n'est pas vert.
Signal de régression : une app interne qui expose une API publique, ou une app SaaS partagée promue sans gate d'isolation, est un défaut de conformité.
Amendements¶
| Date | Amendement | Origine |
|---|---|---|
| 2026-06-21 | Renommage audience → deployment_audience (CODEX_DEPLOYMENT_AUDIENCE) pour éviter collision avec TAIL_AUTH_AUDIENCE (realm JWT, ADR-0011). |
Revue critique avant acceptation. |
| 2026-06-21 | Date butoir gate isolation tenant : 2026-09-30, waiver explicite tracé jusque-là. | Revue critique avant acceptation. |
| 2026-06-21 | Choix migration via publication v2.1.0 (et non mutation de schema_json des v2.0.0 existantes) — préserve les manifestes pinnés. |
Revue critique avant acceptation. |
| 2026-06-21 | Lien explicite tenancy=dedicated × lane (ADR-0017/0019) ; combinaison interdite (dedicated, managed, lane=ref) en cercle exposé. |
Revue critique avant acceptation. |
| 2026-06-21 | Catalogue élargi : ajout de 11 nouveaux templates (mobile-rn, mobile-flutter, bff, adapter, webhook-receiver, cron-batch, migration-job, gateway-portal, doc-site, landing-static, monitoring-stack, shared-infra) — les 4 familles initiales ne couvrent pas le portefeuille réel. |
Revue critique avant acceptation. |
Références¶
- Codex :
dynors-internal/applications/sirrat/backend/src/main/resources/codex/SIRRAT_CODEX_REFERENCE.md - Concept SIRRAT :
dynors-docs/docs/07-plateforme-devops/sirrat-concept.md(sirrat.config.yml, scénarios) - Convention URL :
claude-handoff-dynors/DYNORS_URL_CONVENTION_RULES.md - Doctrine Groupe (séparation produit/plateforme) :
docs/architecture/DOCTRINE_DYNORS_GROUPE_STRUCTURE_PORTEUSE.md - ADR-0011 (realm JWT,
TAIL_AUTH_AUDIENCE) — collision sémantique évitée - ADR-0017 (couloir/
lane) et ADR-0019 (modèle SIRRAT × couloir) - ADR-0010 (payouts/trésorerie Groupe) — câblage
gouvernanceconditionnel