Il check verde che non aveva guardato dentro il Bundle
Un endpoint non valido che supera la validazione, un Bundle con trentatré entry dato per pulito, un round trip che non può fallire: tre modi in cui un controllo verde non dimostra niente.
In un repository pubblico di fixture per test di integrazione — messaggi HL7 v2 e i Bundle FHIR prodotti dalla loro conversione, mantenuti da un servizio di genomica del NHS — c’è un valore che abbiamo letto due volte senza vederlo:
"endpoint": "https//fhir.mft.nhs.uk/Endpoint/cymru.nhs.uk"
Mancano i due punti dopo lo schema. Due righe più sotto, nello stesso file, l’endpoint di origine è scritto correttamente con https://. È proprio questa vicinanza che rende il difetto invisibile: l’occhio confronta due stringhe che si somigliano e registra «sono uguali».
Lo ha trovato un validatore, non una rilettura attenta. E la domanda interessante non è come sia finito lì, ma perché non se ne fosse accorto nessuno dei controlli automatici che quel file già attraversava.
Perché il controllo che c’era non poteva accorgersene
MessageHeader.destination.endpoint in FHIR R4 è obbligatorio — cardinalità 1..1 — ed è di tipo url: la specifica lo definisce come Uniform Resource Locator, cioè un indirizzo a cui si accede direttamente con il protocollo indicato. Senza i due punti non c’è nessuno schema, e quella stringa smette di essere un indirizzo assoluto: diventa un percorso relativo. Un messaggio instradato lì non arriva da nessuna parte.
Qui c’è il punto che conta per chi deve valutare uno strumento di validazione. La definizione del tipo url in R4 porta con sé un’espressione regolare, ed è \S*: qualunque sequenza di caratteri senza spazi. L’abbiamo verificato scaricando la StructureDefinition, perché era il genere di dettaglio su cui non volevamo fidarci della memoria. Un validatore che controlla il tipo primitivo secondo la specifica, quindi, quel valore lo accetta — e fa bene, perché sta eseguendo esattamente la regola che gli è stata data. Il vincolo che quella stringa viola non è espresso in una forma che una macchina possa applicare da sola.
Un file può dunque superare tutti i controlli disponibili e contenere un indirizzo che non risolve. Non è un difetto del validatore: è la distanza fra «conforme» e «funzionante». Nella pratica dell’integrazione ospedaliera quella distanza è esattamente dove abitano i guasti costosi, quelli che si manifestano il primo giorno di esercizio reale e non un minuto prima.
Il secondo caso, che è nostro
Pochi giorni prima avevamo passato nel nostro validatore un Bundle FHIR con trentatré entry. Risposta: zero errori, zero avvisi. Sembrava una buona notizia; era il sintomo peggiore possibile. Lo strumento non aveva mai guardato dentro. Validava l’involucro — la risorsa Bundle, la sua struttura, i campi di primo livello — e si fermava lì, senza scendere in entry[].resource. Trentatré risorse mai esaminate, e un segno di spunta verde.
L’abbiamo scoperto facendo l’esercizio sui file di qualcun altro, corretto lo stesso giorno e dichiarato apertamente nella discussione su chat.fhir.org in cui la cosa era emersa. Adesso i percorsi negli esiti riportano entry[N].resource, e un profilo dichiarato in meta.profile ma non installato produce un avviso esplicito invece di un risultato pulito.
Quest’ultimo punto merita una riga in più, perché è una confusione diffusa. In R4 meta.profile è l’elenco dei profili a cui la risorsa dichiara di conformarsi: è un’affermazione di chi ha prodotto il file, non un accertamento. Dire di essere conformi ed esserlo sono due fatti distinti, e uno strumento onesto deve sempre far capire quale dei due ha verificato.
Il terzo caso: i test che non possono fallire
Lo stesso schema si ripresenta nei test di round trip, cioè nei test che convertono un messaggio v2 in FHIR, lo riconvertono in v2 e confrontano il risultato con la partenza. Il controllo minimo, molto comune, è verificare che ciò che torna indietro sia un messaggio v2, ossia che cominci con MSH. Nel repository da cui siamo partiti questo passaggio è documentato dagli autori stessi come controllo di integrità e non come validazione: una dichiarazione che apprezziamo e che si vorrebbe vedere più spesso.
Il problema nasce quando nessuno la fa, quella dichiarazione. Un test così intercetta un crash, non una perdita silenziosa. Si possono perdere per strada un identificativo del paziente, un campo di un segmento, un intero OBX con il suo risultato: il messaggio ricostruito continuerà comunque a cominciare con MSH. Il test resta verde per sempre — e più a lungo resta verde, più si consolida la convinzione che quella conversione sia coperta.
La differenza fra un controllo di vitalità e un vero oracolo è tutta qui: un test utile deve poter fallire per il motivo per cui lo hai scritto. Il passo per arrivarci è corto e cambia tutto. Si confronta il messaggio ricostruito con l’originale e si misura la differenza contro un elenco di perdite dichiarato, versionato insieme al caso di test. Perdita prevista: passa. Perdita fuori elenco: fallisce. Elenco che non corrisponde più alla differenza osservata: il caso è cambiato e vuole una persona che lo guardi. Come effetto collaterale, quell’elenco è anche il documento che risponde alla domanda «cosa non siamo riusciti a mappare, e perché» — una domanda che di solito trova risposta in un wiki, dove invecchia senza che nessuno se ne accorga.
![Tre controlli verdi sullo stesso file: il tipo url in R4 corrisponde alla regex \S*, quindi un indirizzo senza i due punti passa; il nostro validatore si fermava all’involucro del Bundle senza entrare in entry[].resource; il test di round trip verificava soltanto che il messaggio tornasse indietro cominciando con MSH. Titolo: 0 errori. Contro cosa?](https://techemvee.eu/wp-content/uploads/2026/09/green-check-bundle-wide.png)
Tre domande da fare la prossima volta
Le tre storie raccontate qui sopra sono la stessa storia: un controllo che passa perché non era in condizione di fallire. La prossima volta che un report di validazione dice che è tutto a posto — che arrivi da un fornitore, da uno strumento o dal proprio team — sono tre le domande che cambiano la conversazione.
- Contro cosa ha validato? Non «contro FHIR»: quali profili, presi da quali pacchetti, in quale versione. Un pacchetto assente o una versione diversa cambiano l’esito, e due strumenti possono dire entrambi «conforme» avendo guardato cose diverse.
- Fin dove è sceso? Un esito su un Bundle che non nomina mai le singole entry non è un esito su quel Bundle.
- Cosa sa dirti di non aver controllato? È la domanda che separa uno strumento utile da uno rassicurante. Un rifiuto esplicito è sempre preferibile a un successo ambiguo.
Nella nostra esperienza questo è il rischio meno visibile di un progetto di integrazione, perché non si manifesta come un errore: si manifesta come una sequenza di semafori verdi che nessuno ha motivo di mettere in discussione, fino al giorno in cui i messaggi veri cominciano a passare. A quel punto si vede benissimo.
Il difetto dell’endpoint l’ha trovato BridgeLab, il workbench con cui ispezioniamo e validiamo messaggi v2 e Bundle FHIR. Il difetto nel nostro validatore, invece, l’ha trovato l’esercizio di usarlo sui file di qualcun altro: è il genere di prova che nessuna suite di test interna sostituisce.
Fonti
- HL7 FHIR R4, Data Types e MessageHeader — v4.0.1, Technical Correction #1, 1 novembre 2019
- HL7 FHIR R4, Resource (definizione di
meta.profile) — v4.0.1 - Repository pubblico delle fixture citate: nw-gmsa/Testing
- Discussione di riferimento: Shared corpus of hard v2 → FHIR test cases, canale #v2 to FHIR su chat.fhir.org