Le problème de la résolution à l'exécution
Avant la version 2511, Aidbox résolvait toutes les références canoniques à l'exécution. Lorsqu'une StructureDefinition référence http://hl7.org/fhir/us/core/StructureDefinition/us-core-race sans version, Aidbox devait déterminer quelle version utiliser. Et même lorsque le résultat était mis en cache, le cache était fragile. L'installation d'un nouveau paquet pouvait introduire de nouveaux canoniques, réinitialiser les caches et faire en sorte que la même référence se résolve vers une version différente.
C'est ainsi que la plupart des paquets FHIR sont publiés. Les canoniques contiennent des références sortantes vers d'autres canoniques — des ValueSets pointant vers des CodeSystems, des profils pointant vers des StructureDefinitions de base — et la grande majorité de ces références ne portent aucune version. L'hypothèse est que le consommateur déduira la version à partir du contexte.
Cette hypothèse transfère la responsabilité de la résolution des versions au consommateur.
Le vrai problème n'était pas la performance — c'était l'absence de contexte. À l'exécution, tout ce qu'on possède est un ensemble plat de paquets installés et leurs canoniques. Mais quels paquets ont été explicitement installés par l'utilisateur? Lesquels sont des dépendances transitives? Lesquels dépendent de quoi? Cette information était effectivement perdue après l'installation. Chaque paquet n'était qu'un ensemble de canoniques déversés dans le même bassin commun. Comme le résultat de la résolution dépendait de ce qui se trouvait dans ce bassin, installer ou supprimer un paquet pouvait silencieusement modifier la version vers laquelle une référence se résolvait. Des parties du graphe de dépendances pouvaient apparaître ou disparaître sans que le résolveur puisse le détecter.
Cela rendait les scénarios de dépendances complexes difficiles à gérer correctement. Prenons hl7.fhir.us.davinci-hrex — il intègre trois versions différentes de hl7.fhir.us.core. Lorsqu'un profil de HRex référence http://hl7.org/fhir/us/core/StructureDefinition/us-core-provenance sans version, laquelle des trois versions de US Core Aidbox devrait-il choisir? Cela ne peut pas être déduit de manière fiable à l'exécution — les trois sont présentes simultanément, et il ne reste aucune information pour lever l'ambiguïté. Cette décision doit être prise avant le démarrage du système, et non lors d'un appel de validation.
Nous avions besoin d'un moyen de résoudre toutes ces ambiguïtés une fois pour toutes, en amont, lorsque le graphe de dépendances complet et l'intention de l'utilisateur sont encore disponibles — et non à l'exécution quand ils ont disparu.
La communauté FHIR se dirigeait déjà dans ce sens. Le guide FHIR IG sur l'épinglage a posé les bases — la spécification décrit l'algorithme de résolution prévu :
- Trouver le paquet qui contient la ressource avec la référence
- Compiler la liste complète des dépendances de ce paquet (transitives)
- Trouver toutes les ressources correspondant à l'URL canonique dans ces paquets
- Sélectionner la version la plus récente
Cela semble simple, mais les complications s'accumulent rapidement. « La plus récente » est ambigu lorsqu'on a des chaînes de version en semver, basées sur des dates, ou arbitraires. Plusieurs ressources peuvent avoir la même URL et la même version. Et dans le contexte d'une API RESTful, les canoniques arrivent sans métadonnées de paquet — le serveur peut ne pas savoir quel paquet « possède » un canonique donné.
La spécification est délibérément légère sur ces détails d'implémentation — elle laisse beaucoup à la discrétion des implémenteurs. Nous avons donc procédé à de l'ingénierie inverse à partir des outils existants qui tentent l'épinglage — IG Publisher et UploadFIG — et avons finalisé la stratégie de résolution lors du FHIR Camp 2025 en collaboration avec Grahame Grieve, Gino Canessa et Lloyd McKenzie.
Dans Aidbox, cela se traduit par une approche en deux phases pour la gestion des paquets : tout le travail difficile se produit avant l'exécution réelle du serveur, et l'exécution reçoit un ensemble canonique plat et entièrement résolu.
Approche en deux phases : configuration et exécution
Phase de configuration. Lorsque vous installez un paquet — via l'API, l'interface utilisateur ou la configuration BOX_BOOTSTRAP_FHIR_PACKAGES — Aidbox télécharge le paquet et toutes ses dépendances, puis parcourt l'intégralité du graphe de dépendances. Chaque référence canonique non versionnée est réécrite avec un suffixe |version explicite :
before: http://hl7.org/fhir/ValueSet/observation-status
after: http://hl7.org/fhir/ValueSet/observation-status|4.0.1
En même temps, les canoniques des paquets de dépendances que personne ne référence réellement sont supprimés — c'est l'élagage d'arbre.
Phase d'exécution. Tous les canoniques sont déjà résolus. Chaque référence sortante porte une version explicite. Pour Aidbox, résoudre un canonique est désormais une simple requête de base de données par <url>|<version> — pas de traversée de graphe, pas de résolution dépendante du contexte, pas d'ambiguïté. Les paquets en tant que concept n'existent plus à l'exécution; seuls les canoniques entièrement résolus subsistent.
Comment fonctionne l'épinglage dans Aidbox
Le processus d'épinglage parcourt chaque canonique dans les paquets installés et réécrit ses références sortantes avec des versions exactes.
Collecte des références sortantes
Pour chaque ressource canonique, Aidbox collecte toutes les références canoniques sortantes — les URLs qui pointent vers d'autres canoniques. Il le fait pour les cinq types de ressources canoniques qui peuvent contenir des références sortantes :
- StructureDefinition (à l'exclusion de
snapshot— seuldifferentialcompte) - CodeSystem
- ValueSet
- SearchParameter
- CapabilityStatement
Par exemple, une StructureDefinition peut référencer un ValueSet pour une liaison, une StructureDefinition de base dont elle dérive, et des définitions d'extension qu'elle utilise. Chacune de ces références a besoin d'une version épinglée.
Construction de la liste de candidats
Pour chaque référence sortante, Aidbox construit une liste de candidats : tous les canoniques avec l'URL correspondante disponibles dans l'arbre de dépendances du paquet (y compris les dépendances transitives). Si un paquet dépend de hl7.fhir.us.core@5.0.0, et que US Core dépend de hl7.terminology.r4, alors les canoniques de terminology sont des candidats valides.
L'algorithme de sélection de candidats
Lorsqu'il existe plusieurs candidats — différentes versions, différents paquets, même URL canonique — Aidbox sélectionne le meilleur en utilisant une chaîne de comparaison déterministe à plusieurs étapes :
| Priorité | Critère | Règle |
|---|---|---|
| 1 | Statut | active > draft > retired > unknown |
| 2 | Terminology par rapport à Core | Les canoniques des paquets hl7.terminology ont la priorité sur les canoniques portant le même nom des paquets core |
| 3 | Version | Comparée à l'aide de l'algorithme détecté : semver, entier, date ou alphabétique |
| 4 | lastUpdated | Critère de départage final utilisant meta.lastUpdated |
Avant la comparaison, les candidats sont filtrés. Aidbox exclut les canoniques des paquets correspondant à des motifs de bruit connus — expansions, exemples, liés à la recherche, éléments, corexml — et les ressources CodeSystem dont le content n'est pas "complete".
La détection de l'algorithme de version mérite une note. Aidbox vérifie d'abord versionAlgorithmString ou versionAlgorithmCoding sur la ressource (une fonctionnalité R5+). Si aucun n'est présent — ce qui est le cas la plupart du temps avec le contenu R4 — Aidbox déduit le schéma en inspectant la chaîne de version : ressemble-t-elle à du semver (1.2.3)? Un entier (4)? Une date (2024-01-15)? Le repli est la comparaison alphabétique. Cela correspond à l'approche pratique recommandée dans le guide FHIR IG.
Épinglage récursif
L'épinglage est récursif. Une fois qu'une référence est épinglée à un canonique spécifique, les propres références sortantes de ce canonique sont également épinglées — et ainsi de suite, sur toute la chaîne de dépendances. Chaque paire <url>|<version> n'est traitée qu'une seule fois (mémoïsée), de sorte que le processus se termine même avec des références circulaires.
Ce parcours récursif est également ce qui pilote l'élagage d'arbre.
Élagage d'arbre : seulement ce dont vous avez besoin
Un paquet FHIR typique contient des centaines ou des milliers de canoniques. Mais lorsque vous installez un paquet, vous n'avez pas besoin de tous les canoniques de ses dépendances — vous n'avez besoin que de ceux qui sont réellement référencés.
Considérons l'installation de hl7.fhir.us.core@5.0.0. Il dépend de hl7.terminology.r4 (4 158 canoniques) et de us.nlm.vsac (8 918 canoniques). Mais US Core ne référence qu'un petit sous-ensemble de chacun. Pourquoi charger le reste?
L'élagage d'arbre est la réponse. Lors du parcours d'épinglage récursif, Aidbox suit les canoniques de dépendances qui sont réellement atteints en suivant les références sortantes du paquet cible. Seuls ces canoniques accessibles — et leurs propres dépendances récursives — font partie de l'ensemble canonique final.
Le résultat :
- Paquet cible : tous les canoniques sont inclus (c'est ce que l'utilisateur a demandé)
- Paquets de dépendances : seuls les canoniques référencés sont inclus (élagués)
La réduction est spectaculaire. Comparez les comptes installedCanonicals d'une installation réelle :
POST /fhir/$fhir-package-install
Content-Type: application/json
{
"resourceType": "Parameters",
"parameter": [
{ "name": "package", "valueString": "hl7.fhir.us.core@5.0.0" }
]
}
La réponse montre à quel point l'élagage d'arbre réduit considérablement le nombre de canoniques. Sur les 4 158 canoniques dans hl7.terminology.r4, seulement 7 survivent. Sur les 8 918 dans us.nlm.vsac, seulement 20 :
| Paquet | Canoniques installés | Intention |
|---|---|---|
hl7.fhir.us.core@5.0.0 | 194 | direct |
us.nlm.vsac@0.3.0 | 20 | transitive |
hl7.fhir.uv.sdc@3.0.0 | 11 | transitive |
hl7.terminology.r4@3.1.0 | 7 | transitive |
Chaque canonique dans le système est unique par <url>|<version>. Pas de doublons, pas de conflits, pas d'ambiguïté.
Sources des paquets
Aidbox prend en charge trois façons d'obtenir des paquets :
Registres NPM. Le registre par défaut est https://fs.get-ig.org/pkgs, qui fait office de miroir de packages2.fhir.org avec des synchronisations quotidiennes. Vous pouvez diriger Aidbox vers n'importe quel registre compatible NPM — Simplifier, une instance privée Verdaccio, ou tout miroir personnalisé.
Système de fichiers local. Montez un répertoire contenant des paquets .tgz dans /srv/aidbox-fhir-packages dans le conteneur Aidbox. Les paquets doivent suivre la convention de nommage {name}#{version}.tgz — par exemple, hl7.fhir.r4.core#4.0.1.tgz. Aidbox vérifie d'abord les paquets locaux avant d'accéder au registre distant. C'est essentiel pour les environnements isolés du réseau et accélère également considérablement le démarrage.
URLs directes. Vous pouvez fournir une URL https:// pointant vers une archive .tgz ou un chemin file:// vers un fichier local. Utile pour installer des paquets qui ne sont publiés dans aucun registre — comme des IGs personnalisés construits localement avec SUSHI.
Substitutions de dépendances
Vous souvenez-vous du problème hl7.fhir.us.davinci-hrex mentionné plus tôt — trois versions de US Core, impossible à désambiguïser à l'exécution? Avec les substitutions, l'utilisateur fait ce choix explicitement à la phase de configuration.
HRex intègre hl7.fhir.us.core@7.0.0, hl7.fhir.us.core.v610 et hl7.fhir.us.core.v311. Si vous n'avez besoin que de la version 7.0.0, ignorez les deux autres :
POST /fhir/$fhir-package-install
Content-Type: application/json
{
"resourceType": "Parameters",
"parameter": [
{ "name": "package", "valueString": "hl7.fhir.us.davinci-hrex@1.1.0" },
{
"name": "override",
"part": [
{ "name": "from", "valueString": "hl7.fhir.us.core.v610" },
{ "name": "to", "valueBoolean": false }
]
},
{
"name": "override",
"part": [
{ "name": "from", "valueString": "hl7.fhir.us.core.v311" },
{ "name": "to", "valueBoolean": false }
]
}
]
}
Trois types de substitutions :
| Substitution | Syntaxe | Cas d'utilisation |
|---|---|---|
| Ignorer une dépendance | "to": false (valueBoolean) | Exclure une dépendance — comme supprimer des versions indésirables de US Core |
| Épingler une version | "to": "0.17.0" | Forcer une version spécifique d'une dépendance |
| Remplacer un paquet | "to": "npm:alt.package@1.0.0" | Substituer entièrement un paquet par un autre |
Le champ from prend en charge la correspondance par nom seul (hl7.fhir.us.core — correspond à n'importe quelle version) et la correspondance qualifiée par version (hl7.fhir.us.core@6.1.0 — correspond uniquement à cette version exacte). Les substitutions qualifiées par version ont la priorité.
Les substitutions résolvent également les lacunes des registres. hl7.fhir.us.core@8.0.0 dépend de us.nlm.vsac@0.23.0, mais Simplifier a cessé d'héberger les versions VSAC après 0.17.0. Épinglez-le à 0.17.0, ignorez-le entièrement, ou remplacez-le par une alternative — sans modifier les paquets sources.
Qu'en est-il de l'installation à l'exécution?
Cela signifie-t-il que vous perdez la possibilité d'installer des paquets et des canoniques à l'exécution? Non — mais avec quelques précisions.
Les paquets installés à l'exécution sont toujours épinglés automatiquement. La différence est que l'épinglage se produit dans un contexte isolé : seul l'arbre de dépendances propre au paquet est impliqué. Le contenu déjà installé est inaccessible lors de cette passe d'épinglage, de sorte que le nouveau paquet obtient un ensemble canonique cohérent en lui-même sans interférer avec ce qui est déjà chargé.
Les canoniques individuels que vous téléversez directement — en dehors de tout paquet — relèvent de votre responsabilité en matière d'épinglage. Si vous fournissez un canonique avec des références sortantes non versionnées, Aidbox ne tentera pas de les épingler pour vous. Il conserve cependant un mode de repli pour les références non épinglées : il utilisera le même algorithme de sélection de candidats décrit ci-dessus — statut, priorité de terminology, comparaison de version — pour résoudre vers la meilleure correspondance disponible. Rien ne se casse donc — vous n'obtenez simplement pas les garanties de déterminisme que fournit l'épinglage au niveau du paquet.
Ce que cela signifie en pratique
Voici ce que cela vous apporte dans Aidbox :
Prévisibilité. La même entrée d'installation de paquet produit toujours le même ensemble canonique. Il n'y a pas d'ambiguïté à l'exécution, pas de comportement dépendant de l'ordre, pas d'état caché affectant la résolution.
Performance. Les recherches de canoniques sont de simples requêtes de base de données par <url>|<version>. Pas de traversée de graphe, pas de parcours de dépendances, pas d'heuristiques de mise en cache.
Débogage facilité. Chaque canonique dans le système possède une version explicite. Lorsque la validation échoue, vous voyez exactement quelle version de quelle StructureDefinition a été utilisée — et non « ce qui s'est résolu à ce moment-là ».
Empreinte réduite. L'élagage d'arbre signifie que les dépendances transitives ne contribuent que les canoniques réellement nécessaires. L'installation de US Core intègre 7 canoniques de hl7.terminology.r4 au lieu de 4 158.
La contrepartie est que l'installation prend un peu plus de temps — Aidbox doit parcourir l'intégralité du graphe de dépendances, construire des listes de candidats et épingler récursivement les références. Mais pour l'ensemble de paquets initial, cela ne se produit qu'une seule fois, au premier démarrage.
Références
Spécification :
Documentation Aidbox :
Outils :
- IG Publisher — l'éditeur officiel de guides d'implémentation de HL7
- UploadFIG — l'outil de téléversement de canoniques de Brian Postlethwaite
- SUSHI — compilateur FHIR Shorthand pour construire des IGs personnalisés
- Verdaccio — proxy/registre NPM léger pour l'hébergement privé
Événements :
- FHIR Camp 2025 — où la stratégie de résolution a été finalisée








