Summary
HAIStack needs a platform-level conformance and terminology layer that mirrors the registry’s existing Postgres hybrid model: global canonical stores for heavy shared artefacts (IG StructureDefinitions, LOINC, ICD CodeSystems) with per-tenant opt-in overlays (ValueSets, enablement, bindings). Today, IG/terminology installation is CLI-only, synchronous, not hot-reloaded, and terminology is fully duplicated per tenant.
This issue tracks the runtime/admin API, async install pipeline, global-vs-tenant scoping, remote terminology, and FHIR terminology HTTP surface.
Problem / gap
Current state (as of 2026-03)
| Capability |
Status |
Notes |
| Install IG at runtime |
⚠️ Partial |
haistack module install only; local pre-compiled JSON; blocks synchronously |
| Upload IG over HTTP |
❌ |
No admin/FHIR endpoint |
Install from packages.fhir.org |
❌ |
No NPM/package registry client |
FHIR $npm / Package registry install |
❌ |
Not implemented |
| HTTP/admin API for package install |
❌ |
Module lifecycle is CLI-only |
| Hot-reload validation profile catalog |
❌ |
validate.Engine + RegistryProfileCatalog built once at startup (pkg/runtime/wire.go) |
POST StructureDefinition via FHIR API → updates validation catalog |
❌ |
Stored as ordinary resource only; does not register profiles |
| Global terminology catalog |
❌ |
All terminology is scope_id-scoped (tenant ID on Postgres) |
| Tenant terminology opt-in / overlay |
❌ |
No subscription model; LOINC/ICD would be duplicated per tenant |
Remote terminology server (tx.fhir.org, etc.) |
❌ |
terminology.Chain exists but is not wired in runtime |
HTTP $lookup / $expand / $validate-code |
❌ |
Internal terminology.LocalService only |
| Async IG/terminology install |
❌ |
Module install is synchronous; no job integration |
Architectural mismatch
Registry (Postgres) already does hybrid scoping correctly:
postgres.DB.DefinitionStore() → global canonical FHIR definition JSON
postgres.TenantDB.RegistryInstallStore() → per-tenant enablement overlay
Terminology does not:
scope_id + canonical_url + version → fully tenant-scoped; no shared layer
Desired model for IGs + terminology:
┌──────────────────────────────────────────────────────────────┐
│ Global conformance catalog (platform, read-mostly) │
│ • IG StructureDefinitions, SearchParameters, OperationDefs │
│ • LOINC / ICD / SNOMED CodeSystems (canonical + projections)│
│ • Installed once; shared across tenants │
└──────────────────────────────────────────────────────────────┘
▲
│ fallback / compose
┌─────────────────────────┴────────────────────────────────────┐
│ Per-tenant terminology & conformance overlay │
│ • ValueSets (compose from global CodeSystems) │
│ • Tenant-specific CodeSystems / ConceptMaps (optional) │
│ • Opt-in subscriptions: which global packs are enabled │
│ • Profile/terminology bindings active for this tenant │
└──────────────────────────────────────────────────────────────┘
Rationale: IGs, LOINC, and ICD are large, versioned, and identical across tenants. ValueSets express which codes matter for this tenant’s workflows and should remain tenant-scoped. This avoids storing the same LOINC expansion 50× while still letting tenants differ on bindings.
Runtime install gaps
- Module install persists to DB but a running HTTP server does not hot-reload
RegistryProfileCatalog or validate.Engine.
- No way to trigger install from HTTP, Package Registry, or FHIR
$npm.
- Install is synchronous — large IGs/terminology packs should run as durable jobs (
pkg/jobs) with progress, retry, and failure reporting.
Proposed scope
Phase 1 — Global catalog + tenant overlay (data model)
Phase 2 — Async install pipeline (jobs)
Phase 3 — HTTP surfaces
Phase 4 — Remote terminology provider
Acceptance criteria
Out of scope (follow-up issues)
- Public publication to
packages.fhir.org (we consume, not publish)
- Full Touchstone/Inferno server certification
- R5/R6 terminology or IG support
- Per-tenant custom LOINC subsets (could be a ValueSet-only concern)
Affected packages / files
| Area |
Path |
| Runtime wiring |
pkg/runtime/wire.go, pkg/runtime/builder.go |
| Registry hybrid model |
pkg/registry/, pkg/postgres/definition_store.go, pkg/postgres/registry_install_store.go |
| Terminology |
pkg/terminology/, pkg/postgres/terminology_store.go, pkg/sqlite/terminology_store.go |
| Module installer |
pkg/modules/ |
| Jobs |
pkg/jobs/ |
| HTTP |
pkg/http/ (new admin + terminology operation routes) |
| Validation |
pkg/validate/profile_catalog_registry.go |
| Core writes |
pkg/core/service.go (compileTerminology) |
| CLI (retain) |
cmd/haistack/command/module.go |
Design notes
Align with existing registry hybrid
Reuse the proven pattern:
| Layer |
Global |
Per-tenant |
| StructureDefinitions (IG) |
DefinitionStore |
RegistryInstallStore enablement |
| CodeSystems (LOINC, ICD) |
new global terminology catalog |
— |
| ValueSets |
— |
tenant terminology overlay |
| Profile validation |
global canonical lookup |
tenant enablement + bindings |
Job-based install (not synchronous)
Large terminology packs (LOINC ~100MB+) and IG compiles must not block HTTP handlers. Use pkg/jobs with:
- progress reporting (
installed N/M definitions)
- retry with backoff on transient fetch failures
- compensating rollback on partial failure (existing
modules.Installer pattern)
Hot-reload contract
Define a ConformanceRefresher interface called after install job success:
type ConformanceRefresher interface {
Refresh(ctx context.Context) error // rebuild snapshot, warm catalog, invalidate terminology cache
}
Inject into job handler and optionally expose POST /admin/conformance/refresh for manual trigger.
References
Context from recent PR work
Both reinforce the need for hot-reload after runtime IG install — today a restart is required for new StructureDefinitions to affect validation.
Summary
HAIStack needs a platform-level conformance and terminology layer that mirrors the registry’s existing Postgres hybrid model: global canonical stores for heavy shared artefacts (IG StructureDefinitions, LOINC, ICD CodeSystems) with per-tenant opt-in overlays (ValueSets, enablement, bindings). Today, IG/terminology installation is CLI-only, synchronous, not hot-reloaded, and terminology is fully duplicated per tenant.
This issue tracks the runtime/admin API, async install pipeline, global-vs-tenant scoping, remote terminology, and FHIR terminology HTTP surface.
Problem / gap
Current state (as of 2026-03)
haistack module installonly; local pre-compiled JSON; blocks synchronouslypackages.fhir.org$npm/ Package registry installvalidate.Engine+RegistryProfileCatalogbuilt once at startup (pkg/runtime/wire.go)StructureDefinitionvia FHIR API → updates validation catalogscope_id-scoped (tenant ID on Postgres)tx.fhir.org, etc.)terminology.Chainexists but is not wired in runtime$lookup/$expand/$validate-codeterminology.LocalServiceonlyArchitectural mismatch
Registry (Postgres) already does hybrid scoping correctly:
Terminology does not:
Desired model for IGs + terminology:
Rationale: IGs, LOINC, and ICD are large, versioned, and identical across tenants. ValueSets express which codes matter for this tenant’s workflows and should remain tenant-scoped. This avoids storing the same LOINC expansion 50× while still letting tenants differ on bindings.
Runtime install gaps
RegistryProfileCatalogorvalidate.Engine.$npm.pkg/jobs) with progress, retry, and failure reporting.Proposed scope
Phase 1 — Global catalog + tenant overlay (data model)
postgres.DB(parallel to globalDefinitionStore):CodeSystemcanonical JSON + compiled projections (LOINC, ICD, HL7 base)DefinitionStore(already exists)postgres.TenantDB:ValueSetresources (compose/include from global CodeSystems)terminology.Chainin runtime:Phase 2 — Async install pipeline (jobs)
ig.install,terminology.install,package.fetchpackages.fhir.org)POST /admin/packages/install(returns job ID)Phase 3 — HTTP surfaces
multipart/form-data)packages.fhir.orgpackage name+version$npmcompatibility layerregistry.Manager.RebuildSnapshot()RegistryProfileCatalog.Warm()or replace catalog onvalidate.EngineCapabilityStatementenabled types/operationsCodeSystem/$lookupValueSet/$expandCodeSystem/$validate-code/ValueSet/$validate-codeStructureDefinitionoptionally registers into global or tenant catalog (not just resource store)Phase 4 — Remote terminology provider
terminology.RemoteProvideradapter for FHIR tx servers (tx.fhir.org, on-prem Snowstorm, etc.)force-system-versionsemanticsAcceptance criteria
$validateand write-path validation without restartpackages.fhir.org(at least one pilot package) demonstrated end-to-endterminology.Chainis wired inpkg/runtimewith tenant → global → remote precedence$lookup/$expand/$validate-codeexposed for enabled tenantsOut of scope (follow-up issues)
packages.fhir.org(we consume, not publish)Affected packages / files
pkg/runtime/wire.go,pkg/runtime/builder.gopkg/registry/,pkg/postgres/definition_store.go,pkg/postgres/registry_install_store.gopkg/terminology/,pkg/postgres/terminology_store.go,pkg/sqlite/terminology_store.gopkg/modules/pkg/jobs/pkg/http/(new admin + terminology operation routes)pkg/validate/profile_catalog_registry.gopkg/core/service.go(compileTerminology)cmd/haistack/command/module.goDesign notes
Align with existing registry hybrid
Reuse the proven pattern:
DefinitionStoreRegistryInstallStoreenablementJob-based install (not synchronous)
Large terminology packs (LOINC ~100MB+) and IG compiles must not block HTTP handlers. Use
pkg/jobswith:installed N/M definitions)modules.Installerpattern)Hot-reload contract
Define a
ConformanceRefresherinterface called after install job success:Inject into job handler and optionally expose
POST /admin/conformance/refreshfor manual trigger.References
pkg/registry/README.mdpkg/terminology/README.mdpkg/modules/README.mdterminology.Chain:pkg/terminology/chain.goContext from recent PR work
$validate(full mode by default; depends on profile catalog loaded at startup)Both reinforce the need for hot-reload after runtime IG install — today a restart is required for new StructureDefinitions to affect validation.