Offline terminology and durable clinical validation
Experimental 0.1.0-dev.10 adds complete validator-cache content locking, a finite local terminology catalogue, and a restartable Host-owned validation worker. These are optional reference Host helpers. The encrypted exchange, Host-issued tokens, conditional patient approval, requester permissions and patient-selected hosting remain unchanged.
Locked offline installation
The toolchain lock pins HL7 validator 6.10.4 and US Core 9.0.0. The dependency lock freezes all packages in the tested cache, including versions the validator selects implicitly. It records each package's full sorted file-path/content tree, file count and byte count, plus the cache configuration hash. The checker rejects missing, added or changed packages/files, inconsistent package metadata, symbolic links and special files. Verification runs at construction and before/after every Java invocation. A mismatch grants no approval, including when Java produced a successful report.
Runtime validation passes -no-http-access, -tx n/a and -disable-default-resource-fetcher to the pinned engine. This is an application-level HTTP prohibition, not an operating-system network firewall. Operators can add process/container egress controls. Validation never downloads dependencies to resolve a missing package. The independent local terminology checker does not make network calls.
An existing verified installation can create a portable local cache bundle:
npm run package:validator-cache
This produces .demo/fhir-validator/dependency-cache.tgz and the bundle checksum. It verifies the cache before and after packaging. The bundle is a local artifact, approximately 110 MiB; it is not published here. Rebuilding a compressed bundle can change its archive hash; distribute the matching checksum with that artifact. The per-package content lock remains the authoritative dependency identity. Updating the lock is an explicit maintainer review of dependency sources and licenses, never a runtime operation.
OPRP_VALIDATOR_WORKSPACE can select another installation directory. On a fresh machine, supply either the matching bundle or a directory copied from a verified installation:
OPRP_VALIDATOR_CACHE_BUNDLE=/operator/downloads/dependency-cache.tgz npm run setup:validator
# Alternative: OPRP_VALIDATOR_CACHE_DIRECTORY=/operator/copy/packages npm run setup:validator
npm run validator:cache-conformance
npm run clinical:conformance
Setup verifies the bundle hash before extraction, verifies all extracted tree contents in a staging directory, and installs the directory only after success. Directory input is verified before copying and again after copying. Existing mismatched caches fail rather than being replaced. Setup may download the two hash-pinned official tool artifacts explicitly; no clinical resources are involved. Java must already be installed; the tested runtime is Java 21.0.11. Reproduction currently needs access to a matching cache copy; no public Convey download endpoint is deployed.
Finite local terminology
The catalogue includes complete R4 status code systems, their selected value sets, and the complete pinned HL7 ActCode system for encounter class ancestry. Original source resources retain their metadata and copyright notices. A separate, clearly marked fictional clinical code system exists for synthetic tests. FHIR R4 CodeSystem, FHIR R4 ValueSet.
The local checker verifies coding-system membership, supplied versions, primary coded clinical concepts, and selected resource-field value-set bindings. It supports complete case-sensitive systems, explicit include/exclude concepts, and concept is-a filters using nested ancestry and subsumedBy relationships. Unsupported value-set composition features and incomplete/case-insensitive code systems fail at startup. Every Coding encountered is checked, including security labels and coding-valued extensions; operators must provide catalogues for those systems too. It does not infer a code from free text, validate clinical appropriateness or require matching display wording.
Local mode fails an unavailable system/version with terminology-unavailable, unknown code with code-invalid, missing primary coding with coded-concept-required, and a failed configured field binding with value-set-invalid. Remote terminology stays disabled. Java supplies independent structural/profile validation; the additional local checker supplies the finite terminology verdict. terminology: local must not be interpreted as complete US Core terminology conformance.
LOINC, SNOMED CT, RxNorm, UCUM and broader clinical vocabulary coverage are not supplied. Hosts using those systems must load appropriately licensed, complete local catalogues with reviewed bindings before enabling this strict mode for that content. Tests using fictional codes do not establish real clinical terminology coverage. The optional structural mode remains explicitly advertised as terminology: disabled; it makes no terminology-validation claim. Catalogue bytes and bindings participate in validation identity, and changes during a run reject its result.
Host intake and durable work
import { ExternalFhirValidator, DurableValidationAdmission } from '@convey/oprp-sdk';
const validator = new ExternalFhirValidator({
jar: '/host/tools/validator_cli.jar',
workspace: '/host/private/validator', // Contains verified home/.fhir/packages.
profiles: '/host/profiles',
usCorePackage: '/host/tools/us-core-9.0.0.tgz',
terminology: '/host/profiles/terminology',
});
const admission = new DurableValidationAdmission({
path: '/host/private/validation-jobs.sqlite',
owner: JSON.stringify(host.identity.public), // Stable Host identity/key binding.
validator,
clock: hostClock,
target: 'oprp',
lifetime: 300,
});
host.clinical.admission = admission;
// Trusted worker: invoke repeatedly using the operator's job scheduler.
await admission.runOne();
const jobs = admission.jobs(); // Operator-only metadata, not a public endpoint.
owner must bind the same Host and public-key identity across ordinary restarts. This database is separate from the Host clinical database. An authenticated, authorized create/update checks ownership, resource shape, visibility, labels and prior receipts first. Missing validation stores a digest-bound job in that independent database, then fails external_validation_required; the clinical transaction rolls back. The existing requester queue keeps the operation pending in order. After worker acceptance, the same immutable intent can be retried with a new encrypted envelope. Validation never commits a clinical write on the requester's behalf.
Approvals bind the configured engine/toolchain, full dependency lock, profiles, terminology catalogue, target, patient and exact resource digest. The digest excludes only Host-generated revision/time metadata. A changed ID, label, ownership, clinical field or other content needs new approval. Successful admission lasts one second through one hour (five minutes by default), persists across worker/Host restart and is rechecked inside the ordinary mutation transaction. Current permission, authority, visibility, versions and receipt checks still apply. A committed duplicate receipt remains usable after approval expiry. Deletes need no new clinical representation.
Engine/configuration changes create a different validation binding on reconstruction; old approvals/jobs cannot authorize the new configuration. Deploy catalogue changes immutably and reconstruct the validator/admission helper. Source Host reports and requester-submitted reports are never destination approval authority. Local files and databases remain a trusted operator boundary; hashing does not authenticate a maliciously replaced lock together with its dependencies.
Job lifecycle and recovery
| State | Meaning and next action |
|---|---|
queued |
Await eligible worker/backoff time. |
running |
Worker holds a durable lease/token; expired leases can be reclaimed. |
accepted |
All resources passed; retry the normal authorized operation. |
rejected |
At least one clinical report failed; review and correct the intent explicitly. |
failed |
Unsupported validation target, or tool failures/lost worker leases exhausted attempts; trusted operator repairs and calls retry(id). |
Claims use a short SQLite BEGIN IMMEDIATE transaction; Java executes outside the lock. Independent worker connections coordinate using the same jobs database. Only the still-current lease token may install results, and completion after lease expiry installs no approval. Defaults are a 240-second lease and the validator's 180-second process deadline. Configure leases above the actual maximum worker runtime. Same-instance workers must finish before closing the database.
Tool failure retries use bounded exponential delay (maximum 300 seconds), with five attempts by default and a configurable maximum of ten. Unsupported target/mapping errors stop immediately with their explicit code and require a configuration/workflow decision. The worker validates result count, digest, target, engine, profiles, terminology mode and issue/acceptance consistency before saving admission. Tool errors expose bounded error codes; diagnostic reports stay in the private database for authorized operator inspection. The worker does not log raw clinical payloads. Operators control polling, backoff scheduling and alerts; no background automation is installed by this library.
Clinical rejection produces external_validation_rejected on normal retry. All three SDK queues treat it as a terminal rejected entry and stop subsequent ordered writes. Pending validation and a full validation queue remain retryable. Rejection/approval cache entries expire; an identical rejected resource can be reassessed after expiry, never accepted without another successful verdict. retry(id) resets a failed job's attempts without changing intent; it does not approve rejected clinical content.
Default capacity is 4,096 retained jobs, configurable up to 10,000; a batch holds at most 32 resources and 32 MiB. Archive preparation is limited to 4,096 representations and schedules atomically within the quota. prune() removes expired admission rows and terminal jobs once their patient/binding no longer has live cached admission. Failed/stale-configuration jobs remain available for operator review and require operator retention management. SQLite files, temporary validation inputs, diagnostics and local SDK caches need Host-chosen encryption/storage controls; the protocol does not prescribe an at-rest cipher.
Restore and migration
Archive verification precedes validation admission. Destination preparation schedules every missing authoritative resource and retained clinical version in deterministic batches, deduplicating representations that differ only in Host revision/time metadata. A partial batch may cache successful resources but never makes the whole archive ready while another version is rejected or pending. A restore commits no partial Patient/resources/history/receipts.
Final migration staging cannot issue a ready proof before every representation has fresh destination approval. Restart the destination worker with the same job database, identity and validator configuration to resume. For large archives, configure enough worker capacity and a bounded approval lifetime to finish all batches before earlier approvals expire; readiness always waits for a fully fresh destination verdict. After acceptance, retry staging, obtain ready/release proofs and activate normally. Activation rechecks every representation again. If approval expires after source release, the destination remains staged and the source stays fenced; run the destination worker and retry activation. Validation alone never switches discovery or grants requester access at the destination.
The real-engine conformance workflow exercises offline base/US profile checks, locally coded examples and terminology rejection, encrypted intake through a worker restart, and migration readiness through a destination worker restart. Recorded evidence. Job tests additionally cover dependency tampering, unknown terminology, ownership/configuration binding, leases, stale completion, retries, quotas, ordered rejection and atomic restore.
Next: build deployable Host operations: replaceable persistence/storage interfaces, a reproducible two-Host installation, and operator health/recovery tooling. Broader licensed terminology/profile coverage and independent security review remain release gates.