Décisions d'architecture (ADR)¶
Les Architecture Decision Records documentent les décisions structurantes de Forge.
Chaque ADR a force décisionnelle : à lire avant toute proposition qui le concerne.
Un nouvel ADR est requis pour toute décision structurante (docs/adr/<numéro>-<sujet>.md).
| Numéro | Sujet |
|---|---|
| ADR-001 | Stratégie d'authentification |
| ADR-002 | Stockage de session |
| ADR-003 | API publique en anglais |
| ADR-004 | Périmètre du core minimal strict |
| ADR-005 | Packaging hybride monorepo + multi-distributions PyPI |
| ADR-006 | Python 3.12+ minimum |
| ADR-007 | Adoption formelle de la charte v2 |
| ADR-008 | Audit auth : logging fourni, persistance applicative |
| ADR-009 | Politique de stabilité : audits, bêta consolidée, tests terrain |
| ADR-010 | API canonique auth/session |
| ADR-011 | Périmètre du vocabulaire d'audit auth |
| ADR-012 | Politique de dépréciation du format legacy |
| ADR-013 | Politique nullable / required des contrats |
| ADR-014 | Emplacement du contrat RBAC |
| ADR-015 | Handshake TLS par thread (dev-server) |
| ADR-016 | Unification du modèle opt-in : concept unique, cycle install/enable à 4 verbes |
| ADR-017 | Type slug et module URL-slug canonique (core/http/slug.py) |
| ADR-018 | Extraction du traitement d'image hors du core : forge-mvc-images (proposé) |
| ADR-019 | Extraction de l'upload générique hors du core : forge-mvc-files (proposé) |
| ADR-020 | Périmètre de forge-mvc-files : primitives de stockage média génériques (proposé) |
| ADR-021 | Extraction de pivot advanced hors du core : forge-mvc-pivot (accepté) |
| ADR-022 | Extraction de l'email hors du core : forge-mvc-mail (accepté) |
| ADR-023 | forge starter:build comme seule façon de construire un starter ; forge new produit un projet nu (accepté) |
| ADR-024 | Bootstrap par squelette dédié et dépendance core via pip (accepté) |
| ADR-025 | welcome-forge : tutoriel continu manuel au lieu de starters par palier (accepté) |
| ADR-026 | Accesseurs de Request nommés par leur source : query et route (accepté) |
| ADR-027 | Extraction de l'i18n vers forge-mvc-i18n, repli no-op conservé dans le noyau (accepté) |
| ADR-028 | welcome-forge : tutoriel continu manuel sur les trois niveaux, un mini-projet par niveau (accepté) |
| ADR-029 | Convention de route : chemin /contrôleur/méthode (index nu), nom contrôleur-méthode (accepté) |
| ADR-030 | Injection de routes dans mvc/routes.py par commande explicite et portée de la règle 4.3 (accepté) |
| ADR-031 | Découplage complet du mail hors de core.forge ; forge-mvc-mail lit sa config depuis l'environnement (accepté) |
| ADR-032 | Périmètre de la config upload : seul upload_max_size est du core, le reste va aux opt-ins files/images (accepté) |
| ADR-033 | forge db:apply applique les migrations avec DB_ADMIN_* (et non DB_APP_*) : forge_app reste DML strict (accepté) |
| ADR-034 | forge new génère DB_NAME / DB_APP_LOGIN à partir du nom normalisé du projet, sans suffixes _db/_app (accepté) |
| ADR-035 | Modèle pédagogique unique : parcours réalisés à la main depuis la doc, retrait de starter:build/starter:list et de la génération (accepté) |
| ADR-036 | Typage statique du cœur vérifié en CI (Pyright), py.typed, strictness par cliquet en commençant par l'API publique (accepté) |
| ADR-037 | Agrégation par comptage dans forge-mvc-stats (accepté) |
| ADR-038 | Documentation des opt-ins embarquée par paquet (packages/<paquet>/docs/), agrégée dans le site unique ; slug d'URL = nom sans forge-mvc- (accepté, pilote stats validé) |
| ADR-039 | Refonte de l'architecture d'information de docs/ (cœur) : un sujet = un emplacement canonique, tronc « Opt-ins officiels », dédoublonnages (proposé) |
| ADR-040 | Surface de test par paquet opt-in : modèle hybride (transversal à la racine, smoke + unitaire dans le paquet), testpaths = tests packages, importorskip (accepté) |
| ADR-041 | Infrastructure de test partagée (forge-mvc-testing dev-only, plugin pytest + FakeRequest) pour rendre les tests de paquet autonomes (accepté) |
| ADR-042 | Découpler la documentation du cœur et celle des opt-ins (accepté) |
| ADR-043 | Documentation embarquée du cœur et du CLI ; renommage forge_cli → cli (accepté) |
| ADR-044 | Le dépôt Forge ne porte que le framework ; application racine relocalisée (accepté) |
| ADR-045 | Intégrer la publication du site officiel dans Forge (accepté) |
| ADR-046 | Registre de loaders de templates Jinja pour les opt-ins (accepté) |
| ADR-047 | Couche de guidance agent IA dans les applications Forge (accepté) |
| ADR-048 | Parcours d'accueil « welcome-projet » dans le squelette (annulé) |
| ADR-049 | Repositionnement : framework de production auditable (accepté) |
| ADR-050 | Opt-in QR Code forge-mvc-qrcode (accepté) |
| ADR-051 | Insertion d'une méthode dans le contrôleur des pages publiques (make:public-page), explicite, idempotente, fail-safe et ciblée (proposé) |
| ADR-052 | Stratégie et critères des opt-ins : deux filtres d'admission (runtime WSGI, charte), classification des candidats, ordre recommandé (proposé) |
| ADR-053 | Extraction du déploiement (deploy:init/deploy:check + gabarits + doc) dans un opt-in CLI-only forge-mvc-deploy (proposé) |
| ADR-054 | Cœur agnostique BDD : backends (MariaDB, SQLite, PostgreSQL, SQL Server) en opt-ins exclusifs, découverts par entry points (acceptée) |
| ADR-055 | Classification des opt-ins par destination : champ category au catalogue, taxonomie unique (CLI + docs), sans renommer les paquets (proposé) |
| ADR-056 | Extraction du contrat (schéma) et de l'outillage RBAC (rbac:validate/rbac:audit) du cœur vers l'opt-in forge-mvc-rbac (acceptée) |
| ADR-057 | Découplage du schéma pivot : relations (cœur) cesse de référencer pivot (bloc opaque), pivot.schema.json extrait vers forge-mvc-pivot (proposé) |
| ADR-058 | Source unique des schémas JSON : cli/schemas/ canonique, suppression de la copie racine schemas/, squelette et embeds opt-in = copies dérivées gardées (proposé) |
| ADR-059 | Registre de dispatch des commandes CLI : tables de dispatch opt-in et cœur, découverte par entry points forge_mvc.commands (accepté) |
| ADR-060 | Squelette livré sans backend BDD : requirements.txt ne pin que forge-mvc, la config BDD quitte le squelette pour le backend installé (accepté) |
| ADR-061 | Registre d'opt-ins visible et unifié (optins/registry.py, à la config/bundles.php de Symfony) : une ligne par opt-in utilisé, tous kind + backend, code toujours dans le .venv (accepté) |
| ADR-062 | forge new épingle le projet généré sur la source dont provient le CLI : Git (forge-mvc @ git+…@commit) si installé depuis GitHub, PyPI sinon ; détection via direct_url.json PEP 610 (accepté) |
| ADR-063 | Le squelette livre par défaut l'apparat qualité Forge complet (typage # pyright: strict, ruff, socle pytest + smoke, mkdocs, CI, make check, scaffold ADR, .editorconfig, CHANGELOG) et garde le noyau applicatif minimal ; échappatoire forge new --bare ; révise la portée de « nu » d'ADR-024 (acceptée) |
| ADR-064 | forge db:config amorce les variables d'environnement du backend BDD dans env/example, env/dev et env/prod (write-if-missing, annoncé, sans secret) ; le contrat DatabaseBackend porte env_template ; db:init reste focalisé sur le provisioning (acceptée) |
| ADR-065 | Le squelette de projet passe de cli/skeleton/ à skeleton/ à la racine, en restant un paquet Python (packages += skeleton*, package-data re-clés) : plus découvrable, toujours empaqueté dans le wheel ; imports cli.skeleton → skeleton (acceptée) |
| ADR-066 | Le contrat d'environnement des backends BDD unifie l'adresse serveur : DB_HOST/DB_PORT partagés par les connexions applicative et d'administration, seuls les identifiants restant distingués (DB_APP_*/DB_ADMIN_*) ; DB_APP_HOST/DB_ADMIN_HOST et leurs ports disparaissent (acceptée) |
| ADR-067 | forge db:init génère et affiche par défaut le SQL de provisioning dérivé de env/ (base + comptes scellés à DB_NAME), à exécuter dans une session d'administration ; --run exécute (opt-in) ; plus aucun root serveur exigé dans env/ ; DB_ADMIN_* = propriétaire de la base du projet (acceptée) |
| ADR-068 | Les routes applicatives passent d'un mvc/routes.py monolithique à un package mvc/routes/ : __init__.py branche explicitement chaque register_<controleur>_routes(router), un fichier mvc/routes/<snake>_routes.py par contrôleur ; make:crud/make:auth/make:public-page génèrent le fichier et affichent le branchement ; pas d'auto-découverte (acceptée) |
| ADR-069 | La clé étrangère devient un champ de première classe de l'entité source (type foreign_key + references, normalisé au type de la PK visée, colonne snake_case) ; make:relation l'injecte dans le JSON d'entité ; relations.sql ne pose plus que la contrainte (acceptée) |
| ADR-070 | Le moteur d'entités (génération et modélisation : make:entity, make:relation, normaliseur, validation, build:model, migrations, make:crud, provisioning db:*) est extrait du cœur vers un opt-in forge-mvc-entities qui absorbe forge-mvc-pivot ; le cœur ne garde que la couture runtime core/database ; dépend du contrat Dialect, indépendant des backends (acceptée) |
| ADR-071 | Convention unique de provisioning des opt-ins adossés à la base : la commande <opt-in>:init dépose une migration dans mvc/migrations/, appliquée par forge migration:apply (déjà suivie par 7 opt-ins) ; mvc/models/sql/ + db:apply reste réservé au modèle applicatif (socle auth) ; forge-mvc-sessions-db réaligné (acceptée) |
| ADR-072 | Contrat des commandes CLI des opt-ins : dispatch_optin intercepte -h/--help avant tout effet (F40) et amorce la config projet (env/dev via load_project_config) pour les commandes déclarant config: True dans leur table COMMANDS (F39, sessions:gc), comme le cœur le fait pour migration:apply ; clé additive rétro-compatible (acceptée) |
| ADR-073 | Les vues de l'application (à la main ou make:crud) vivent sous un namespace mvc/views/app/ (réglage APP_VIEWS_NAMESPACE de config.py, défaut "app", "" = plat), aux côtés de public/ ; les 6 dossiers du cadre restent à la racine. render()/loader inchangés (chemins littéraux), changement localisé aux générateurs (retour terrain 018 F41) (acceptée) |
| ADR-074 | Opt-in forge-mvc-fixtures à CLI seule (modèle deploy, ADR-053) pour les données de démo et de test rejouables et cadrées par environnement (fixtures:load/fixtures:purge, SQL visible, prod protégée) ; frontière nette avec la migration de seed qui reste la voie du référentiel permanent (principe 11) ; catégorie operations, franchit les deux filtres d'admission ADR-052 (acceptée) |
| ADR-075 | Extension du contrat Dialect (ADR-054) d'un rendu de littéral SQL render_literal(value) (booléens, dates, chaînes échappées par dialecte) implémenté par les 4 backends ; consolide le sql_default_literal dialecte-naïf du moteur d'entités (principe 11) et corrige un bug de portabilité des DEFAULT ; cantonné à la génération d'artefacts relus (fixtures, DDL), la DML d'exécution reste paramétrée (principe 7) ; débloque fixtures:generate (acceptée) |
| ADR-076 | Génération de fixtures par classes factory possédées par l'utilisateur (mvc/fixtures/factories/, scaffoldées depuis le contrat d'entité) : rows() comme surface de code pédagogique (boucles, conditions, tableaux), self.faker optionnel ; fixtures:make-factory/fixtures:generate émettent du .sql relu via render_literal (ADR-075), chargé par fixtures:load (mécanisme unique, principe 11) ; faker en dépendance directe ; pas de proxy ni d'auto-persistance ORM (principe 3) (acceptée) |
| ADR-077 | Fixtures reliées : make-factory scaffolde les colonnes réelles de l'entité (F45, via column_for_field d'entities), Factory.reference(table, col, valeur) rendu en sous-requête SQL (SELECT Id FROM … WHERE … LIMIT 1) pour lier une fixture à l'Id créé par une autre (F43), et fixtures:load trie topologiquement par dépendances FK depuis relations.json (F44, repli nom de fichier, option --no-fk-checks) ; API additive, dépendance douce à entities ; piste callable différée (acceptée) |
| ADR-078 | Fixtures callable : classe de base Fixture (load(), tables, depends_on) sous-classée dans mvc/fixtures/*.py, découverte et exécutée par fixtures:load dans l'ordre topologique unifié avec les .sql (affichée avant exécution, prod protégée) ; pour ce que le SQL statique ne peut exprimer (import canonique, agrégats calculés) ; fixtures:purge démonte les callable (purge()) ; tranche la piste différée par ADR-077 (acceptée) |
| ADR-079 | Découpeur d'instructions SQL canonique dans le cœur (core.database.sql_script.split_sql_statements), conscient des chaînes '...' ('') et des commentaires -- / /* */ : un ; dans une chaîne ou un commentaire n'est pas un séparateur. Remplace les deux implémentations divergentes de migration:apply/db:apply et fixtures:load (principe 11) ; fiabilise les retours terrain 012 (apostrophe) et 021 (commentaire) (acceptée) |
| ADR-080 | Validation du sujet authentifié, explicite et opt-in (sans état global ni changement de is_authenticated) : AuthMiddleware(..., user_loader=None) valide l'existence du sujet via current_user et ferme la session orpheline (logout + purge cookie) avant de rediriger (F54) ; le provider Jinja auth/RBAC est la source autoritaire de is_authenticated dans le rendu, loader-aware quand l'app câble un loader (F55). Retour terrain 021 (acceptée) |
| ADR-081 | Horodatages created_at/updated_at (options.timestamps) gérés par le framework : marqueur managed posé par le normaliseur, exclus du formulaire (classe Form et form.html), posés par le modèle généré via datetime.now(timezone.utc) (les deux à l'INSERT, updated_at seul à l'UPDATE, created_at stable), DDL sans DEFAULT (Python seule autorité, cohérent sessions-db), exclus de toutes les vues générées (formulaire, liste, détail, CSV, tri) car métadonnées système. Retour terrain 021 F56 (acceptée) |
| ADR-082 | forge new et forge agents:init posent un second ADR d'amorçage dans le projet, docs/adr/002-style-documentation.md, qui acte les règles de rédaction de la doc (français, une phrase par ligne, pas de tiret cadratin, ponctuation française, liens vérifiés au build strict) ; write-if-new comme l'ADR-001, propriété du projet, numérotation suivante à 003. Extension d'ADR-047 (acceptée) |
| ADR-083 | options.soft_delete: true produit une suppression logique réelle : deleted_at marqué managed = "soft_delete", exclu des vues et de l'INSERT/UPDATE ; la suppression (simple et groupée) devient UPDATE deleted_at = now() au lieu de DELETE ; toutes les lectures (liste, count, pagination, export, par id, par slug) filtrent deleted_at IS NULL ; DDL sans DEFAULT. Corrige un contrat rompu (l'option promettait la suppression logique et hard-deletait). Version minimale (pas de restore/corbeille) (acceptée) |
| ADR-084 | Deux niveaux de support opposables des backends BDD pour la série 1.x : niveau plein (MariaDB, SQLite) où tout chemin de génération SQL passe par le dialecte et est couvert en intégration réelle ; niveau Alpha (PostgreSQL, MSSQL) publié mais sans garantie de bout en bout, où tout chemin non garanti refuse explicitement (règle B) au lieu de produire du SQL d'un autre dialecte. Critères de promotion listés (provisioning db:init, identité d'insertion, CI réelle, chemins dialectaux, doc). Tickets dérivés : AUTH-INIT-DIALECT-DDL-001, ENTITIES-RELATIONS-DIALECT-001 (acceptée) |
| ADR-085 | Câblage de routes canonique : une seule façon, générer un fichier mvc/routes/<x>_routes.py (ADR-068) puis afficher le branchement, jamais d'injection dans mvc/routes/__init__.py. make:public-* et opt-in:enable cessent d'injecter (par AST/ancre) et passent au mode fichier + affichage, comme make:crud. Révise l'ADR-030 (l'injection n'est plus la voie retenue) ; aligne le geste sur les principes 9 et 11 (pas d'écriture invisible, une seule façon). L'utilisateur colle deux lignes ; routes/__init__.py reste sous son contrôle (acceptée) |
| ADR-086 | Élimination de la représentation legacy interne du moteur d'entités : le dict pivot format_version:1 (clés sql_type/python_type/column/primary_key/auto_increment) est un artefact de transition, pas un contrat (ADR-012). Épopée incrémentale : extraire le mapping type vers SQL/Python dans un service field_resolver (source unique, principe 11), migrer les 6 sous-systèmes du plus petit au plus grand, puis supprimer le pont et l'essentiel de validation.py. Refactor purement interne, sans changement de format d'entrée ni bénéfice externe ; gain = une seule représentation, moins de machinerie cachée (principe 3). Prolonge ADR-012 et ADR-070 (acceptée) |
| ADR-087 | Style rédactionnel canonique de la documentation Forge, source unique des sept règles (français, une phrase par ligne, pas de tiret cadratin, ponctuation française, au plus un deux-points par phrase, liens vérifiés au build strict, anglicismes évités). CLAUDE.md §2.1 y renvoie et cesse de les énoncer en double ; le gabarit posé dans les projets par l'ADR-082 en dérive. Deux règles sont figées par un garde-fou à cliquet ; les autres relèvent du jugement. L'ADR-003 n'est pas révisé : l'API publique reste en anglais (acceptée) |
| ADR-088 | Contrat unique des réponses d'API JSON. Forge portait une convention déclarée (api_success/api_error en enveloppe {"success", "error":{"code","message"}}, @require_api_token, mvc/api_routes.py), documentée sur 411 lignes et câblée dans le cœur, que ses trois opt-ins exposant du JSON n'ont jamais suivie : iot, video et audio rendent une forme plate {"error": "<code>"} et s'authentifient par core/http/bearer.py. Mesuré : api_error n'a que 3 sites d'appel, tous dans core/security/api_auth.py, et api_success aucun. La forme pratiquée devient le contrat ; l'enveloppe, api_auth.py (seconde implémentation Bearer, à la posture de sécurité plus faible) et sa doc sont retirés. Le succès rend la ressource, le code HTTP portant l'information que {"success": true} redoublait. Révise l'ADR-052 sur ce point (acceptée) |
| ADR-089 | Séparer l'identité du contact dans l'authentification. La table users du cœur n'a qu'une colonne d'identité, email, et rien ne vérifie qu'elle contienne une adresse : core/auth/user.py n'exige qu'une chaîne non vide, et la première application Forge y inscrit 2TNE1-01. Le vocabulaire a produit du comportement, deux fois : une fonction _normalize_email abaissait la casse, fermant la connexion sur SQLite (AUTH-CASE-ASYMMETRY-001), et la CLI refuse tout argument sans @, donc rejette la saisie au lieu de chercher le compte. Décision : login porte l'identité, unique, sans contrainte de forme, casse conservée ; email devient le contact, facultatif, non unique, normalisé, et email_verified_at le suit. last_login_at reste sur la ligne d'utilisateur, décidé et non subi. Aucune migration, la convention pré-1.0 l'autorisant et la seule application existante pouvant reconstruire sa base : c'est précisément pour éviter la migration en trois temps que la décision est prise avant le tag 1.0.0. Rupture d'API publique assumée, imputable au défaut et non à sa correction |
| ADR-090 | Empreinte de contrat dans les fichiers engendrés. Corriger un générateur ne corrige aucune application déjà engendrée, et son auteur ne l'apprend pas : le silence est le défaut. Chaque fichier engendré porte le numéro de contrat de son générateur, jamais un condensat du contenu, un fichier engendré étant fait pour être édité, et jamais la version du framework, qui ferait crier à chaque montée. Un registre par générateur dit ce qui a changé à chaque montée, sans quoi l'avertissement ne se traduit pas en geste. Contrat identique : silence. Empreinte absente : le contrôle dit qu'il ne sait pas, il n'accuse pas. forge doctor gagne un seizième contrôle, et ne répare toujours rien (principe 9) |
| ADR-091 | Le cœur émet les événements d'authentification. Un retour terrain relève une table auth_audit_log vide après des semaines d'usage et propose que le cœur l'alimente : écarté, l'ADR-008 ayant rangé la persistance du côté applicatif et qualifié cette table de latente. Le défaut réel est ailleurs et l'ADR-008 ne le couvre pas : le générateur n'appelle jamais safe_log_auth_event, si bien qu'aucun événement n'est émis, pas même vers le logger, et qu'une application configurant un handler comme l'ADR-008 le demande ne recevrait rien. Le cœur émet donc les trois événements, parce que authenticate_user est le seul à savoir POURQUOI une connexion échoue, là où le contrôleur ne voit qu'un None. Le mot de passe n'entre jamais dans un événement, l'émission ne peut jamais faire échouer une connexion, et un test COMPTE les émissions pour qu'aucune ne soit double. last_login_at, créée par le cœur et écrite par personne, est retirée : le cœur n'écrit nulle part ailleurs dans users, et le journal répond déjà mieux à la même question. Complète l'ADR-008 sans le réviser |
| ADR-092 | Le chemin WSGI refuse de servir une application désarmée. Le squelette prescrit le câblage des middlewares dans app.py, que build_application() ne lit jamais : la production servait donc une application privée de toutes ses gardes sauf la première, sans erreur, sans trace, avec des pages qui répondent 200. Mesuré en production le 2026-08-24. create_configured_wsgi_app() refuse désormais de construire quand app.py câble ce qu'il ne voit pas : une erreur au démarrage, jamais un avertissement, un avertissement dans un journal ayant déjà été essayé sans rien empêcher. La détection est statique, par ast et jamais par recherche de texte, le squelette livrant un exemple de câblage en commentaire qu'un grep prendrait pour une déclaration. Ne répare pas la cause : déplacer le câblage dans une source que les deux points d'entrée lisent est la cible, et fera son propre ADR. Ce refus la précède parce que les projets existants, eux, ne migrent pas |
| ADR-093 | Le câblage de l'application vit dans une source unique. L'ADR-092 posait un refus en attendant celle ci ; la cause restait entière, deux séquences de construction pour une seule application. Le squelette livre bootstrap.py, lu par les DEUX points d'entrée, et app.py cesse de construire : c'est ce second point qui retire la cause, tant que deux fichiers construisent ils peuvent diverger. Un module absent n'est pas une erreur, un module CASSÉ fait échouer le démarrage, la distinction étant tout le sujet : attraper son ImportError ferait retomber un opt-in désinstallé sur une application désarmée. Le câblage par défaut devient explicite au lieu d'être un défaut d'Application que personne ne lit. Coût assumé : app.py perd sa séquence d'initialisation, et sa valeur pédagogique avec |
| ADR-094 | Un registre de fichiers dans forge-mvc-files. Le paquet ecrivait des fichiers sans garder trace de ce qu'il avait ecrit, l'ADR-020 ayant exclu tout etat de son perimetre. Une consequence n'avait pas ete mesuree : sans registre, ni quota, ni purge d'orphelins, ni nom d'origine survivant au mode UUID, ni analyse antivirus branchable. L'etat existait pourtant deja dans la table media de forge-mvc-images, ou rien n'est propre a l'image : un projet stockant des PDF aurait du tirer Pillow pour une table. Le registre porte ce que le STOCKAGE sait, chemin, nom d'origine, type MIME, taille, proprietaire libre, jamais une notion metier ; role et position restent a media. L'enregistrement est explicite, ecrire un fichier n'enregistre rien de soi meme, et le paquet reste utilisable sans base. Amende l'ADR-020 sur ce seul point, tout le reste de son hors perimetre tenant. Doublon partiel avec media assume le temps de la serie 1.x, la convergence appartenant a un ticket post-1.0 |
| ADR-095 | Heritage entre roles RBAC. Le contrat associait un role a une liste plate : un projet a trois roles recopiait la liste du lecteur dans l'editeur, puis les deux dans l'admin. Trois copies de la meme regle, qui divergent au premier ajout, et l'administrateur se retrouve avec MOINS de droits qu'un editeur, en silence. role_inherits declare l'heritage, transitif, et la cle reste facultative : un contrat qui ne la porte pas se resout comme avant. L'heritage est DECLARE, jamais deduit d'un nom de role, une deduction fausse sur un controle d'acces ne se reparant pas apres coup. Un cycle est refuse et NOMME, s'arreter arbitrairement donnant des permissions dependant de l'ordre de lecture du JSON ; un role herite inconnu l'est aussi, une faute de frappe n'accordant rien du tout. Une hierarchie fautive n'accorde RIEN plutot que les droits directs : un controle qui se degrade en silence est pire qu'un controle qui refuse. Profondeur bornee a dix, au dela une revue de securite ne peut plus suivre la chaine. Amende l'ADR-014 |