Skip to content

Instantly share code, notes, and snippets.

@niquola
Last active February 20, 2025 15:56
Show Gist options
  • Save niquola/6dfde8e4ea0bbec1fecd455b17f538e8 to your computer and use it in GitHub Desktop.
Save niquola/6dfde8e4ea0bbec1fecd455b17f538e8 to your computer and use it in GitHub Desktop.
Semver based Canonical Resolution and Package management

FHIR Canonical Resources and Packages (AI Draft)

1. Introduction

This specification defines how FHIR canonical resources and packages are:

  1. Authored (i.e., created and maintained in Implementation Guides (IGs))
  2. Assembled (Configured) into final systems by users
  3. Executed (Runtime) to provide functionality

It focuses on versioning, dependency resolution, canonical references, and package management to ensure consistency, maintainability, and clarity in the ecosystem of FHIR artifacts.


2. Key Concepts and Terminology

  1. Canonical Resource
    A FHIR resource with a url (canonical URL) and version element. Examples: StructureDefinition, ValueSet, CodeSystem, ConceptMap.

  2. Canonical URL
    The unique, resolvable (or at least logically unique) URI for a resource; typically used as a global identifier.

  3. Versioning

    • Exact version: MAJOR.MINOR.PATCH (e.g., 3.1.0).
    • Partial/Range version: A constraint allowing multiple versions, e.g., 3.1.*, 3.*, or none (*).
  4. Packages
    Bundles of FHIR resources that follow Semantic Versioning (semver). Each package may declare dependencies on other packages.

  5. Phases

    • Authoring: Creating IGs and resources.
    • Configuration (Assembling): Combining multiple packages or unpackaged resources into a final system.
    • Runtime: Executing or interpreting resources (e.g., validating data, expanding terminologies).
  6. End User / Administrator
    The actor responsible for assembling final systems and resolving conflicts (e.g., version clashes, overlapping definitions).


3. Lifecycle Phases

3.1 Authoring

Purpose

  • Develop reusable FHIR Implementation Guides (IGs).
  • Ensure references and version constraints are clear and compatible.

Scope

  • Authors produce resources that reference each other and external dependencies.
  • Authors decide how strictly or loosely to version dependencies.

Primary Tasks

  1. Create or Update Canonical Resources: Define or refine StructureDefinition, ValueSet, etc.
  2. Declare Dependencies: Reference external resources or packages, specifying version constraints.
  3. Ensure Compatibility: Strive for backward/forward compatibility via semver.
  4. Release: Publish a new package version with properly incremented MAJOR.MINOR.PATCH.

Outputs

  • A package (or IG) containing canonical resources, plus a manifest of dependencies and recommended version constraints.

3.2 Configuration (Assembling)

Purpose

  • Combine multiple packages and possibly unpackaged resources into a coherent final system.

Scope

  • The user or administrator decides which package versions to include, resolves any conflicts, and overrides any references as needed.

Primary Tasks

  1. Collect Packages/Resources: Gather all dependencies, possibly from various repositories.
  2. Resolve Versions: Apply version constraints (exact, partial, unversioned) to obtain a consistent set of canonical resources.
  3. Handle Conflicts: Detect duplicates, select a preferred version, or override references if necessary.
  4. Produce Final Artifact: A single, consistent environment (could be a folder, server, or knowledge base) with resolved resources.

Outputs

  • Final set of canonical resources with fully resolved versions and references.
  • Configuration logs or metadata describing how conflicts and overrides were resolved.

3.3 Runtime

Purpose

  • Execute or interpret the assembled FHIR canonicals for validation, terminology expansion, data exchange, etc.

Scope

  • Canonical resources are accessible in a global namespace.
  • Runtime systems typically do not need to know about packages—only canonical URLs and resolved versions.

Primary Tasks

  1. Interpret Canonical Resources: e.g., validate incoming data against StructureDefinition.
  2. Reference Resolution: If a resource references <URL>|x.y.z, the runtime uses that specific version or, if unversioned, a “latest” or configured version.
  3. Optional Dynamic Re-configuration: Some systems may allow loading or switching versions on the fly.

Outputs

  • Executed or interpreted behavior (e.g., validation results, expansions).
  • Potential log messages indicating unresolvable references (if a resource is missing).

4. Canonical Resources: References and Versions

4.1 Reference Types

  1. Exact Version Reference: <url>|3.1.0

    • Locks to a specific version.
    • Changes require explicit upgrade by the user.
  2. Partial Version Reference: <url>|3.1.*, <url>|3.*, or <url>|*

    • Allows a version range.
    • System resolves to the “latest” matching version.
  3. Unversioned (Implied |*): <url>

    • Commonly used to reference general resources (e.g., external code systems like SNOMED).
    • System picks the latest available version.

4.2 Author’s Intent

  • Strict: For critical dependencies, authors may choose exact versions to avoid unexpected changes.
  • Flexible: For code systems or minor references, authors can use partial/unversioned references for up-to-date expansions.

4.3 DRY Authoring Technique

  • Placeholders:
    "reference": "http://example.org/fhir/StructureDefinition/Patient|@"  
    
    • A build tool replaces |@ with a chosen semver range (e.g., |1.2.*) at build time.

5. Package Concept

5.1 Definition

A package is a named library of FHIR resources with a package.json-like manifest. It includes:

  • Name
  • Version (MAJOR.MINOR.PATCH)
  • Dependencies (with version ranges)
  • List of Canonical Resources

5.2 Usage

  • Authoring: IG authors publish or update packages when new or revised resources are available.
  • Configuration: Users fetch or install packages, resolving dependencies.
  • Runtime: Packages effectively “disappear.” Only resolved resources remain relevant.

5.3 Package Dependencies

Resolution: The system fetches the highest available version that fits a partial or wildcard constraint; exact references remain pinned.


6. Duplicates, Warnings, and Conflict Resolution

6.1 Duplicate Canonicals

Problem: Two or more packages may contain the same canonical URL and version, or slightly different versions of the same resource.

Recommendations:

  1. Single Authoritative Source: Ideally, one canonical belongs to exactly one package.
  2. Automatic Comparison: Systems can compare duplicates:
    • If they are identical, keep one copy.
    • If they differ, warn the user to select which version is “preferred.”
  3. User Override: If conflict remains unresolved, the final user decision prevails.

6.2 Outgoing References Not Found

Scenario: A package references a canonical that is not in its declared dependencies.

  • Warn: The build or configuration tool notifies the author/user.
  • User Action: They may add or fix a dependency, or override the missing reference with a known local resource.

6.3 Handling Breaking Changes

Definition: Breaking changes occur when a canonical resource changes in an incompatible way (e.g., removing required elements, constraints, or entire resources).

  • Recommendation: Increment the MAJOR version per semver rules.
  • Configuration Tools: Must alert users that a major upgrade may break existing references.

7. Semantic Versioning

Overview: Semver uses MAJOR.MINOR.PATCH:

  • MAJOR: Incompatible or breaking changes (or new resources that break older assumptions).
  • MINOR: Backward-compatible additions (e.g., new extensions or resources).
  • PATCH: Backward-compatible bug fixes or minor enhancements.

Example progression: 1.0.0 → 1.0.1 → 1.1.0 → 2.0.0

7.1 Pre-release and Build Metadata

Optionally, authors may add:

  • Pre-release: 1.2.3-alpha
  • Build Metadata: 1.2.3+20230201

Use these to indicate unstable releases or internal builds.


8. User Overrides and Final Authority

8.1 Why Overrides Matter

  • Package authors cannot anticipate all contexts.
  • Administrators may have system-wide constraints or specialized local resources.

8.2 Override Mechanisms

  1. Package Version Override: Force a newer (or older) version if needed.
  2. Canonical Resource Override: Replace a resource from the package with a local variant (a “monkey patch”).
  3. Reference Override: Change a reference from |3.1.* to a pinned |3.1.2 or vice versa.

8.3 Tracking and Documentation

  • Systems should log overrides to allow transparency and repeatable builds.
  • Tools should warn administrators to revisit overrides at each package update.

9. Best Practices and Recommendations

  1. Consistent Use of Semver

    • Increment MAJOR for truly breaking changes.
    • Keep patch increments for smaller fixes.
  2. Clear Reference Types

    • Use partial versions only when it is safe to retrieve the “latest.”
    • Use pinned versions for critical dependencies.
  3. Avoid Duplicate Canonicals

    • Maintain a single canonical resource in one package.
    • If multiple packages provide the same resource, unify them or coordinate with authors.
  4. Validate References Early

    • Automated tools to check that all referenced canonicals exist in the declared dependencies.
  5. Leverage Tooling

    • Use build tools (e.g., FHIR publisher, custom scripts) to inject version placeholders, detect duplicates, and generate warnings.
  6. User-Centric Conflict Resolution

    • Provide an interface or script to let users pick the “preferred” version in a conflict.
    • Log final decisions.

10. Example Implementation Outline

10.1 Authoring Example

  1. Initialize Package: [email protected]
  2. Declare Dependencies:
    {
      "dependencies": {
        "fhir.core.r4": "4.0.*",
        "hl7.fhir.us.core": "3.*"
      }
    }
  3. Create Resources: StructureDefinition/my-pediatrics-patient, referencing:
    • http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|*
  4. Release: Publish 1.0.0.

10.2 Configuration Example

  1. Fetch Packages:
  2. Detect Conflicts: Suppose [email protected] also references [email protected].
  3. Resolve: The system tries to pick [email protected] if it satisfies 3.*.
  4. User Override (Optional): If [email protected] specifically pinned 3.1.0, user must decide to:
    • Force 3.1.0, or
    • Switch to 3.2.0.
  5. Deploy: The final set includes:

10.3 Runtime Example

  1. Load: The final system has a global store of canonical resources from above.
  2. Evaluate: A new Patient instance is validated against my-pediatrics-patient → which references us-core-patient|3.2.0 (latest in store).
  3. Dynamic Update: Administrator decides to bring in [email protected] next month:
    • The system sees partial references 3.* and automatically updates references to 3.3.0 if approved.

11. Conclusion

This specification outlines a unified approach to authoring, configuring, and running FHIR canonical resources and packages. By consistently applying semantic versioning, carefully managing dependencies and references, and enabling the end user to resolve conflicts, the FHIR ecosystem can maintain compatibility, reuse, and clarity over time.


Appendix: Quick Reference

  • Major Goals: Avoid version conflicts, duplicates, and silent breakages.
  • Core Principle: The final user has ultimate control (overrides) while authors provide best-practice constraints.
  • Semver: Strictly followed for stable and predictable upgrades.
  • Tools: Rely on build/configuration systems to detect warnings and manage references automatically.

End of Specification

Canonical Resolution and Packages (DRAFT Nikolai)

It's important to distinguish following phases:

  • authoring - authoring on reusable library (IG), which may depend on other libraries (packages)
  • configuration - configure final system from multiple packages and unpackaged canonicals
  • runtime - evaluate canonicals - validations, terminology expansion, etc.

Authoring

Authoring is the process of creating a reusable library (IG), which may depend on other libraries (packages). Authors are focused on describing specific domain, and making canonicals reusable and composable. Authors are concerned to make releases backward and forward compatible. Authors manages IG dependencies, ideally making compatible dependency updates simpler.

Configuration (Assembling)

Configuration is the process of configuring final system from multiple packages and unpackaged canonicals. It's usually done by "users" or "administrators". Users should be able to get consistent and working up-to-date system. Users have the most complete context of the system. In ideal case configuration is built from compatible and composable packages with minimal effort. But it is the user who will resolve conflicts if any and make final decision.

Runtime

Runtime is executing and evaluating configuration. There is no strict borderline between configuration and runtime, because system may allow dynamic re-configuration. If all resolutions are done at configuration time, runtime does not need to know about packages and may work with global namespace of canonicals with unique URLs and versions.

The main unit of runtime is canonical resource. Runtime interprets canonical resources and evaluates them. Resources may reference each other by URL and optional version, if version is omitted, runtime should use latest version.

:::info

In "well-behaved" world there should be tendency to use latest compatible versions - i.e. in case of semver address resources by MAJOR version or without version at all.

:::

Packages

Packages are used at authoring and configuration time. Packages essentially are named libraries with dependencies. Packages disappear at runtime. Runtime can keep tracking source package of canonical, but this information is not essential for resolution. Resolution happens at configuration time.

:::info

Finally it should be up to authors and users to decide how to manage references and dependencies. Authors and users know their context and can make better decisions.

At the top level we can provide tools - strictly versioned references for canonicals and deps and soft references with partial versions plus resolution rules and let the authors and users choose what they want.

:::

FHIR Canonical Resources have a canonical URL and version. The recommended version algorithm is Semantic Versioning. Canonical resources may reference other canonical resources using canonical URLs.

There are two types of canonical references:

  • versioned references: <URL>|3.1.0 - references a very specific version of dependency
  • partially versioned references: <URL>|3.1.* or <URL>|3.* or <URL>|* - references up to minor or patch version. We may consider <URL> reference as <URL>|* - i.e. just latest version.

:::warning

There is some mess in current understanding of reference types, because authors of IGs use unversioned references meaning that package deps will be used to pin the version. But at the same time it will blur the difference with real unversioned references - like LOINC or SNOMED CodeSystems.

Implicit usage of references types will help to avoid this mess!

:::

:::info

Type of reference should be explicitly chosen by the author of the resource! For example, for external CodeSystems it worth to use unversioned references to get the latest version of the code system. But for base of profile you probably want to use minor or major version. At a very specific case author may choose to use versioned references, to guarantee that the reference will never change.

:::

For DRY authoring we can provide special placeholder for references - so the build tool will put package version expression into canonical reference: http://..../Patient|@ -> {deps: {'package': "1.2.*'}} -> http://..../Patient|1.2.*

Based on semver assumptions and explicit reference types, there are following resolution rules:

  1. If reference is versioned, it should be resolved to the exact version.
  2. If reference is partial, it should be resolved to the "latest" version that matches the partial version.

:::note

"latest" mean latest version available in the system at configuration or startup time. It is expected that during configuration time the system will download the latest version of the dependency from repository.

:::

Package management

FHIR package is a collection of canonical resources and dependencies. Package versions follow semantic versioning. That means that packages with different patch version can be upgraded to latest without risk of breaking changes.

For Minor versions there are could be some non-obvious breaking changes, but similar assumption can be done, if authors culture and tools will be able to provide some guarantees.

There are few rules for versioning packages:

  • If there is at least one breaking change in canonical resource or dependency, MAJOR version should be increased.
  • If there is any addition in canonical resource or dependency, MINOR version should be increased.
  • If there is any fix in canonical resource or dependency, PATCH version should be increased.

:::note

What is considered as breaking change?

  • Removal or renaming of canonical resource
  • Removal or renaming of extension in profile
  • Addition of new invariant, required binding
  • More strict constraints on cardinality, value set or in invariants.

What is considered as addition?

  • addition of new canonical resource
  • addition of new extension in profile

:::

Package authors may choose the resolution rules for package dependencies:

Based on author's choice the package system will resolve the dependencies accordingly. If partial versions are used, system may resolve packages to the latest version.

:::note

There is a philosophical question, should end user be able to override 'author's' dependency resolution rules?

Most of programming languages allow to do this! For example npm overrides

:::

Outgoing canonical references in package

It's recommended to check that all outgoing canonical references are resolvable in direct dependencies of the package, especially versioned references. If not, this is considered as a warning and should be reported to author and user.

Canonical duplicates

It's considered a good practice to host one canonical in one specific package. Because if canonical is copied in many packages, there is a risk to have different instances of canonical in the same system. Multiple instances of canonical may lead to inconsistency and unexpected behavior. Such situation is considered as a warning and should be reported to the user and authors of packages.

Smart systems can try to compare duplicates and if they are "equal" - keep only one instance. Tools and linters should help authors to avoid duplicates in packages.

Possible resolution algorithms:

  • Pick the nearest canonical from the dependency tree (java style). This approach allows user to do monkey patching of the canonical.
  • Let the user configure resolution rules for conflicts.
  • Use all of them. For example validate resource against all of them and produce a warning if they are different.

Semantic Versioning

Semantic Versioning (semver) is a versioning scheme using three numbers: MAJOR.MINOR.PATCH (e.g., 2.1.0).

  • MAJOR version increases when making incompatible API changes,
  • MINOR version increases when adding functionality in a backwards compatible manner
  • PATCH version increases when making backwards compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. For example, 1.0.0-alpha or 1.0.0+20130313144700. This versioning scheme helps developers understand the impact of updates and manage dependencies effectively.

User overrides

Because the user is the last source of truth, in case of conflict user should be able to override the canonical resources, references and package versions.

In Ideal World

In ideal world where canonicals and packages follow semver and care about compatibility, dependencies and references could be set on Major version level. System may load latest versions of packages and use latest up-to-date canonicals. With assumption that canonicals belongs to only one package, there will be only one instance even by URL of canonical in the system.

Semver based Canonical Resolution and Package management

Finally it should be up to authors and users to decide how to manage references and dependencies. At the top level we can provide tools - strictly versioned references for canonicals and deps and soft references with partial versions plus resolution rules and let the authors and users choose what they want.

FHIR Canonical Resources have a canonical URL and version. The recommended version algorithm is Semantic Versioning. Canonical resources may reference other canonical resources using canonical URLs.

There are two types of canonical references:

  • versioned references: <URL>|3.1.0 - references a very specific version of dependency
  • partially versioned references: <URL>|3.1.* or <URL>|3.* or <URL>|* - references up to minor or patch version. We may consider <URL> reference as <URL>|* - i.e. just latest version.

Type of reference should be explicitly chosen by the author of the resource! For example, for external CodeSystems it worth to use unversioned references to get the latest version of the code system. But for base of profile you probably want to use minor or major version. At a very specific case author may choose to use versioned references, to guarantee that the reference will never change.

For DRY authoring we can provide special placehoder for references - so the build tool will put package version expression into canonical reference: http://..../Patient|@ -> {deps: {'package': "1.2.*'}} -> http://..../Patient|1.2.*

Based on semver assumptions and explicit reference types, we can describe resolution rules:

  1. If reference is versioned, it should be resolved to the exact version.
  2. If reference is partial, it should be resolved to the "latest" version that matches the partial version.

"latest" mean latest version available in the system at configuration or startup time. It is expected that during configuration time the system will download the latest version of the dependency from repository.

Package management

FHIR package is a collection of canonical resources and dependencies. Package versions follow semantic versioning. That means that packages with different patch version can be upgraded to latest without risk of breaking changes.

For Minor versions there are could be some nonobvious breaking changes, but similar assumption can be done, if authors culture and tools will be able to provide some guarantees.

There are few rules for versioning packages:

  • If there is at least one breaking change in canonical resource or dependency, MAJOR version should be increased.
  • If there is any addition in canonical resource or dependency, MINOR version should be increased.
  • If there is any fix in canonical resource or dependency, PATCH version should be increased.

What is considered as breaking change?

  • Removal or renaming of canonical resource
  • Removal or renaming of extension in profile
  • Addition of new invariant, required binding
  • More strict constraints on cardinality, value set or in invariants.

What is considered as addition?

  • addition of new canonical resource
  • addition of new extension in profile

Package authors may choose the resolution rules for package dependencies:

Based on author's choice the package system will resolve the dependencies accordingly. If partial versions are used, system may resolve packages to the latest version.

:::note

There is a philosophical question, should end user be able to override 'author's' dependency resolution rules?

Most of programming languages allow to do this! For example npm overrides

:::

Outgoing canonical references in package

It's recomended to check that all outgoing canonical references are resolvable in direct dependencies of the package, especially versioned references. If not, this is considered as a warning and should be reported to author and user.

Canonical duplicates

It's considered a good practice to host one canonical in one specific package. Because if canonical is copied in many packages, there is a risk to have different instances of canonical in the same system. Such situation is considered as a warning and should be reported to the user.

Multiple instances of canonical may lead to inconsistency and unexpected behavior.

Possible resolution algorithms:

  • Pick the nearest canonical from the dependency tree (java style). This approach allows user to do monkey patching of the canonical.
  • Let the user configure resolution rules for conflicts.
  • Use all of them. For example validate resource against all of them and produce a warning if they are different.

Semantic Versioning

Semantic Versioning (semver) is a versioning scheme using three numbers: MAJOR.MINOR.PATCH (e.g., 2.1.0).

  • MAJOR version increases when making incompatible API changes,
  • MINOR version increases when adding functionality in a backwards compatible manner
  • PATCH version increases when making backwards compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. For example, 1.0.0-alpha or 1.0.0+20130313144700. This versioning scheme helps developers understand the impact of updates and manage dependencies effectively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment