Skip to content

Reference source · 0.1.0-dev.11

sdk-conformance.ts

scripts/sdk-conformance.ts · Download original file

import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline';
import { mkdtempSync,rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join,resolve } from 'node:path';
import { fixture,PATIENT } from '../src/reference/fixtures.ts';
import { SCOPES } from '../src/authorization.ts';
import { signCredential } from '../src/identity.ts';
import { decryptBackup } from '../src/archive.ts';
import { seal,type Envelope } from '../src/envelope.ts';
import { b64,unb64,ProtocolError } from '../src/core.ts';
import { signedMalformed,malformedHeaders } from '../tests/helpers/adversarial.ts';
import { Client, DurableMirror, ValidatedClinicalAdmission,DurableValidationAdmission } from '../.demo/typescript-consumer/node_modules/@convey/oprp-sdk/dist/index.js';
import { validationDigest } from '../src/external-fhir.ts';

const dir=mkdtempSync(join(tmpdir(),'oprp-sdk-conformance-'));
const python=resolve(process.env.OPRP_PYTHON??'.venv/bin/python');
const cases=[
 {name:'Python',command:python,args:['-u','python/conformance.py']},
 {name:'.NET',command:'dotnet',args:['.demo/dotnet-consumer/bin/Release/net10.0/Conformance.dll']},
];
try{
 // Exercise the COMPILED TypeScript installation entry point, not TypeScript source imports.
 const f=fixture();try{
  const client=new Client({identity:f.appIdentity,credential:f.appCredential,hostCredential:f.hostCredential,hostId:f.hostIdentity.public.subject,trust:f.trust,transport:async e=>f.host.handle(e,f.clock()),clock:f.clock});
  const pending=await client.requestAccess({patient:PATIENT,scopes:Object.values(SCOPES),requested_duration_seconds:1800,refresh_requested:true});assert.equal(pending.status,'pending');if(pending.status!=='pending')throw new Error('approval_missing');f.host.authorization.approve(f.app,pending.request_id,PATIENT,f.clock());await client.approvalStatus(pending.request_id);
  f.host.clinical.admission=new ValidatedClinicalAdmission({engine:'packaged-adapter',manifest:{version:'test',us_core:{version:'test'}}} as any);await assert.rejects(client.createResource(PATIENT,{resourceType:'Condition',subject:{reference:`Patient/${PATIENT}`},code:{text:'Unvalidated'}},'typescript-gated'),/external_validation_required/);f.host.clinical.admission=undefined;
  const proposed={resourceType:'Condition',subject:{reference:`Patient/${PATIENT}`},code:{text:'Rejected test'}};const durable=new DurableValidationAdmission({path:join(dir,'packaged-validation.sqlite'),owner:JSON.stringify(f.hostIdentity.public),clock:f.clock,validator:{binding:'packaged-test',engine:'packaged-test',manifest:{version:'test'},profile:()=>['packaged-test'],validateMany:async(resources:any[])=>resources.map(resource=>({accepted:false,digest:validationDigest(resource),target:'oprp',engine:'packaged-test',profiles:['packaged-test'],terminology:'disabled',issues:[{severity:'error',code:'invalid'}]}))} as any});
  try{f.host.clinical.admission=durable;await assert.rejects(client.createResource(PATIENT,proposed,'packaged-durable'),/external_validation_required/);await durable.runOne();await assert.rejects(client.createResource(PATIENT,proposed,'packaged-durable'),/external_validation_rejected/);}finally{f.host.clinical.admission=undefined;durable.close();}
  const created=await client.createResource(PATIENT,{resourceType:'Condition',subject:{reference:`Patient/${PATIENT}`},code:{text:'Compiled SDK'}},'typescript-create');assert.equal((await client.readResource(PATIENT,'Condition',created.resource_id)).version,1);
  const mirror=new DurableMirror(join(dir,'ts-mirror.sqlite'),client,PATIENT);try{await mirror.sync(1);assert.equal(mirror.resources().length,2);
   const grantId=client.authorization!.grant_id;f.host.visibility.setGrant(grantId,{labels:[],allow_unlabelled:true,deny_fields:{Condition:['/code']}},f.clock());await mirror.sync(1);const condition=mirror.resources().find(r=>r.resource.resourceType==='Condition')!;assert.equal(condition.resource.code,undefined);assert(condition.resource.meta.tag.some((t:any)=>t.code==='SUBSETTED'));
   f.host.visibility.setGrant(grantId,{labels:[],allow_unlabelled:true,deny_fields:{}},f.clock());const secret={system:'http://terminology.hl7.org/CodeSystem/v3-Confidentiality',code:'R'};f.host.clinical.classify(PATIENT,'Condition',created.resource_id,[secret],f.clock());await mirror.sync(1);assert.equal(mirror.resources().length,1);f.host.visibility.setGrant(grantId,{labels:[secret],allow_unlabelled:true,deny_fields:{}},f.clock());await mirror.sync(1);assert.equal(mirror.resources().length,2);
  }finally{mirror.close();}
  console.log('PASS packaged TypeScript entry point: encrypted clinical operations, field masking and visibility-loss/regrant mirror recovery');
 }finally{f.host.close();f.trust.close();}
 for(const test of cases){
  const f=fixture();let calls=0,dropNext=false,replyOverride:any=null,peerPublic:any;let stderr='';
  const child=spawn(test.command,test.args,{env:{...process.env,OPRP_PYTHON:python,PYTHONPATH:resolve('.demo/python-install'),DOTNET_CLI_TELEMETRY_OPTOUT:'1'},stdio:['pipe','pipe','pipe']});
  const reader=createInterface({input:child.stdout});const stream=reader[Symbol.asyncIterator]();child.stderr.on('data',chunk=>{stderr+=chunk;});
  async function read(){const next=await stream.next();assert(!next.done,`${test.name} peer ended (${stderr.slice(0,200)})`);return JSON.parse(next.value);}
  function send(value:any){child.stdin.write(JSON.stringify(value)+'\n');}
  let credential:any;let app:any;
  async function command(value:any){send(value);for(;;){const packet=await read();if(packet.kind==='exchange'){calls++;let response=f.host.handle(packet.envelope,f.clock());if(replyOverride){const header=JSON.parse(unb64(packet.envelope.protected).toString());response=seal(replyOverride,f.hostIdentity,f.hostCredential,peerPublic,f.clock(),header.messageId);replyOverride=null;}if(dropNext){dropNext=false;send({drop:true});}else send({envelope:response});}else {assert.equal(packet.kind,'result');if(packet.error)throw new Error(packet.error);return packet.result;}}}
  const call=(method:string,...args:any[])=>command({action:'call',method,args});
  try{
   const first=await read();assert.equal(first.kind,'identity');peerPublic=first.identity.public;credential=signCredential(f.applicationAuthority,first.identity.public,'application',f.clock());app=f.trust.validate(credential,f.clock());
   send({now:f.clock(),credential,host_credential:f.hostCredential,policies:f.trust.policies,status:Object.fromEntries([...f.trust.roots.keys()].map(i=>[i,f.trust.statusEvidence(i)]))});assert.equal((await read()).kind,'ready');
   const control=seal({status:'ok'},f.hostIdentity,f.hostCredential,peerPublic,f.clock(),null);assert.equal((await command({action:'crypto_open',envelope:control})).status,'ok');
   for(const edit of malformedHeaders(f.clock()))await assert.rejects(command({action:'crypto_open',envelope:signedMalformed(f.hostIdentity,f.hostCredential,peerPublic,f.clock(),edit)}));
   await assert.rejects(command({action:'crypto_open',envelope:signedMalformed(f.hostIdentity,f.hostCredential,peerPublic,f.clock(),{},true)}));
   const badSignature=unb64(control.signature);badSignature[0]^=1;for(const envelope of [{...control,signature:b64(badSignature)},{...control,nonce:control.nonce+'='}])await assert.rejects(command({action:'crypto_open',envelope}));
   const unicode={text:'Café <>& "quote"\n',number:1.25,integer:1,small:1e-7,threshold:1e-6,large:1e20,indexed:{'2':'second','1':'first'}};
   assert.equal((await command({action:'canonical',value:unicode})).encoded,JSON.stringify(unicode));
   const grant=await call('request_access',PATIENT,Object.values(SCOPES),1800,true,'sdk-access');assert.equal(grant.status,'pending');f.host.authorization.approve(app,grant.request_id,PATIENT,f.clock());assert.equal((await call('approval_status',grant.request_id)).status,'granted');
   const baseline=await call('snapshot',PATIENT);
   const resource={resourceType:'Condition',subject:{reference:`Patient/${PATIENT}`},code:{text:'Synthetic SDK example'},extension:[{url:'urn:example:preserved',valueDecimal:1.25}]};
   const created=await call('create_resource',PATIENT,resource,'sdk-create');const repeated=await call('create_resource',PATIENT,resource,'sdk-create');assert.deepEqual(created,repeated);
   const initialMirror=await command({action:'mirror',patient:PATIENT,limit:1});assert.equal(initialMirror.resources.length,2);
   let mirrorState=initialMirror.state;
   for(const edit of [{events:[null]},{has_more:'false'},{events:[{event_id:'bad',kind:'created',version:1,resource:{resourceType:'Patient',id:'other-patient'}}]},{events:[{event_id:'bad',kind:'deleted',version:2,resource:{resourceType:'Condition',id:'x',code:{text:'Forbidden'}}}]},{events:[{event_id:'bad',kind:'unknown',version:1,resource:{resourceType:'Patient',id:PATIENT}}]},{events:[{event_id:'bad',kind:'updated',version:1.5,resource:{resourceType:'Patient',id:PATIENT}}]},{events:[{event_id:'bad',kind:'updated',version:2,resource:{resourceType:'Patient',id:PATIENT,meta:{security:[{system:'http://terminology.hl7.org/CodeSystem/v3-ActCode',code:'DELAU'}]}}}]}]){
    replyOverride={status:'ok',cursor:mirrorState.cursor,visibility_epoch:mirrorState.visibility_epoch,has_more:false,events:[],...edit};await assert.rejects(command({action:'mirror',patient:PATIENT,limit:1}));await assert.rejects(command({action:'mirror_view',patient:PATIENT,allow_stale:true}),/mirror_unavailable/);const recovered=await command({action:'mirror',patient:PATIENT,limit:1});assert.equal(recovered.resources.length,2);mirrorState=recovered.state;
   }
   const readback=await call('read_resource',PATIENT,'Condition',created.resource_id);assert.equal(readback.resource.extension[0].valueDecimal,1.25);const updated={...readback.resource,note:[{text:'Synthetic changed note'}]};
   await call('update_resource',PATIENT,updated,1,'sdk-update');await assert.rejects(call('update_resource',PATIENT,updated,1,'sdk-conflict'),/version_conflict/);
   await call('delete_resource',PATIENT,'Condition',created.resource_id,2,'sdk-delete');await assert.rejects(call('read_resource',PATIENT,'Condition',created.resource_id),/resource_deleted/);
   assert.equal((await call('read_resource',PATIENT,'Condition',created.resource_id,1)).version,1);assert.equal((await call('resource_history',PATIENT,'Condition',created.resource_id)).versions.length,3);
   const mirrored=await command({action:'mirror',patient:PATIENT,limit:1});assert.equal(mirrored.resources.length,1);assert.equal((await command({action:'mirror_view',patient:PATIENT})).resources.length,1);
   const historyPage=await call('resource_history',PATIENT,'Condition',created.resource_id,1);assert.equal(historyPage.has_more,true);assert.equal((await call('resource_history',PATIENT,'Condition',created.resource_id,1,historyPage.page_cursor)).versions[0].version,2);
   const page=await call('changes',PATIENT,baseline.cursor);assert.deepEqual(page.events.map((e:any)=>e.kind),['created','updated','deleted']);f.host.feed.retainAfter(f.host.feed.watermark());assert.equal((await command({action:'mirror',patient:PATIENT,limit:1})).resources.length,1);
   const state=await command({action:'state'});f.advance(301);await command({action:'clock',now:f.clock(),status:Object.fromEntries([...f.trust.roots.keys()].map(i=>[i,f.trust.statusEvidence(i)]))});
   const before=calls;await command({action:'persist_failure'});assert.equal(calls,before);
   dropNext=true;await assert.rejects(call('snapshot',PATIENT),/transport_unavailable/);await command({action:'restore'});assert.equal((await call('snapshot',PATIENT)).status,'ok');const refreshed=await command({action:'state'});assert.equal(refreshed.authorization.authorized_until,state.authorization.authorized_until);
   const archive=await call('export_record',PATIENT);const backup=await command({action:'archive',archive,patient:PATIENT});assert.equal(backup.profile,'oprp-reference-r4-expanded');assert.deepEqual(decryptBackup(backup.backup,Buffer.from(backup.key,'base64url')),archive);
   const until=f.clock()+500,callback='https://requester.example/oprp/exchange';await command({action:'receiver_expect',callback,id:'sdk-sub',until});await call('subscribe',PATIENT,callback,['Condition'],until,'sdk-sub');
   const factory=()=>async (message:Envelope)=>await command({action:'receiver',envelope:message});
   assert.equal((await f.host.subscriptions.deliver(f.clock,factory)).acknowledged,1);await f.host.subscriptions.deliver(f.clock,factory);
   let inbox=await command({action:'inbox'});assert.equal(inbox.tasks.length,1);await command({action:'complete',id:'sdk-sub',revision:inbox.tasks[0].revision});
   await call('create_resource',PATIENT,resource,'notified-create');let lost=true;await f.host.subscriptions.deliver(f.clock,()=>async message=>{const reply=await command({action:'receiver',envelope:message});if(lost){lost=false;throw new Error('lost_ack');}return reply;});
   await command({action:'receiver_expect',callback,id:'sdk-sub',until});f.advance(2);await command({action:'clock',now:f.clock(),status:Object.fromEntries([...f.trust.roots.keys()].map(i=>[i,f.trust.statusEvidence(i)]))});await f.host.subscriptions.deliver(f.clock,factory);inbox=await command({action:'inbox'});assert.equal(inbox.tasks[0].revision,2);
   dropNext=true;const intent={operation:'create_resource',patient:PATIENT,resource_type:'Condition',resource,operation_id:'queued-sdk-create'};const queue=await command({action:'queue',intents:[intent]});assert.equal(queue.entries[0].state,'pending');assert.equal((await command({action:'queue',intents:[]})).entries[0].state,'committed');
   dropNext=true;await assert.rejects(command({action:'mirror',patient:PATIENT,limit:1}),/transport_unavailable/);await assert.rejects(command({action:'mirror_view',patient:PATIENT}),/mirror_unavailable/);assert.equal((await command({action:'mirror',patient:PATIENT,limit:1})).resources.length,3);
   const fullGrant=refreshed.authorization.grant_id;
   f.host.visibility.setGrant(fullGrant,{labels:[],allow_unlabelled:true,deny_fields:{Condition:['/extension']}},f.clock());
   const filtered=await command({action:'mirror',patient:PATIENT,limit:1});const conditions=filtered.resources.filter((r:any)=>r.resource.resourceType==='Condition');assert.equal(conditions.length,2);for(const r of conditions){assert.equal(r.resource.extension,undefined);assert(r.resource.meta.tag.some((t:any)=>t.code==='SUBSETTED'));}
   await assert.rejects(call('update_resource',PATIENT,conditions[0].resource,conditions[0].version,'sdk-filtered-write'),/insufficient_visibility/);await assert.rejects(call('export_record',PATIENT),/export_visibility_required/);
   f.host.visibility.setGrant(fullGrant,{labels:[],allow_unlabelled:true,deny_fields:{}},f.clock());const secret={system:'http://terminology.hl7.org/CodeSystem/v3-Confidentiality',code:'R'};const hiddenId=conditions[0].resource.id;f.host.clinical.classify(PATIENT,'Condition',hiddenId,[secret],f.clock());assert.equal((await command({action:'mirror',patient:PATIENT,limit:1})).resources.length,2);await assert.rejects(call('read_resource',PATIENT,'Condition',hiddenId),/not_found/);
   f.host.visibility.setGrant(fullGrant,{labels:[secret],allow_unlabelled:true,deny_fields:{}},f.clock());assert.equal((await command({action:'mirror',patient:PATIENT,limit:1})).resources.length,3);assert.equal((await call('read_resource',PATIENT,'Condition',hiddenId)).resource.meta.security[0].code,'R');
   f.host.clinical.admission={check:()=>{throw new ProtocolError('external_validation_rejected');},capabilities:()=>({engine:'rejection-conformance'})};
   const rejectedQueue=await command({action:'queue',intents:[{operation:'create_resource',patient:PATIENT,resource_type:'Condition',resource,operation_id:'queued-validation-rejected'},{operation:'create_resource',patient:PATIENT,resource_type:'Condition',resource,operation_id:'queued-after-rejection'}]});assert.deepEqual(rejectedQueue.entries.slice(-2).map((e:any)=>e.state),['rejected','pending']);f.host.clinical.admission=undefined;
   await command({action:'trust_restore',issuer:app.issuer,key:app.signingKid});
   const narrowed=await call('request_access',PATIENT,[SCOPES.demographicsRead],1800,true,'sdk-narrowed');assert.equal(narrowed.status,'granted');await assert.rejects(command({action:'mirror_view',patient:PATIENT}),/mirror_unavailable/);assert.equal((await command({action:'mirror',patient:PATIENT,limit:1})).resources.length,1);
   f.host.authorization.revoke(narrowed.grant_id);await assert.rejects(command({action:'mirror',patient:PATIENT,limit:1}),/authorization_expired_or_revoked/);await assert.rejects(command({action:'mirror_view',patient:PATIENT}),/mirror_unavailable/);await assert.rejects(call('snapshot',PATIENT),/authorization_expired_or_revoked/);
   console.log(`PASS packaged ${test.name} SDK: grants, scoped CRUD/history/deletion, refresh-loss recovery, archive/backup, callback restart/dedup, queue retry, paged reads, durable mirrors/gaps/outages/narrowed grants, visibility invalidation, 12 envelope rejections, 7 authenticated mirror-page rejections, trust persistence and revocation`);
   await command({action:'stop'});
  }finally{child.kill();reader.close();f.host.close();f.trust.close();}
 }
}finally{rmSync(dir,{recursive:true,force:true});}