Reference source · 0.1.0-dev.11
clinical-conformance.ts
scripts/clinical-conformance.ts · Download original file
import assert from 'node:assert/strict';
import { readFileSync,writeFileSync,mkdirSync,mkdtempSync,rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { resolve } from 'node:path';
import { ExternalFhirValidator,ValidatedClinicalAdmission } from '../src/external-fhir.ts';
import { RESOURCE_TYPES } from '../src/resource-profile.ts';
import { fixture,PATIENT } from '../src/reference/fixtures.ts';
import { SCOPES } from '../src/authorization.ts';
import { DurableValidationAdmission } from '../src/validation-jobs.ts';
import { ReferenceHost } from '../src/host.ts';
import { makeIdentity,signCredential } from '../src/identity.ts';
import { ReferenceDirectory } from '../src/reference/directory.ts';
import { DirectoryTrust } from '../src/discovery.ts';
const root=resolve('.demo/fhir-validator');mkdirSync(root,{recursive:true});
const validator=new ExternalFhirValidator({jar:process.env.OPRP_VALIDATOR_JAR??resolve(root,'validator_cli.jar'),workspace:root,profiles:resolve('profiles'),usCorePackage:process.env.OPRP_US_CORE_PACKAGE??resolve(root,'us-core-9.0.0.tgz')});
const examples=RESOURCE_TYPES.map(t=>JSON.parse(readFileSync(`profiles/examples/${t}.json`,'utf8')));
// Independent validator checks actual profile constraints, not an echo of the handwritten validator.
const positives=await validator.validateMany(examples);assert(positives.every(r=>r.accepted),JSON.stringify(positives.filter(r=>!r.accepted).map(r=>r.issues)));
console.log('PASS six OPRP StructureDefinitions with HL7 validator 6.10.4 / R4 4.0.1 (terminology disabled)');
const invalid=examples.map(r=>({...r,contained:[{resourceType:'Patient',id:'forbidden'}]}));const negatives=await validator.validateMany(invalid);assert(negatives.every(r=>!r.accepted));assert(negatives.every(r=>r.issues.some(i=>i.severity==='error'||i.severity==='fatal')));console.log('PASS independent rejection of forbidden contained content for all six profiles');
const usTypes=RESOURCE_TYPES.filter(t=>t!=='MedicationStatement');const us=usTypes.map(t=>JSON.parse(readFileSync(`profiles/us-examples/${t}.json`,'utf8')));const usReports=await validator.validateMany(us,'oprp-us-core');assert(usReports.every(r=>r.accepted),JSON.stringify(usReports.filter(r=>!r.accepted).map(r=>r.issues)));await assert.rejects(validator.validate(examples[2]!,'oprp-us-core'),/unsupported_us_core_resource/);const usNegative=await validator.validate(examples[4]!,'oprp-us-core');assert.equal(usNegative.accepted,false);assert(usNegative.issues.some(i=>i.severity==='error'&&i.details?.text?.includes('us-core-7')));console.log('PASS five mapped US Core 9.0.0 profiles; MedicationStatement mapping rejects explicitly');
const f=fixture();try{
const gate=new ValidatedClinicalAdmission(validator);f.host.clinical.admission=gate;const grant=await f.client.requestAccess({patient:PATIENT,scopes:Object.values(SCOPES),requested_duration_seconds:1800,refresh_requested:true});if(grant.status==='pending'){f.host.authorization.approve(f.app,grant.request_id,PATIENT,f.clock());await f.client.approvalStatus(grant.request_id);}
const resource=structuredClone(examples[0]!);delete resource.id;await assert.rejects(f.client.createResource(PATIENT,resource,'clinical-gated-create'),/external_validation_required/);const approved=await gate.assess([resource],PATIENT,f.clock());assert(approved[0]!.accepted);const result=await f.client.createResource(PATIENT,resource,'clinical-gated-create');assert.equal(result.version,1);assert.deepEqual(await f.client.createResource(PATIENT,resource,'clinical-gated-create'),result);assert.equal((await f.client.capabilities()).external_validation.engine,'hl7-validator-6.10.4');
console.log('PASS Host rejects unvalidated write, admits digest-bound external preflight and preserves idempotent retries');
}finally{f.host.close();f.trust.close();}
const local=new ExternalFhirValidator({...validator.options,terminology:resolve('profiles/terminology')});
const system='https://www.convey.care/fhir/CodeSystem/oprp-synthetic-clinical';const fields:any={Condition:['code','condition'],AllergyIntolerance:['code','allergy'],MedicationStatement:['medicationCodeableConcept','medication'],Procedure:['code','procedure'],Immunization:['vaccineCode','vaccine']};
const coded=examples.map(r=>{const value=structuredClone(r);const field=fields[r.resourceType];if(field)value[field[0]]={coding:[{system,code:field[1]}]};return value;});
const localReports=await local.validateMany(coded);assert(localReports.every(r=>r.accepted),JSON.stringify(localReports.filter(r=>!r.accepted).map(r=>r.issues)));
const localBad=await local.validateMany([{...coded[0],code:{coding:[{system,code:'invented'}]}},{...coded[0],code:{coding:[{system:'http://loinc.org',code:'2345-7'}]}},{...coded[5],status:'in-progress'}]);assert(localBad.every(r=>!r.accepted));console.log('PASS six locally coded examples; invalid code, unavailable LOINC catalogue and value-set violation reject');
const durableFixture=fixture(),folder=mkdtempSync(resolve(tmpdir(),'oprp-real-validation-'));const identity=makeIdentity('urn:oprp:demo:durable-destination'),credential=signCredential(durableFixture.hostingAuthority,identity.public,'host',durableFixture.clock());const destination=new ReferenceHost(':memory:',identity,credential,durableFixture.trust,durableFixture.policy);const directory=new ReferenceDirectory(':memory:',durableFixture.hostingAuthority,['patients.example'],durableFixture.trust);
const options={path:resolve(folder,'intake.sqlite'),owner:JSON.stringify(durableFixture.hostIdentity.public),validator:local,clock:durableFixture.clock};let intake=new DurableValidationAdmission(options);let migration=new DurableValidationAdmission({...options,path:resolve(folder,'migration.sqlite'),owner:JSON.stringify(identity.public)});
try{durableFixture.host.clinical.admission=intake;destination.clinical.admission=migration;const grant=await durableFixture.client.requestAccess({patient:PATIENT,scopes:Object.values(SCOPES),requested_duration_seconds:1800,refresh_requested:true});if(grant.status==='pending'){durableFixture.host.authorization.approve(durableFixture.app,grant.request_id,PATIENT,durableFixture.clock());await durableFixture.client.approvalStatus(grant.request_id);}
const value=structuredClone(coded[0]!);delete value.id;await assert.rejects(durableFixture.client.createResource(PATIENT,value,'durable-real'),/external_validation_required/);intake.close();intake=new DurableValidationAdmission(options);durableFixture.host.clinical.admission=intake;assert.equal((await intake.runOne())!.state,'accepted');const created=await durableFixture.client.createResource(PATIENT,value,'durable-real');assert.equal(created.version,1);
const address='synthetic@patients.example';directory.enroll(address,PATIENT,durableFixture.hostCredential,'https://source.example/oprp/exchange',durableFixture.clock());const trust=new DirectoryTrust({[durableFixture.hostingAuthority.issuer]:{publicKey:durableFixture.hostingAuthority.publicKey,domains:['patients.example']}},durableFixture.trust);
const plan=durableFixture.host.migration.begin(PATIENT,credential,'https://destination.example/oprp/exchange',directory.document(address,durableFixture.clock()),trust,PATIENT,durableFixture.clock(),'durable-real-migration');destination.migration.admit(plan,PATIENT,durableFixture.clock());const archive=durableFixture.host.migration.freeze('durable-real-migration',durableFixture.clock()),caller=durableFixture.trust.validate(durableFixture.hostCredential,durableFixture.clock());assert.throws(()=>destination.migration.stage(plan,archive,true,caller,durableFixture.clock()),/external_validation_required/);assert.equal(destination.migration.incoming('durable-real-migration').ready,undefined);
migration.close();migration=new DurableValidationAdmission({...options,path:resolve(folder,'migration.sqlite'),owner:JSON.stringify(identity.public)});destination.clinical.admission=migration;assert.equal((await migration.runOne())!.state,'accepted');const ready=destination.migration.stage(plan,archive,true,caller,durableFixture.clock()).ready,release=durableFixture.host.migration.release('durable-real-migration',ready,durableFixture.clock());destination.migration.activate('durable-real-migration',release,caller,durableFixture.clock());assert.equal(destination.migration.custody(PATIENT).state,'active');assert.equal(durableFixture.host.migration.custody(PATIENT).state,'moved');console.log('PASS real-engine durable intake restart and migration worker restart, readiness gate and activated custody');
}finally{intake.close();migration.close();directory.close();destination.close();durableFixture.host.close();durableFixture.trust.close();rmSync(folder,{recursive:true,force:true});}
const evidence={version:validator.manifest.version,validator:validator.engine,fhir:'4.0.1',us_core:'9.0.0',terminology:'local finite catalogue; remote disabled',validation_binding:local.binding,dependency_packages:validator.dependencies.packages.length,http_access:false,positive:positives.map((r,i)=>({type:RESOURCE_TYPES[i],accepted:r.accepted,warnings:r.issues.filter(i=>i.severity==='warning').length})),negative:negatives.map((r,i)=>({type:RESOURCE_TYPES[i],accepted:r.accepted})),us:usReports.map((r,i)=>({type:usTypes[i],accepted:r.accepted,warnings:r.issues.filter(i=>i.severity==='warning').length})),local_positive:localReports.map((r,i)=>({type:RESOURCE_TYPES[i],accepted:r.accepted})),local_negative:localBad.map(r=>({accepted:r.accepted,errors:r.issues.filter(i=>i.severity==='error').map(i=>i.details?.text??i.code)})),host_gate:true,us_missing_performed_rejected:true,durable_intake_restart:true,durable_migration_restart:true};writeFileSync(resolve(root,'evidence.json'),JSON.stringify(evidence,null,2)+'\n');