Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

FerroTERM is a pure-Rust FHIR terminology server for SNOMED CT, LOINC, and other clinical code systems, SNOMED CT first. It serves the HL7 FHIR terminology API from a single binary, backed by a memory-mapped index built once per code system release. There is no JVM, no Elasticsearch, and no external database.

Note

The name: Ferro for the Rust family FerroTERM shares with FerroEHR, TERM for terminology. The site is https://ferroterm.eu.

Status: early design

This repository is in discovery. The team is scoping the design before writing product code, so this book describes intended behaviour, marked as planned where the server does not exist yet. Read a sentence in the future tense (“the server returns”, “you run”) as a design commitment, not a claim that you can run it today. The design authority, with citations, is docs/architecture.md in the repository.

Who each part of this book is for

The book is organized by what you want to do.

  • Evaluate is for anyone deciding whether FerroTERM fits. It covers what the server is, why it exists, the architecture at a glance, and how it compares to Snowstorm, Ontoserver, and Hermes.
  • Operate is for operators and deployers. It covers installing and running the server, configuration, loading a licensed SNOMED CT edition, hardware sizing, and verifying a release before you run it.
  • Integrate is for API consumers. It covers the FHIR terminology operations, implicit value sets and ECL, the supported FHIR versions, and worked request and response examples.
  • Contribute is for contributors. It covers building and testing, the FHIR code generation model, and where the deeper design lives.

Two things that are always true

You bring your own code system content. The software is open source under the MIT license. SNOMED CT is licensed by SNOMED International, LOINC by the Regenstrief Institute, and this repository ships no code system content. You load a licensed release, and the server serves it. See Loading a SNOMED CT edition.

What FerroTERM is

Architecture at a glance

This page is a short tour of the design. The full design authority, with citations to the terminology-server and graph-reachability literature, is docs/architecture.md in the repository.

SNOMED CT is two things at once

SNOMED CT is a formal ontology in the OWL 2 EL profile, and it is a polyhierarchical graph. The is-a hierarchy a client queries is the inferred hierarchy, produced by a description-logic reasoner, not the stated one.

That fact splits the system into two problems that have different engineering answers:

graph TD
    R["SNOMED CT RF2 release"] --> OFF["Offline: classification, once per release"]
    OFF --> IDX["Materialized index: CSR adjacency + roaring closure + text index"]
    IDX --> ON["Online: serving, from precomputed structures"]
    ON --> API["FHIR terminology API (R4 / R4B / R5 / R6)"]
  • Offline: classification, once per release. The hierarchy FerroTERM serves is a reasoner output. FerroTERM computes the transitive closure from the shipped inferred relationship file once per release and persists it. It never re-derives inference at query time.
  • Online: serving, from precomputed structures. Each operation is answered from the structure shaped for it: a subsumption test is a bitmap membership test, an ECL descendant set is a bitmap returned directly, and a point read is a lookup in a columnar store.

Index-materialized graph, not a graph database

SNOMED is stored as a graph, natively: integer-keyed compressed sparse-row (CSR) adjacency arrays, plus roaring bitmaps holding the transitive closure. This is a graph model with an index-materialized store.

A general-purpose graph database is rejected on the evidence. No production terminology server uses one: Ontoserver is Postgres plus Lucene, Snowstorm is Elasticsearch, and Hermes is a memory-mapped store plus Lucene. Graph databases degrade on the deep multi-hop neighborhoods that ECL descendant expansion produces, which is exactly the query a terminology server is stressed by. At SNOMED’s size the full transitive closure fits in memory and gives an O(1) subsumption test with no traversal fallback.

Persistence is a pure-Rust embedded engine

The built structures persist in redb, a pure-Rust, memory-mapped, ACID embedded key-value engine. The build tool writes these artifacts once per edition, and the server opens them read-only. redb is the storage format. At startup the server loads the transitive closure into a resident, ordinal-indexed structure so a subsumption test is a membership check against a resident bitmap, which is how the design reaches microsecond-scale subsumption in pure Rust.

FHIR support is machine-generated

HL7 publishes the whole type system and every operation as machine-readable StructureDefinition and OperationDefinition resources, in versioned packages. FerroTERM vendors and pins those packages and generates per-version Rust modules from them, so R5’s extra $expand parameters appear where the spec has them and are absent where it does not. Every generated file is marked // @generated, a drift check regenerates it in CI, and one runtime wrapper answers R4, R4B, R5, and R6 callers at once. This mirrors how the sibling project FerroEHR generates its openEHR model.

The SNOMED semantics are hand-written

RF2 loading, the materialized ontology, ECL evaluation, and $expand paging are the product, and no existing Rust project provides them. ECL is the hard part: every expression constraint returns a set of codes, so the evaluator compiles ECL to set algebra over the descendant bitmaps and the attribute adjacency, using bitmap AND and OR rather than pointer-chasing. Correctness is measured against Snowstorm as the reference server.

Text search is a separate index

Description search is its own concern, held in its own index. A word inverted index maps each word to a roaring-bitmap postings list, and an fst dictionary answers per-word prefix and fuzzy lookup. A query intersects the matching words’ postings, applies the language-reference-set and active-status filters, and sorts by matched-term length.

How it compares

FerroTERM serves the same FHIR terminology API as the established servers. The difference is the runtime footprint and the stack it needs to operate. This page sets FerroTERM beside Snowstorm, Ontoserver, and Hermes.

Note

The numbers for the other servers come from their own documentation and published work, cited in docs/architecture.md. FerroTERM’s own figures are design targets, since the server is in design.

The servers

  • Snowstorm (SNOMED International): Java on Elasticsearch. The reference server and FerroTERM’s correctness oracle. It is full-featured, and a full International edition deployment wants 16 to 32 GB of RAM plus a search cluster.
  • Snowstorm Lite (SNOMED International): drops Elasticsearch for a single Lucene index and runs the full International edition in about 500 MB. It is the closest point of comparison for footprint.
  • Ontoserver (CSIRO): Postgres plus Lucene. A production FHIR terminology server, index-materialized rather than a graph database.
  • Hermes (Mark Wardle): Clojure, a memory-mapped store plus Lucene. It serves subsumption in tens of microseconds from a materialized structure, which is the design point FerroTERM follows in pure Rust.

Side by side

ServerLanguage / runtimeStoreFull-edition memoryDeploys as
SnowstormJava (JVM)Elasticsearch16 to 32 GBJVM plus a search cluster
Snowstorm LiteJava (JVM)Luceneabout 500 MBJVM service
OntoserverJava (JVM)Postgres plus Luceneserver plus databaseJVM plus Postgres
HermesClojure (JVM)memory-mapped store plus LucenemodestJVM service
FerroTERM (planned)Rustredb, memory-mappeda few hundred MB targetsingle static binary

What FerroTERM trades

FerroTERM aims for a small footprint and a single binary, and it starts as a read-oriented server over a loaded edition. The mature servers carry features that come later in FerroTERM’s build order, such as full MRCM validation of post-coordinated expressions and closure maintenance. See what is not built yet in What FerroTERM is.

Every production server in this list converges on a materialized index rather than live graph traversal. FerroTERM takes the same shape and implements it in pure Rust with a machine-generated FHIR layer across four versions, which no existing Rust project provides.

Install and run

This page describes how you install and start the server. The distribution is in design, so treat the steps as the planned shape.

Warning

FerroTERM has no released binary or image yet. The commands below are the intended interface, not something you can run today. Follow the repository for release announcements.

Planned distribution

FerroTERM ships as a single static binary and as a container image. Both carry the whole server: there is no JVM to install, no Elasticsearch to run, and no external database to provision. You provide a built SNOMED CT index (see Loading a SNOMED CT edition) and point the server at it.

Run the binary (planned)

Download the binary for your platform and its attestation, verify the attestation (see Verifying releases), then run it:

$ ferroterm --index /path/to/ferroterm-index

The server starts, opens the index read-only, and listens for FHIR requests. You configure the listen address, the index path, and the served FHIR versions through the settings described in Configuration.

Run the container (planned)

$ docker run --rm -p 8080:8080 \
    -v /path/to/ferroterm-index:/data/index:ro \
    ghcr.io/rubentalstra/FerroTERM:<tag> \
    --index /data/index

Mount the index read-only. The server needs no writable volume for serving, because the index is built offline by a separate tool.

Check that it is serving

Once the server is up, a FHIR client can call the terminology operations. The CapabilityStatement tells a client which operations and FHIR versions this deployment serves:

$ curl http://localhost:8080/metadata

See The FHIR terminology API for the operations themselves.

Configuration

This page describes how you configure the server. The configuration surface is in design, so the setting names below are the planned shape and may change before the first release.

Note

No configuration schema is frozen yet. This page lists the settings the design calls for. Check the release notes for the exact names when a build ships.

What you configure

A deployment configures a small set of things:

  • The index path. Where the built SNOMED CT index lives. The server opens it read-only at startup.
  • The listen address and port. Where the server accepts FHIR requests.
  • The served FHIR versions. Which of R4, R4B, R5, and R6 this deployment answers. See FHIR versions.
  • Expansion limits. The default and maximum page size for ValueSet/$expand, so a broad expansion cannot return an unbounded result in one response. See Implicit value sets and ECL.
  • Logging. The log level and format for the tracing output.

How you set it

The design follows the common Rust server pattern: command-line flags for the essentials, environment variables for a container, and a file for the rest, with flags taking precedence over the environment and the environment over the file. The exact grammar is settled with the first build.

What you do not configure

You do not configure a database connection, a search cluster, or a JVM heap. FerroTERM has none of these. The one input the server needs is the built index, and you build that offline with the release-loading tool described in Loading a SNOMED CT edition.

Loading a SNOMED CT edition

FerroTERM serves the SNOMED CT edition you load. This page explains the licensing rules you must satisfy first, and the planned build step that turns an RF2 release into the index the server reads.

You bring your own content

Warning

This repository ships no SNOMED CT content, and no build of FerroTERM contains any. SNOMED CT is licensed separately by SNOMED International. You must hold a valid SNOMED CT licence for the edition you load.

The split is firm, and you must keep the two apart:

  • The software in this repository is open source under the MIT license.
  • SNOMED CT content is the property of SNOMED International and is not distributed here.

A SNOMED CT licence is free within member countries, the Netherlands among them, and available under the affiliate licence elsewhere. You obtain the RF2 release for your edition from your national release centre or from SNOMED International, under your licence, and you load it into FerroTERM yourself.

The offline build (planned)

The running server never parses RF2 and never classifies the ontology. A separate tool turns an RF2 release into the memory-mapped index once per edition, and the server opens that index read-only.

graph LR
    RF2["Licensed RF2 release<br/>(you provide)"] --> BUILD["ferroterm-build<br/>(offline)"]
    BUILD --> IDX["ferroterm-index<br/>(graph + store + text)"]
    IDX --> SRV["ferroterm<br/>(read-only)"]

The planned command takes the unpacked RF2 release and writes the index:

$ ferroterm-build --rf2 /path/to/SnomedCT_Release --out /path/to/ferroterm-index

The build computes the transitive closure from the shipped inferred relationship file, builds the CSR adjacency and the roaring closure bitmaps, writes the columnar concept and description store, and builds the text index. It runs once per release. When a new edition arrives, you rebuild the index and restart the server against it.

Note

The build tool is in design. The command name and flags above are the intended shape.

Test content

FerroTERM’s own tests use shaped, synthetic content only. They never contain real SNOMED CT concepts extracted from a release, which keeps the licence line clean in the repository itself.

Hardware sizing

FerroTERM targets a small footprint so you can run the full International edition on ordinary hardware. This page gives the design targets and where the memory goes.

Note

These are design targets grounded in the reference servers and in roaring-bitmap compression behaviour, not measurements of a shipped build. See docs/architecture.md for the reasoning.

The target

The design target is to serve the full SNOMED CT International edition in a few hundred megabytes of resident memory, so the server fits on a 2 to 4 GB box with room to spare. For comparison, SNOMED International’s Snowstorm Lite runs the same edition in about 500 MB, and the Java-plus-Elasticsearch Snowstorm wants 16 to 32 GB.

Where the memory goes

At startup the server loads the reachability closure into resident memory and leaves the rest on the memory-mapped index. The expected split:

StructureResidentRough size
Transitive closure (ancestor and descendant bitmaps)yes100 to 300 MB
CSR adjacency (is-a and per-attribute)yestens of MB
fst text dictionaryyestens of MB
Columnar concept and description storememory-mapped, paged on demandon disk

Both directions of the closure are stored on purpose: subsumption needs one direction, and ECL returns each set directly, so keeping both trades roughly 2x the closure space for direct answers. Roaring compresses SNOMED-shaped sets heavily, which is why the resident closure lands in the hundreds of megabytes rather than the gigabytes a naive bitset would need.

Disk and CPU

The index on disk is the memory-mapped redb file the build tool writes. Size it for the edition you load. The offline build is the CPU-heavy step, and it runs once per release in the build tool, not on the server. Serving is a point read or a bitmap operation, so a modest CPU handles it. A heavy $expand that materializes a large set, and any cold read that page-faults from disk, run on a blocking pool so they never stall the request runtime.

Verifying releases

Every FerroTERM release artifact carries a signed provenance attestation. Verify it before you run a binary, so you know the artifact was built by the project’s own release workflow and not tampered with.

Note

The release pipeline is stood up and activates on the first tag. Until a release exists, the recipe below is the interface you will use, not a check you can run yet. The design and rationale are in docs/ci-cd.md.

What a release carries

Each release artifact ships with:

  • An embedded dependency list, written into the binary’s .dep-v0 section by cargo auditable.
  • A CycloneDX SBOM.
  • A .sha256 checksum.
  • A keyless Sigstore provenance attestation and a signed SBOM, both bound to the artifact digest and signed by the release workflow’s own identity.

The signing is keyless through Sigstore, so there is no long-lived key to manage or leak.

Verify the provenance

Use the GitHub CLI to verify an artifact against the workflow that is allowed to sign it. Replace <tag> and <target> with the release you downloaded:

$ gh attestation verify ferroterm-<tag>-<target>.tar.gz -R rubentalstra/FerroTERM \
    --signer-workflow rubentalstra/FerroTERM/.github/workflows/release-build.yml

The --signer-workflow flag is the point of the check. It requires that the attestation was produced by that exact reusable workflow in this repository, so a signature from any other workflow or repository fails. FerroTERM builds its releases in a reusable workflow to reach SLSA Build Level 3, where the signing identity is not reachable by user build steps.

Check the checksum

Confirm the download matches its published checksum:

$ sha256sum -c ferroterm-<tag>-<target>.tar.gz.sha256

Run both checks. The checksum tells you the bytes are intact, and the attestation tells you where the bytes came from.

The FHIR terminology API

FerroTERM speaks the HL7 FHIR terminology API. If your client already talks to a FHIR terminology server, it talks to FerroTERM. This page lists the operations and what each one answers.

Note

The server is in design. The operations below are the terminology surface FerroTERM implements, and their FHIR shapes are fixed by the specification, but you cannot call a running server yet. Requests and responses are marked as planned on the Worked examples page.

The operations

OperationAnswersStructure that serves it
CodeSystem/$lookupa concept’s properties and designationscolumnar store plus adjacency
CodeSystem/$subsumesdoes concept A subsume concept Broaring-bitmap membership
CodeSystem/$validate-codeis this a valid code and displaycolumnar store
ValueSet/$validate-codeis this code a member of the value setstore plus expansion
ValueSet/$expandthe members of a value set, ECL-drivenprecomputed bitmaps and set algebra
ConceptMap/$translatethe targets a code maps tomap-refset lookup

The FHIR specification defines the parameters and the response shape for each operation. FerroTERM follows the specification for the FHIR version in the request.

How a request is shaped

Every operation is a FHIR operation invocation, called with GET for simple parameters or POST with a Parameters resource for complex input. Results come back as a Parameters resource, or the operation’s defined resource for $expand (an expanded ValueSet). Errors come back as an OperationOutcome with a diagnostic issue.

For example, $lookup takes a system and a code and returns the concept’s name, its designations, and its requested properties. $subsumes takes two codes and a system and returns whether the first subsumes the second, the reverse, is equivalent, or is unrelated.

SNOMED CT as the code system

For SNOMED CT the system is the SNOMED CT URI, http://snomed.info/sct. An edition and version are addressed with the SNOMED CT URI standard’s version form. The implicit value sets and ECL that drive $expand are covered on Implicit value sets and ECL.

Metadata

A client discovers what a deployment serves from its CapabilityStatement at /metadata, and from the TerminologyCapabilities resource. These report the FHIR versions this server answers and the terminology operations it supports.

Implicit value sets and ECL

SNOMED CT defines implicit value sets: value sets you name by a URL convention rather than by storing a ValueSet resource. FerroTERM expands them with SNOMED’s Expression Constraint Language (ECL). This page explains the URL convention and how ECL drives an expansion.

Note

ECL is the hard part of the server and its main risk, so it is built and tested as its own layer against the published ECL grammar before the value-set surface depends on it. Correctness is measured against Snowstorm.

The implicit value set convention

SNOMED CT on FHIR lets you name a value set by an ECL expression in the value set’s URL, using the fhir_vs convention:

http://snomed.info/sct?fhir_vs=ecl/<expression>

You pass that URL as the value set to ValueSet/$expand, and the server expands the ECL expression against the loaded edition. The plain ?fhir_vs (with no ecl/) names the implicit value set of all SNOMED CT concepts, and ?fhir_vs=isa/<code> names the value set of a concept and its descendants.

What ECL expresses

Every ECL expression returns a set of concepts. The core operators over the is-a hierarchy:

ECLMeaning
<< XX and all its descendants
< Xthe descendants of X, not X itself
>> XX and all its ancestors
> Xthe ancestors of X, not X itself

Refinement narrows a set by attribute values, for example < 404684003 : 363698007 = << 39057004 reads as the descendants of one concept that have a given attribute pointing into a given subtree. ECL also supports conjunction, disjunction, exclusion, attribute groups, and cardinality.

How FerroTERM evaluates it

FerroTERM compiles an ECL expression to set algebra over the precomputed structures. A << or >> set is a precomputed bitmap returned directly, a refinement is a bitmap intersection or union over the per-attribute adjacency, and conjunction and disjunction are bitmap AND and OR. There is no live graph traversal on this path, which is what keeps a descendant expansion of a high-level concept fast.

Paging a large expansion

A broad expansion can name a large set, so $expand pages its results with the FHIR count and offset parameters, bounded by the server’s configured maximum page size (see Configuration). Request a page at a time rather than an unbounded expansion.

FHIR versions

FerroTERM serves the FHIR terminology API across four versions from one running server: R4, R4B, R5, and R6. A client picks the version, and the server answers in that version’s shapes.

Why four versions from one server

HL7 publishes the whole type system and every operation as machine-readable resources, in versioned packages. FerroTERM vendors and pins those packages and generates per-version Rust modules from them, so each version’s operation surface is correct by construction. An operation parameter that R5 adds appears in the R5 module and is absent from R4B, because the generator emits what each package declares. A runtime wrapper routes a request to the module for its version, so one server answers all four callers at once.

Version status

VersionPackageStatus in FerroTERM
R4Bhl7.fhir.r4b.core 4.3.0first generation implemented
R5hl7.fhir.r5.core 5.0.0follows R4B
R4hl7.fhir.r4.core 4.0.1follows R4B
R6hl7.fhir.r6.core 6.0.0-ballotballot-tracking, follows the others

Note

R4B is the first version implemented. It is the current stable release of the R4 line and a near-superset of R4, so an R4B-first build already serves the R4-family terminology surface. R5, R4, and R6 follow as further generations. R6 tracks the ballot, with publication expected around late 2026.

What this means for you

Send your request in the FHIR version your client uses. The operation names, parameters, and response shapes are the ones the FHIR specification defines for that version, so you do not adapt your client to FerroTERM. Implementation starts with R4B, and the other versions become available as they are implemented. The CapabilityStatement at /metadata reports which versions a given deployment serves.

Worked examples

This page shows request and response shapes for the common operations. The FHIR shapes are fixed by the specification. The values are illustrative.

Warning

These examples are planned. FerroTERM has no running server yet, so you cannot send these requests today. The request and response shapes follow the FHIR terminology specification, and the concrete codes and displays are placeholders.

$lookup a concept

Read a concept’s display and properties:

GET /CodeSystem/$lookup?system=http://snomed.info/sct&code=73211009

A Parameters response carries the name, the display, and the requested properties:

{
  "resourceType": "Parameters",
  "parameter": [
    { "name": "name", "valueString": "SNOMED CT" },
    { "name": "display", "valueString": "Diabetes mellitus (disorder)" }
  ]
}

$subsumes two concepts

Test whether one concept subsumes another:

GET /CodeSystem/$subsumes?system=http://snomed.info/sct&codeA=73211009&codeB=44054006

The response reports the relationship, one of subsumes, subsumed-by, equivalent, or not-subsumed:

{
  "resourceType": "Parameters",
  "parameter": [
    { "name": "outcome", "valueCode": "subsumes" }
  ]
}

$validate-code against a value set

Check that a code is a member of a value set:

GET /ValueSet/$validate-code?url=http://snomed.info/sct?fhir_vs=isa/73211009&system=http://snomed.info/sct&code=44054006
{
  "resourceType": "Parameters",
  "parameter": [
    { "name": "result", "valueBoolean": true }
  ]
}

$expand an ECL value set

Expand an implicit value set named by ECL, one page at a time:

GET /ValueSet/$expand?url=http://snomed.info/sct?fhir_vs=ecl/<<73211009&count=20&offset=0

The response is an expanded ValueSet whose expansion.contains holds the page of members, with total reporting the full set size:

{
  "resourceType": "ValueSet",
  "expansion": {
    "total": 137,
    "offset": 0,
    "contains": [
      { "system": "http://snomed.info/sct", "code": "73211009", "display": "Diabetes mellitus (disorder)" }
    ]
  }
}

An error

An invalid or unknown input returns an OperationOutcome:

{
  "resourceType": "OperationOutcome",
  "issue": [
    { "severity": "error", "code": "code-invalid", "diagnostics": "Unknown code '00000' in system 'http://snomed.info/sct'." }
  ]
}

See The FHIR terminology API for the operation list and Implicit value sets and ECL for the ECL URL convention.

Build and test

This page is a short pointer for contributors. The deeper design and the working discipline live in the repository, and this book keeps to what you need to get building.

Read these first

  • docs/architecture.md: the design authority, with citations to the terminology-server and graph-reachability literature.
  • CONTRIBUTING.md: the contribution rules, branches, commit signing, and pull-request checklist.

The local gates

Once the Cargo workspace exists, the local gates mirror CI exactly. Run them before you open a pull request:

$ cargo fmt --all --check
$ cargo clippy --workspace --all-targets --all-features -- -D warnings
$ cargo nextest run --workspace --locked
$ cargo test --doc --workspace --locked
$ RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features --document-private-items
$ cargo deny check

Scope any command to a crate with -p <crate> while you iterate, then run the full --workspace gates before you push. Every cargo invocation uses --locked, and Cargo.lock is committed.

The workflow lanes that check shell and YAML (actionlint, zizmor, shellcheck) run on every change, including before any Rust exists. If you edit a shell script, keep it clean at shellcheck --severity=style.

The two layers

  • crates/ferroterm-fhir is generated from the vendored FHIR specs. Never hand-edit a // @generated file. See The codegen model.
  • The SNOMED engine and the server are hand-written, idiomatic Rust, with the FHIR and SNOMED specifications as the authority.

No SNOMED CT content in the repository

Tests use shaped, synthetic content only. Never commit real SNOMED CT concepts from a release, and never commit an RF2 file. See Loading a SNOMED CT edition for the licensing rule.

Correctness oracle

Terminology answers are checked against Snowstorm as the reference server over the same edition, and the ECL evaluator is tested against the published ECL grammar as its own layer before the value-set surface depends on it.

The codegen model

The FHIR layer of FerroTERM is generated, not hand-written. This page explains the model at the level a contributor needs. The mechanics live in the generator crate and its rules in the repository.

Why generate the FHIR layer

HL7 publishes the whole FHIR type system and every operation as machine-readable StructureDefinition and OperationDefinition resources, in versioned packages. Rather than transcribe those by hand for four versions, FerroTERM vendors the packages and generates per-version Rust modules from them. Each version’s operation surface is then correct by construction: a parameter that R5 adds appears in the R5 module because the R5 package declares it.

The pinned inputs

The generator reads vendored, pinned FHIR packages:

PackageVersion
hl7.fhir.r4.core4.0.1
hl7.fhir.r4b.core4.3.0
hl7.fhir.r5.core5.0.0
hl7.fhir.r6.core6.0.0-ballot
hl7.terminologyTHO

The packages are vendored verbatim under tools/ferroterm-fhir-codegen/vendor/, each with a PROVENANCE.md, and fetched by a script. You never hand-edit a vendored package. Change the fetcher and re-run it.

The rules

  • Never hand-edit a // @generated file. To change the output, change the generator (tools/ferroterm-fhir-codegen) or its override map, then regenerate.
  • The generator emits the complete model within its declared closure. A terminology server touches a small root set of resources, so the generator’s root set is the terminology surface (CodeSystem, ValueSet, ConceptMap, Parameters, OperationOutcome, CapabilityStatement, TerminologyCapabilities, Bundle, and the terminology operations), and it emits the complete transitive closure of the datatypes those roots reference. It never trims inside that closure to quiet a diff, and it never adds a hand-written shape outside it.
  • A drift check regenerates in CI and fails on any diff, so the generated layer stays in step with the vendored inputs.

Regenerate

$ cargo run -p ferroterm-fhir-codegen -- emit

Then run the drift check. If consuming code needs a shape the generated crate lacks, fix the emitter rather than shadowing it with a hand-written type.

The generator design follows the sibling project FerroEHR, which generates its openEHR model from vendored machine-readable specs the same way.