


Développer un framework qui offre une seule API dans plusieurs langages de programmation
Pour un framework qui génère des paquets pour plusieurs langages à partir d’un seul code. Fixe les règles du protocole JSON entre processus, des types exportables, de la génération de code et des tests qui vérifient le même comportement dans chaque langage.
Pour utiliser ces règles d'IA
## Garder le même comportement et la rétrocompatibilité dans chaque langage
Un framework multilangage doit privilégier un comportement qui reste compréhensible et équivalent dans chaque langage pris en charge, plutôt que toutes les tournures idiomatiques disponibles dans son langage d’implémentation.
### Invariants fondamentaux
- Préserver la rétrocompatibilité du format de l’assembly, du protocole d’exécution et des API publiques.
- Exiger un comportement observable équivalent dans toutes les cibles de langage prises en charge.
- Générer du code conforme aux conventions usuelles de nommage, de création de paquets et de typage de chaque langage cible.
- N’accepter que les fonctionnalités du langage source pouvant être représentées fidèlement dans toutes les cibles.
- Privilégier l’exactitude et l’interopérabilité plutôt que les raccourcis d’optimisation propres à une cible.
### Frontières des paquets
- Séparer le noyau du protocole, les environnements d’exécution hôtes, les définitions de schéma, les générateurs de code, les outils de réflexion, les contrôles de compatibilité et les fixtures de test.
- Maintenir les utilitaires de build et la production des rapports de conformité hors des paquets d’exécution.
- Traiter chaque environnement d’exécution de langage et chaque intégration facultative comme un paquet géré indépendamment.
- Placer les fixtures partagées à un endroit où chaque compilateur, générateur et test d’exécution peut exploiter les mêmes cas de comportement.
- Relier les composants maintenus dans des dépôts distincts au moyen de dépendances explicites et versionnées.
## Exécuter le code dans un processus noyau qui échange en JSON avec chaque langage
Utiliser un modèle d’exécution à deux composantes lorsque les langages hôtes ne peuvent pas exécuter directement le langage d’implémentation. Le processus hôte communique avec un processus noyau par des messages JSON transmis sur l’entrée standard et la sortie standard, tandis que toute exécution du langage d’implémentation reste confinée au noyau.
### Frontière des processus
- Attribuer à chaque requête un discriminant d’opération explicite.
- Affecter des opérations de protocole distinctes au chargement des assemblies, au cycle de vie des objets, aux appels de méthodes, à l’accès aux propriétés, aux tâches asynchrones, aux statistiques et aux callbacks.
- N’utiliser un processus d’encapsulation supplémentaire que lorsqu’un environnement d’exécution hôte ne peut pas préserver les descripteurs de fichiers requis par le processus central.
- Envoyer les diagnostics sur le flux d’erreur afin qu’ils ne puissent pas altérer le flux du protocole.
### Séquence de callback
Lorsque le code du langage d’implémentation atteint une méthode ou une propriété redéfinie par le langage hôte :
1. Le noyau envoie une requête de callback au lieu de la réponse initiale.
2. L’hôte exécute la redéfinition et renvoie un message d’achèvement contenant soit le résultat, soit l’échec.
3. Le noyau reprend l’opération initiale et renvoie sa réponse effective.
### Évolution du protocole
- Traiter les noms de requêtes, la signification des champs, l’ordre des réponses et l’enchaînement des callbacks comme des contrats de compatibilité.
- Mettre à jour de concert tous les environnements d’exécution hôtes lorsque le protocole change.
- Ajouter des tests de compatibilité avant de modifier la forme d’un message ou une règle de cycle de vie.
## Décider si les valeurs passent entre langages par copie ou par référence
Définir une représentation filaire pour chaque catégorie de valeur portable et rendre explicite la sémantique de propriété. Une valeur transmise par copie ne doit pas être confondue avec un objet dont le noyau conserve la propriété.
### Représentation filaire
- Transmettre les booléens, les nombres et les chaînes par valeur au moyen de leur représentation JSON.
- Transmettre les listes et les tables associatives par valeur, avec une enveloppe explicitement balisée si nécessaire pour les distinguer des autres objets.
- Encoder les énumérations à l’aide d’un identifiant de membre entièrement qualifié.
- Encoder les dates au moyen d’une représentation horodatée, balisée et sans ambiguïté.
- Encoder les structures contenant uniquement des données avec leur type entièrement qualifié et les données de leurs champs.
- Encoder les instances de classe par référence au moyen d’un identifiant d’objet stable.
### Règles de frontière
- Préserver entre les langages les chaînes UTF-8, le comportement des nombres à virgule flottante, la possibilité d’accepter une valeur nulle et la sémantique de copie des collections.
- Maintenir stables les clés filaires balisées dès lors que des consommateurs en dépendent.
- Libérer explicitement les références d’objet lorsque le protocole assure la gestion du cycle de vie distant.
- Tester les allers-retours à travers chaque environnement d’exécution hôte, et pas seulement du côté du langage d’implémentation.
## N’exporter que des types que chaque langage peut représenter
Appliquer les restrictions de portabilité aux déclarations exportées depuis le point d’entrée principal du module, tout en autorisant le code d’implémentation interne à utiliser des fonctionnalités plus étendues du langage.
### Surface d’API portable
- Prendre en charge les primitives, listes, tables associatives, énumérations, dates, classes, interfaces, structures de données, promesses et valeurs opaques uniquement lorsque chaque cible dispose d’une représentation sûre.
- Distinguer les interfaces comportementales des structures contenant uniquement des données au moyen d’une règle explicite de nommage ou de schéma.
- Exiger que les structures contenant uniquement des données ne comportent que des propriétés en lecture seule et aucune méthode.
- N’autoriser les promesses que dans les positions que chaque cible peut exposer de manière cohérente, comme les types de retour de méthode.
- Considérer les valeurs opaques comme implicitement nullables lorsque les cibles à typage statique ne peuvent pas retrouver un type plus précis.
- Transmettre les classes par référence et les structures assimilables à des valeurs par copie.
### Restrictions
- Rejeter les constructions exportées non prises en charge, telles que les tuples, les types inhabités, les entiers de précision arbitraire, les symboles et les surcharges de méthodes.
- Exiger des méthodes redéfinies qu’elles conservent des signatures exactement compatibles ; ne pas s’appuyer sur des types de retour covariants ni sur des paramètres contravariants.
- Éviter les unions lorsqu’elles se réduisent à un objet non typé dans les cibles à typage statique.
- Ne pas permettre aux interfaces comportementales et aux structures contenant uniquement des données d’hériter les unes des autres.
## Générer le paquet de chaque langage depuis le même assembly compilé
Générer chaque cible depuis le même assembly compilé afin que les paquets cibles partagent une source unique de vérité pour l’API.
### Pipeline de génération
1. Lire l’assembly depuis le paquet compilé du langage d’implémentation.
2. Générer du code source idiomatique pour le langage cible sélectionné.
3. Compiler le code source généré avec la chaîne d’outils de ce langage.
4. Conditionner le résultat avec le gestionnaire de paquets standard de l’écosystème cible.
### Conception des cibles
- Doter chaque cible de langage de son propre adaptateur, construit sur une abstraction de parcours commune.
- Utiliser une cible mature comme référence lors de l’ajout d’une nouvelle cible, tout en préservant les tournures idiomatiques du nouveau langage.
- N’inclure l’artefact d’implémentation d’origine que si l’architecture d’exécution l’exige.
- Inclure les métadonnées de l’assembly nécessaires aux outils de réflexion ou de génération en aval.
- Mutualiser entre les cibles les utilitaires de mise en forme, d’indentation, de création de fichiers et de blocs de code.
- Mettre à jour les snapshots du code généré chaque fois qu’une cible change.
## Compiler et tester dans le monorepo avec les versions d’outils fixées
Exécuter l’installation, les builds, les tests, le lint, les mises à jour de snapshots et la production des rapports de conformité au moyen de la chaîne d’outils de workspace dont les versions sont fixées par le dépôt.
### Environnement et périmètre
- Partir de la racine du dépôt pour une initialisation complète et une validation à l’échelle du dépôt.
- Exécuter les scripts limités à un paquet depuis le répertoire de ce paquet pour les itérations ciblées.
- Respecter les versions déclarées du gestionnaire de paquets et de l’outil d’orchestration.
- Limiter la concurrence des builds lorsque les dépendances entre paquets rendent leur exécution parallèle dangereuse.
### Workflow de modification
- Identifier le paquet responsable de la modification avant toute édition ou exécution de commande.
- Commencer par builder et tester le paquet ciblé, puis effectuer une validation plus large des contrats partagés.
- Modifier les sources plutôt que les bundles générés, puis rebuilder au moyen du pipeline prévu.
- Conserver en local les remplacements temporaires de configuration d’exécution et les réglages du débogueur.
- Ne mettre à jour les snapshots qu’après avoir confirmé que la modification générée est intentionnelle.
## Tester avec des snapshots et une suite commune que chaque langage doit passer
Combiner des tests rapides au niveau des paquets avec des contrôles de régression sur les sorties générées et une suite de conformité interlangage.
### Niveaux de validation
- Utiliser des tests unitaires pour la logique des paquets et les cas limites du protocole.
- Utiliser des snapshots pour détecter les modifications involontaires du code source généré.
- Utiliser des fixtures partagées couvrant les classes, interfaces, structures de données, énumérations, collections, appels asynchrones et redéfinitions.
- Utiliser des tests de conformité pour vérifier l’équivalence du comportement dans chaque liaison de langage.
### Fixtures et rapports
- Compiler les fixtures partagées avec le même compilateur que celui utilisé par les véritables consommateurs.
- Actualiser chaque snapshot affecté de générateur, d’outil de réflexion et d’environnement d’exécution lorsque les fixtures changent.
- Définir chaque cas de conformité une seule fois, puis exiger de chaque liaison de langage qu’elle l’implémente ou le classe explicitement.
- Régénérer et committer le rapport de conformité chaque fois que les cas ou leurs résultats changent.
- Faire échouer la validation lorsqu’une liaison ajoute un cas absent de la définition de la suite ou lorsque le rapport n’est plus à jour.
## Publier les modifications avec snapshots et rapports à jour, sans push forcé
Traiter le code généré, les formats du protocole, les bibliothèques d’exécution et les rapports comme des périmètres de livraison coordonnés.
### Contraintes non négociables
- Ne pas assouplir les restrictions sur les types exportés sans validation dans toutes les cibles.
- Ne pas modifier les opérations du protocole ni les formats de sérialisation balisés sans mettre à jour de manière coordonnée les environnements d’exécution hôtes.
- Ne pas modifier les modèles de génération sans mettre à jour et examiner les snapshots.
- Ne pas présumer de la sûreté des threads lorsque les bibliothèques d’exécution sont intentionnellement monothread.
- Ne pas introduire de dépendances circulaires entre paquets lorsque le graphe de build rejette les cycles.
- Ne pas modifier directement les sorties incluses dans les paquets.
### Pull request et transfert
- Respecter le format de titre, les conventions de périmètre et la confirmation de licence de contribution exigés par le dépôt.
- Inclure tous les snapshots générés et rapports de conformité requis par la modification des sources.
- Exclure de la modification les fichiers générés sans rapport, les modifications du fichier de verrouillage et les réglages locaux.
- Si la branche distante a avancé, effectuer un fetch et un rebase ou un merge, résoudre les conflits, valider puis effectuer un push normal ; ne jamais effectuer de push forcé.
- Consigner dans la checklist de livraison la synchronisation des dépôts ou de la documentation en aval, au lieu de compter sur la mémoire.
