Position
Le code était auparavant considéré comme source de vérité car sa construction était lente car faite par des humains et cette lenteur, forçait, par sa durée, à clarifier l’intention initiale. Désormais avec les agents IA l’activité rapide est la génération du code, l’activité lente est de décider ce que l’on veut, de s’accorder sur cette intention, et le challenge devient de prouver que le code qui s’exécute remplit l’intention initiale. Le code est devenu un produit dérivé de la spécification, le vrai challenge devient d’intégrer les mécanismes de vérification dès la spécification de la structure et du comportement attendu du système. La source ultime de vérité était et reste toujours le comportement tangible et réel du système avec le code qui s’exécute en production sur des données concrètes.
Deux types de vérifications sont possibles pour mettre en relation intention initiale et comportement réel du code : vérification computationnelle et vérification inférentielle.
La spécification d’un système logiciel est un ensemble vaste nécessitant de séparer différentes responsabilités (concerns). Les mécanismes de vérification computationnlle qui peuvent être présent dès la spécification. Cela nécessite des structurer fortement la spécification, notamment du domaine.
Cette montée en abstraction, du code vers la spécification, n’est viable que si la spécification est structurée pour être manipulée à la fois par un agent IA et par un humain. Des milliers de lignes de contenu textuel sans structures ne conviennent ni à l’humain, ni à l’agent.
Notre proposition est de structurer cette spécification au format texte suivant un métamodèle explicite sous la forme d’un Domain-Specific Language (DSL).
Le code devient un produit dérivé de la spécification. La spécification devient la source de vérité, le code devient un produit dérivé sur lequel vont porter des vérifications de validation du résultat vis à vis de l’intention.
Trois couches pour bien séparer les niveaux d’abstraction
La spécification est structurée en trois couches : un Product Requirements Document, un ensemble de System Requirements, et un Domain Model. Chacune répond à une question distincte, utilise des concepts spécifiques, s’adresse à une audience déterminée, et reste agnostique des décisions prises dans la couche en aval.
Le PRD (Product Requirement Document) porte le pourquoi et pour qui. Il décrit les personas, les jobs-to-be-done, les features, les success metrics, les hypothèses, les risques. Il parle le langage du produit et se lit comme un document destiné aux parties prenantes. Il ne dit pas comment le système se comportera — il dit pourquoi le système doit exister, pour qui, et les critères d’évaluation de son succès.
Les System Requirements portent le ce que le système doit. Ils traduisent les intentions issues du PRD en exigences testables, écrites avec la syntaxe EARS — « [Tant que …,] [Quand|Si|Là où …,] le système doit réponse. » Chaque requirement est court, scopé à un seul bounded context, porte un identifiant, une justification, une priorité MoSCoW (Must, Should, Could, Won’t), et une ligne source qui pointe vers la feature du PRD qui en est à l’origine.
Le Domain Model porte les concepts qui seront mis en oeuvre pour répondre à l’exigence. Les concepts du DDD sont présents mais avec un niveau de formalisation élevé : bounded contexts, agrégats, entités, value types, commandes, événements, invariants, agreements, réactions. Le Domain Model est indépendant de la stack technique — le même modèle de domaine peut être réalisé avec différents styles architecturaux ou stack technique car ce sont des préoccupations d’implémentation. Il adhère néanmoins à un modèle structurel précis : on y prend des décisions de conception que la couche System Requirement en amont ne doit pas prendre.
Ces trois couches permettent de bien séparer les responsabilités et de founir les abstractions adéquate pour que chacune remplisse son rôle. Un document unique qui contient simultanément personas, exigences EARS et entités de domaine est amené à échouer : il est trop vague pour les ingénieurs parce que le contenu lié au produit dilue la précision et il est trop concret pour le produit parce que les détails d’implémentation obscurcissent l’intention initiale.
Dans notre exemple RideNow. La décision produit « le passager peut annuler sans frais avant qu’un chauffeur ne soit assigné » existe simultanément sous forme de feature dans un fichier
.prd, sous forme d’obligation EARS dans un fichier.sysreq, et sous forme de commande, événement et invariant dans un fichier.domain. Trois fichiers que n’importe quel agent peut parcourir, n’importe quel humain peut lire.
Dans RideNow. La feature produit « Cancellations with fair fees and penalties » est restée un paragraphe dans le PRD. Elle engendre huit requirements EARS dans le contexte Ride Management (
REQ-RIDE-060àREQ-RIDE-067), trois autres dans Payment (libération du pre-auth, calcul du fee post-assignation) et Driver Management (suspension chauffeur sur annulations excessives). Ces obligations n’allaient jamais tenir dans le même paragraphe que la feature. Le domaine, lui, les réalise par une commandeCancelRequestByRider, un événementRideRequestCancelledByRider, un invariantnoFeeBeforeAssignmentsur l’agrégatRideRequest, et une réaction qui déclenche le refund côté Payment. La même intention, à trois granularités différentes, dans trois vocabulaires, sans aucune perte d’information.
Le métamodèle du domaine
Un modèle de domaine n’est exhaustif que s’il a trois dimensions : la structure, le comportement et les propriétés. Les deux premières sont familières en DDD ; la troisième est ce qui rend le modèle vérifiable par construction, et c’est dans cette dimension que se loge la majeure partie de la valeur ajoutée d’un métamodèle conçu pour être lu et écrit par des agents IA.
La structure décrit ce qui existe : les entités avec leur identité, les value types avec leurs contraintes, les agrégats avec leur racine et leurs invariants transactionnels, les bounded contexts avec leurs frontières et leurs context-map patterns. C’est la dimension statique du modèle.
Le comportement décrit ce qui se passe en réaction à un stimuli (externe, interne ou temporel) :
- les opérations sur les entités,
- les machines à états qui contraignent les transitions de cycle de vie,
- les services de domaine qui orchestrent des opérations dépassant le périmètre d’un seul agrégat.
- les commandes (intentions explicites de modification),
- les requêtes (lecture d’état courant),
- les événements internes (faits passés à l’intérieur d’un contexte),
- les événements externes (faits passés observés depuis un autre contexte),
- les événements temporels (faits passés produits par l’écoulement du temps),
- les réactions (règles automatisées qui déclenchent des effets en réponse à des événements). C’est la dimension dynamique du modèle.
Les propriétés décrivent ce qui doit être évaluable logiquement : les invariants (prédicats d’état synchrones au sein d’un agrégat), les agreements (prédicats cross-agrégats avec fenêtre d’incohérence et réconciliation), les préconditions et postconditions des opérations (le contrat de chaque opération), les contraintes de cycle de vie (gardes de transitions, invariants scopés à un état particulier), les chaînes d’escalade (séquences ordonnées de compensations en cas d’échec). C’est la dimension contractuelle du modèle, celle qui répond à la question « qu’est-ce qui doit être vrai à chaque instant ? ».
Ces détails, qui étaient laissés implicites à l’ére pré-IA, peuvent maintenant être intégré dès le départ, et être ensuite utilisé en aval par les agents IA pour générer puis exéctuer le code de vérification. Cela permet de disposer d’une vérifiabilité complétement intégrée dès le départ. Ces propriétés sont des citoyens de première classe du métamodèle, avec leur prédicat, leur scope, et leur stratégie d’enforcement.
Dans RideNow. Sur l’agrégat
RideRequestdu contexte Ride Management, les trois dimensions co-existent : la structure déclare l’entitéRideRequest, ses champs, son agrégat racine ; le comportement déclare la commandeCancelRequestByRideret la machine à états avec les transitionsunassigned → cancelledetassigned → cancelledAfterAssignment; ainsi que les événementsRideRequestCancelledByRideretRideRequestCancelledAfterAssignment, enfin la réaction qui déclenche le refund côté Payment ; les propriétés déclarent l’invariantnoFeeBeforeAssignment(avec stratégie d’enforcement par rejet), la préconditioncancellationAllowedFromCurrentStatesur la commande, et la postcondition qui contraint le fee résultant en fonction de l’état antérieur.
Vérifiabilité par construction
Chaque propriété déclarée dans le modèle du domaine est un prédicat vérifiable par du code traditionnel sous réserve de disposer du générateur de données synthétiques associés. Chaque Value Type dispose d’un générateur de valeurs, par exemple une value PhoneNumber { countryCode: Integer, number: #"[0-9]*"} mais également des value type composite plus complexe, composé eux-mêmes de value plus primitive. Par exemple le value type FareEstimate {...} est composé des Values Type: Amount, Distance, Duration, SurgeMultiplie, etc. chacun disposant de son générateur, le générateur de FareEstimate prend en compte la cohérence de chacun des attributs avec des algorithmes de génération procédurale (par exemple : on définit la distance, puis la durée en est déduite, ensuite le montant en est déduit, cela afin de conserver la cohérence des attributs).
Par vérifiabilité par construction, nous entendons : chaque propriété déclarée dans le modèle (invariant, agreement, précondition, postcondition, contrainte d’état) porte un prédicat et peut donc être vérifiée avec du code déterministe qui utilise les générateurs de données synthétiques. Cette approche peut être qualifiée de property-based testing dirigé par le domaine.
Autre exemple : un invariant « le total de la commande est égale à la somme des lignes » déclaré sur l’agrégat Order est exécutable par un générateur qui produit des séquences arbitraires de commandes avec des lignes de commandes (AddLine, RemoveLine, UpdateLine), et vérifie le prédicat de l’invariant après chaque action.
Pour reprendre le terme consacré à ce type de guide dans l’industrie logiciel : un computational guide est une vérification qui s’exécute de manière déterministe avec du code traditionnel : un test unitaire, une assertion, un check statique, un test PBT. La machine sait dire oui ou non sans ambiguïté. Dans notre contexte, chaque propriété déclarée dans le modèle est candidate à une vérification computationnelle : la précondition, la postcondition, l’invariant, le prédicat d’agreement, la garde de transition. Toutes ces affirmations peuvent être exécutées contre l’état observable du système, en test ou en runtime, sans intervention humaine.
Mais toute vérification n’est pas déterministe. Certaines vérification demandent un agent qui agit comme reviewer — par exemple, juger qu’un nom de feature dans le PRD respecte l’ubiquitous language utilisé dans le domaine. Ces vérifications sont des inferential guides : un LLM lit les fragments de spécifications ou de code concernés, applique les heuristiques et retourne un diagnostic argumenté.
La combinaison des deux types de guides couvre l’espace de vérification : déterministe pour ce qui est exprimable en prédicat, LLM pour ce qui demande un jugement contextuel. C’est cette combinaison qui rend l’agent IA utile dans le rôle de vérificateur.
Les mêmes déclarations alimentent l’observabilité runtime. Les invariants et les agreements peuvent être évalués en continu contre des flux d’événements en production ; la dérive entre le modèle et la production devient observable.
Dans RideNow. L’agreement « weeklyPayoutBalancesCaptures » déclaré dans le contexte Payment énonce que la somme des captures pour une semaine donnée doit égaler la somme des paiements aux chauffeurs pour cette même semaine, avec une fenêtre d’incohérence explicite de six heures après dimanche 23h59 UTC. Eventually consistent est une affirmation auditable, avec un prédicat (
sum(captures) == sum(payouts)), un scope (la semaine), et une fenêtre temporelle nommée. La réconciliation qui maintient l’agreement est déclarée à côté ; si elle échoue, la chaîne d’escalade prend le relais, avec retry, compensation alternative, alerte, suspension, intervention manuelle. La chaîne doit terminer ; le métamodèle l’impose. Aucune des affirmations précédentes n’est de la prose — toutes sont des prédicats que la machine peut exécuter.
Traçabilité et Ubiquitous Language
Les trois couches sont reliées entre elles par des relations entre les concepts des différents niveaux d’abstraction. La chaîne de traçabilité se matérialise par trois marqueurs simples, présents dans les fichiers de spécification :
- Chaque ensemble de requirements déclare
prd-source "ride-now.prd"— un pointeur résolvable vers le PRD en amont. - Chaque requirement EARS porte une ligne
source "Feature: <nom> — AC: <texte>"— une référence qui peut être recherchée vers le critère d’acceptation exact du PRD qu’il formalise. - Chaque élément de domaine qui réalise un requirement déclare
satisfies [REQ-…]— un lien inverse, de l’implémentation vers l’exigence.
Cette chaîne fait partie du contenu des fichiers, et un agent ou humain disposant d’un accès au système de fichiers peut la traverser aisément. Deux capacités sont permises par cette chaine de provenance :
- La couverture : quels requirements
mustn’ont aucun élément de domaine qui déclaresatisfies? — ce sont des exigences non couvertes, et la requête est un simplegrepsur les fichiers de domaine. - L’analyse d’impact : si une feature du PRD change, quels requirements la nomment dans leur
source, et quels éléments de domaine satisfont ces requirements ?.
La vérification de l’homogénéité de l’ubiquitous language est également une conséquence de cette structuration. Cet ubiquitous language est traditionnellement maintenu par discipline humaine et par un travail collectif. Avec un métamodèle structuré et trois couches qui se partagent un vocabulaire commun, cette discipline devient automatisable : un agent peut parcourir les fichiers .prd, .sysreq et .domain, lister les noms qui n’apparaissent que dans une seule couche, et alerter quand le PRD parle de « passager » alors que le domaine ne connaît qu’un « Customer ». L’ubiquitous language devient un critère vérifiable.
La traçabilité et l’ubiquituous language produisent un autre bénéfice, plus structurel : le découplage des cadences de changement, et la traçabilité des modifications par niveau. Une couche supérieure, par définition, reste agnostique des modifications qui interviennent dans la couche inférieure — une décision de conception dans le domaine ne réécrit pas le PRD. À l’inverse, un changement dans une couche supérieure remonte explicitement à la chaîne et identifie les éléments aval à reconsidérer. Le couplage est unidirectionnel et auditable, ce qui est exactement la propriété qu’une équipe veut pour s’autoriser à faire évoluer chaque couche à son propre rythme.
Dans RideNow. Quand nous avons touché à la politique d’annulation en cours de construction, nous avons parcouru la chaîne en quelques secondes. Annulations avec frais et pénalités équitables → huit requirements dans Ride Management → environ quinze éléments de domaine répartis sur
RideRequest,Ride, et les réactions d’annulation → obligations cross-context sur Payment (refund) et Driver Management (seuil de suspension). Aucun outil à installer — les références étaient dans les fichiers. Une variation du langage ubiquitaire s’est révélée à la même occasion : le PRD parlait de « cancellation fee », le domaine de « cancellationCharge ». Deux noms pour une seule chose. Un alerte qu’un agent aurait pu lever pendant la rédaction.
De l’intention vérifiable au résultat vérifié
L’agent IA devient utile dans les deux directions de la chaîne : production et vérification. Vers l’aval, il produit l’artefact à partir de l’intention structurée ; vers l’amont, il vérifie l’artefact contre l’intention.
Vers l’aval (production), l’agent lit la spécification, identifie les requirements à satisfaire, génère le code qui réalise les éléments de domaine concernés, déclare les satisfies correspondants, respecte l ubiquitous language, et honore les invariants. La structure lui donne un cadre dans lequel il ne peut pas dériver sans laisser de trace : chaque artefact produit s’inscrit dans la chaîne, et chaque omission devient détectable par les requêtes de couverture.
Vers l’amont (vérification), l’agent lit l’artefact produit (qu’il l’ait produit lui-même ou qu’un autre agent l’ait produit), traverse les listes satisfies pour vérifier la couverture, exécute les computational guides (PBT depuis les préconditions et postconditions, validation des invariants contre des séquences générées, vérification des agreements contre des traces d’événements), applique les inferential guides (jugement sur le respect du langage ubiquitaire, clarté des descriptions, cohérence des choix de modélisation). La structure lui donne ce qu’il faut pour produire un verdict sur l’adéquation du résultat avec l’intention.
Cette double directionnalité est la conséquence pratique de tout ce qui précède. La séparation en trois couches donne à l’agent un cadre de travail clair avec les bonnes séparations et abstractions pour chaque type de tâche. Les métamodèles de chaque couche (PRD, System Requirements, Domain Model) lui donne des cibles précises pour produire et vérifier. La chaîne de traçabilité lui donne une carte d’impact. L’ubiquitous language lui donne les noms à respecter et à auditer.
L’IA amplifie dans la direction qu’on lui donne. Fournissez-lui une intention structurée — avec une bonne séparation des responsabilités, tracée de bout en bout, vérifiable par construction, lisible dans un seul vocabulaire partagé — et l’amplification compose : chaque production d’artefact de code et in-fine de structure et de comportement de notre système logiciel est vérifiable.