Skip to content

Releases: FerroHEALTH/FerroTERM

0.1.8

Choose a tag to compare

@github-actions github-actions released this 16 Oct 12:07
Immutable release. Only release title and notes can be modified.
v0.1.8
fbda3ed

Added

  • The server sends a usage report to FerroPULSE, operated by Cadasto B.V.: one
    report at each start and one a day, with the instance id, the version, the
    deployment kind, coarse host size, the loaded code system families, and
    latency per operation group. It never carries a code, a URL, or a request
    path. It is on by default, FERROTERM_USAGE_REPORT=off stops it, and it is
    off in CI and without a writable FERROTERM_STATE_DIR. The compose file
    keeps the state on a named /data/state volume; a plain container sends
    nothing until it is given one. What is sent, what
    the collector stores (the source IP address and the country derived from
    it, for 13 months), and how to have it erased:
    https://ferroterm.eu/docs/operate/usage-report.html.

Fixed

  • The ferroterm starting and listening log lines name every served FHIR
    version (fhir_versions=r4,r4b,r5,r6, bases=/r4,/r4b,/r5,/r6) instead of
    r4b alone.

0.1.7

Choose a tag to compare

@github-actions github-actions released this 16 Oct 08:23
Immutable release. Only release title and notes can be modified.
v0.1.7
6364333

Changed

  • The Licensor and copyright holder of the project's own work is Cadasto B.V.
    (#700), which replaces Vernum Projecten B.V. Every Licensor:, copyright and
    SPDX-FileCopyrightText line, the contribution grant, the governance text and
    the site footer name the company. The licence terms are unchanged, and
    maintainer credit stays a person.
  • A commercial licence, and any other business or licensing question, goes to
    Cadasto B.V. at info@cadasto.com or https://www.cadasto.com/contact/ (#702).
    Every LICENSE copy, the README, the handbook licensing page,
    MAINTAINERS.md, GOVERNANCE.md and SUPPORT.md say so. The maintainer
    keeps the technical side, and the licence terms are unchanged.
  • Pull requests record a user-visible change as a fragment under
    changelog.d/, which the release cut assembles into this file (#704).
  • The build service in the release compose.yaml builds any code system
    ferroterm-build knows, so a deployment without a SNOMED CT licence builds
    LOINC or another system through Compose. It mounts a release directory
    (./releases, or FERROTERM_RELEASES_DIR) at /releases and takes the
    builder's own arguments:
    docker compose run --rm build --loinc /releases/Loinc_2.82.zip --out /data/index/loinc.
    FERROTERM_RF2 is gone; a SNOMED CT build passes
    --rf2 /releases/<zip> --out /data/index/snomed (#713).

Security

  • rustls moves to 0.23.46, which fixes RUSTSEC-2026-0346, RUSTSEC-2026-0347,
    RUSTSEC-2026-0348 and RUSTSEC-2026-0349 in the server and sync service TLS
    stack, and zip moves to 9.0.3 because every 8.x release is yanked (#715).

0.1.6

Choose a tag to compare

@github-actions github-actions released this 01 Oct 17:24
Immutable release. Only release title and notes can be modified.
v0.1.6
3c42f22

Added

  • The sync lists a service's FHIR API beside its feed (#692). The Nictiz
    NTS feed carries only the SNOMED CT binary index, and its CodeSystem,
    ValueSet, and ConceptMap resources answer on its FHIR endpoint. A source
    that names a FHIR API is searched per type and paged to the end, each
    resource becomes an entry the subscription and the replace rule read as they
    read the feed, and it goes through the resource lane with the digest of what
    arrived recorded, since the API advertises none. The run record carries the
    API address, how many resources it listed, each entry's origin, and the
    subscribed canonicals no listing carried, which the account could not see. A
    CodeSystem listed with content not-present is reported, never served.
    The NTS add-on lists its FHIR endpoint by default (fhir_api = false keeps a
    run to the feed).

Fixed

  • scripts/live/nts-feed.sh follows the discovery redirect (#602). The
    NTS answers its SMART configuration URL with a 301 to the Keycloak
    discovery document, and the script stopped there with "the discovery
    document names no token_endpoint"; it now follows up to three redirects,
    as the add-on already did.

  • A subscription to a SNOMED CT edition matches the live NTS feed (#691).
    Ontoserver identifies an edition release by the bare code system
    http://snomed.info/sct and carries the edition in the version URI, so a
    subscription listing the edition URI admitted nothing and a run took nothing.
    A listed edition now admits a release whose version URI sits under it, the
    fixture feed carries the live shape, and a built artifact is held under its
    system as well as its edition, so a re-run recognises the release as served.

Changed

  • The repository moved to the FerroHEALTH organisation: it lives at
    https://github.com/FerroHEALTH/FerroTERM, and releases from this one on
    publish the image to ghcr.io/ferrohealth/ferroterm and sign their
    attestations as FerroHEALTH/FerroTERM. Releases up to v0.1.5 keep their
    rubentalstra signer identity, and the book's verification page says how to
    check them.
  • The NTS add-on names the Dutch canonicals the service serves (#602):
    ICD-10-NL, the Labcodeset supplement and its three maps, NHG-Tabel 24, the
    NHG table prefix, and the zib value set prefix, read from the live service.
    The book's NTS section records what the feed and the FHIR API carry, the
    24 hour token lifetimes, and how an unlicensed system is hidden.

0.1.5

Choose a tag to compare

@github-actions github-actions released this 24 Sep 18:29
Immutable release. Only release title and notes can be modified.
v0.1.5
06841e1

Added

  • The project extensions resolve on the website (#671). Each of
    terminology-artifact, implicit-value-set and operation-level under
    https://ferroterm.eu/fhir/StructureDefinition/ has a StructureDefinition
    in the R4B shape (<name>.json, with its context, its sub-extensions and
    their types) and a page at the canonical itself that links it, as FHIR asks
    of an extension's URL (https://hl7.org/fhir/R4B/extensibility.html#defining).
    The book's FHIR API page links each definition beside its wire example, and
    scripts/checks/extension-definitions.sh fails CI when the server emits a
    canonical the site does not define.

  • metadata?mode=terminology declares the implicit value sets each served
    version resolves
    (#657). Every form is an extension
    https://ferroterm.eu/fhir/StructureDefinition/implicit-value-set on
    codeSystem.version, on R4, R4B, R5 and R6, with a pattern (the URL
    template as the code system's page spells it, placeholders in square
    brackets) and an argument (none, code or expression). SNOMED CT
    declares its five ?fhir_vs forms, LOINC /vs, /vs/[id] and
    /vs/[partcode], RxNorm /vs, UCUM /vs and /vs/[expression], and an
    ICD-11 linearization [entity]/postcoordinationScale/[axis]; the ClaML
    classifications, BCP 13, BCP 47 and CodeSystem resources declare none. The
    composer's value set picker offers each declared form for the clause's code
    system and fills its placeholders, a code through the concept search and an
    expression through a text field.

  • The history interaction on the persisted resources (#667).
    GET {type}/{id}/_history and GET {type}/_history answer a history
    Bundle on every served version, newest first, with entry.request naming
    the POST, PUT or DELETE that made each version and entry.response
    its status, etag and lastModified; the delete is an entry with no
    resource (https://hl7.org/fhir/R4B/http.html#history). _since narrows
    both levels, and _count, _at and _list answer 400 not-supported. A
    loaded resource answers an empty history. The CapabilityStatement declares
    history-instance and history-type wherever it declares vread. A delete
    is now recorded as a version of its own: a version read of it answers 410,
    and a resource written again after a delete counts on from it, where it used
    to start again at version 1 and overwrite the first version in the store.
    The viewer's history screen keys a delete by the version its etag names.

  • _summary on read and search, and _elements on read (#669). The read
    of a CodeSystem, ValueSet or ConceptMap takes _elements, with the
    projection and SUBSETTED tag a search already gave. Both interactions take
    _summary (https://hl7.org/fhir/R4B/search.html#summary): true keeps
    id, meta and the top-level elements the served version's definition
    marks isSummary; text keeps
    text, id, meta and the top-level elements the served version's
    definition makes mandatory, read from the element table of the pinned
    packages; data drops text; false is the whole resource; count, on a
    search only, answers total with no entries. A resource that lost an element
    carries SUBSETTED. A value the search-summary code system does not define
    is a 400. The
    CapabilityStatement declares _summary beside _elements.

  • A refused expression constraint states where the parser stopped, as
    data
    (#656). When ValueSet/$expand or ValueSet/$validate-code refuses
    a malformed filter value or ecl/ implicit value set, the
    OperationOutcome issue names the input in issue.expression
    (Parameters.parameter[i].resource.compose.include[j].filter[k].value for a
    filter of an inline value set, Parameters.parameter[i].valueUri for a
    url in a body, http.url for the url query parameter) and carries the
    operationoutcome-issue-line (1) and operationoutcome-issue-col
    extensions. The column is 1-based and counts characters into the value
    issue.expression names; for http.url it counts into the query value as
    the client sent it, percent-encoding included. The answer is the same on R4,
    R4B, R5 and R6, and never sets the issue.location element R6 dropped. The
    viewer's composer marks the character from these two, and no longer reads a
    number out of the diagnostic's wording.

  • The viewer's editor bundle reads a resource's versions, compares two of
    them, and restores one
    (#637). Every authoring screen links to
    /ui/editor/history for the resource it has open. The version list is read
    with whichever interaction the server's CapabilityStatement declares, the
    history interaction or the version read, and the screen says which it used
    and when the server declares neither. Any two versions can be compared, as
    the elements they disagree about rather than as text, with the two server-
    assigned meta elements left out; the pair is in the address, so a
    comparison is a link. Restore this version writes that version's whole
    resource back with If-Match of the version the server holds now, so a
    concurrent edit is refused with 412 and shown as one; it needs the write
    scope for the resource type, and a reader sees the versions and no restore
    control. Where a deployment puts the synchronisation service's admin listener
    behind the server's own origin, the screen also lists what the newest run
    found about the open resource; where nothing answers there, it shows nothing.

  • The viewer's editor bundle composes a local ValueSet (#635).
    /ui/editor/compose builds a ValueSet.compose out of value sets the server
    already publishes, own codes picked through the concept search, the filter
    properties and operators the served code system version declares, and
    excludes. The composition rules are drawn as they apply, a clause that breaks
    vsd-1, vsd-2 or vsd-3 says so by number, and the preview is
    ValueSet/$expand by POST with the unsaved definition inline. Saving needs
    user/ValueSet.cud and goes through the REST API with If-Match, carrying
    back every element the form does not draw. Content the server serves from its
    loaded indexes opens read-only.

  • A concept map is authored in the browser, at /ui/editor/conceptmap (#636).
    The screen carries the metadata, the value sets that scope the map, the
    groups of systems it maps between, and every code with the targets it maps
    to. The element a save writes is the served version's own: R4 and R4B take
    source[x]/target[x] and group.element.target.equivalence, R5 and R6
    take sourceScope[x]/targetScope[x] and target.relationship, R5 made a
    group's system a canonical carrying its own version, and R5 added
    element.noMap (https://hl7.org/fhir/R4B/conceptmap.html,
    https://hl7.org/fhir/R5/conceptmap.html). The relationship control offers
    what the served root expands that version's own value set to, and the form
    refuses what the version refuses: a target with no relationship, a code that
    both maps to nothing and carries a target (cmd-4), and an uncommented
    target on the relationship codes cmd-1 names, which differ per version and
    are a warning rather than an error on the R6 ballot. Every code on both sides
    is picked out of the system its group names, through the same
    ValueSet/$expand search the concept browser runs, so a target arrives with
    the display the server gave it. A preview runs ConceptMap/$translate by
    POST with the map itself in the conceptMap parameter, which is how a map
    that has never been saved is translated through; a server may refuse a map
    sent that way
    (https://hl7.org/fhir/R4B/conceptmap-operation-translate.html), so the
    screen then runs against the saved map and says which it used. Saving sends
    the whole resource with If-Match, a 412 is shown as a concurrent edit
    with a reload, and every refusal renders the server's own OperationOutcome
    and announces its text. A map this deployment loaded opens the same screen
    read-only, because the server states no meta.versionId for it.

  • A code system is authored in the browser, at /ui/editor/codesystem (#634).
    The screen carries the metadata (url, version, status, content,
    caseSensitive), the properties the system declares, and the concepts with
    their designations and property values, with every coded control offering
    what the served root expands that element's own value set to. A concept is
    retired rather than deleted: the lifecycle control writes exactly the
    standard concept properties the state implies (status always, inactive
    where the state means it, and deprecationDate or retirementDate,
    https://hl7.org/fhir/R5/codesystem-concept-properties.html), and after a
    save the screen runs CodeSystem/$validate-code on what it retired and shows
    the answer. Every write sends the whole resource with If-Match, so a change
    made elsewhere is refused with 412 and offered a reload rather than
    overwritten, and every refusal renders the server's own OperationOutcome
    and announces its text in the screen's live region. A code system this
    deployment built from a release opens the same screen read-only, because the
    capability statement marks it as served from an artifact.

  • The viewer ships as two bundles, and the server serves both (#634). The
    reader bundle is what /ui has always served, unchanged in size and in
    content. The editor bundle is the same crate built with --features editor,
    served at /ui/editor, carrying every reading screen plus the authoring
    screens. A reader who never edits downloads no authoring byte, and a person
    who edits opens /ui/editor, signs in there, and reads there too. Each
    bundle has its own recorded size (app/ferroterm-viewer/bundle-size.json and
    bundle-size-editor.json), and the release lane embed...

Read more

0.1.4

Choose a tag to compare

@github-actions github-actions released this 23 Sep 05:09
Immutable release. Only release title and notes can be modified.
v0.1.4
cfdfe29

Added

  • app/ferroterm-sync, the synchronisation service: one way from a terminology
    syndication feed into a running server (#579). It runs beside the server and
    never inside it. On a schedule (every = "24h", at = "03:00" in a
    configured zone, no cron), on POST /run, or as ferroterm-sync run-once,
    it lists each configured source, takes what the deployment does not hold yet,
    and puts it through one of two lanes: an RF2 archive is built by
    ferroterm-build into a staging directory and renamed into the index root as
    a new release beside the previous one, and a FHIR resource is written into
    the managed resource directory with its service's corrections applied. It
    then asks the server to reload. activation = "manual" stops after staging
    and waits for POST /activate. Retention keeps the newest releases per code
    system and removes the rest only after a reload succeeded; nothing else is
    ever deleted, and the server's own write store is never touched. A build that
    fails or a reload the server refuses leaves the served set exactly as it was,
    rolling back what the run had moved, and still fires the webhook. Every run
    writes one JSON record (what the feed offered, what was taken, what was left
    behind and why, the builds, the activation, the reload replies, retention,
    and the errors), served with the metrics and the manual triggers on the
    service's own admin listener. The Nictiz NTS add-on is registered as the
    nts source kind. docker/Dockerfile.sync carries the service and the
    builder on the same distroless base, and compose.sync.yaml runs it beside
    the server over one volume with the server's admin listener turned on.
  • The sync service revalidates the deployment's own content after a release is
    activated (#583). With fhir_base_url set it reads every locally authored
    ValueSet and ConceptMap over the server's public FHIR API, with
    ValueSet/$validate-code and ValueSet/$expand and their activeOnly
    parameter, and writes into the run record every local code the new release
    made inactive, removed, or left outside the value set it was included
    through, each with its system, its code, and the release that changed it. A
    run that finds nothing says so. The finding count travels in the webhook
    summary, and /metrics carries ferroterm_sync_revalidation_findings as a
    gauge per locally authored resource. The check reports and never edits local
    content.
  • Optional SMART App Launch bearer validation over the write routes and the
    admin listener (#586). FERROTERM_OIDC_ISSUER turns it on: the server reads
    the issuer's OpenID Connect discovery document and its JWKS before it binds,
    refuses to start with the reason when either does not answer, and then
    requires an OAuth 2.0 bearer token on POST, PUT, and DELETE of
    CodeSystem, ValueSet, and ConceptMap on every served FHIR version, on
    POST [base]/$closure, and on every admin-listener route. The scope for a
    route is the SMART version 2
    one (system/CodeSystem.c to create, .u to update, .d to delete, the
    combined cud and cruds forms accepted), and the version 1 .write and
    .* forms are accepted as the specification maps them
    (https://hl7.org/fhir/smart-app-launch/scopes-and-launch-context.html);
    FERROTERM_OIDC_ADMIN_SCOPE names the scope the admin listener requires. A
    request with no token answers 401 with a WWW-Authenticate: Bearer
    challenge, a token that does not verify answers 401 with
    error="invalid_token", and a token without the scope answers 403 with
    error="insufficient_scope", each with an OperationOutcome (RFC 6750 §3).
    The signature is checked against the issuer's JWKS with an asymmetric
    algorithm only, and iss, exp, and nbf are required, as is aud when
    FERROTERM_OIDC_AUDIENCE names one; a typ header that names a kind other
    than an access token is refused (RFC 8725 §3.1, §3.8, §3.9, §3.12). A token
    naming a key the server has not read refreshes the key set, at most once a
    minute. The issuer URL is https, and a cleartext one is refused unless its
    host is the loopback.
    [base]/.well-known/smart-configuration is served per version from the
    issuer's document, and the capability statements declare SMART-on-FHIR with
    the oauth-uris extension
    (https://hl7.org/fhir/smart-app-launch/conformance.html). The read surface
    stays open, and a deployment that names no issuer behaves exactly as before.
  • The server reloads the served set without a restart (#578). SIGHUP and
    POST /reload both make it read FERROTERM_INDEX and FERROTERM_CODESYSTEMS
    again, open everything read-only, and swap the whole registry; a request in
    flight finishes on the set it started on. POST /reload is served by an admin
    listener bound to FERROTERM_ADMIN_LISTEN, which serves nothing else and is
    absent when the variable is unset; the FHIR listener answers /reload with
    the not-found OperationOutcome any unknown path answers. A reload that
    meets an artifact it cannot open leaves the served set untouched, logs the
    reason, and answers the admin request with a 500 naming it.
    FERROTERM_RESOURCES is carried over rather than reopened. /metrics gains
    ferroterm_reloads_total{outcome="ok"|"failed"}, and
    ferroterm_code_system_loaded now describes the set being served rather than
    the set loaded at start.
  • A concept of a CodeSystem resource the server loaded or a client persisted
    is inactive when its standard concept properties say so (#584). inactive = true, a status of retired, and a retirementDate the request is behind
    each retire the concept; deprecated, a deprecationDate, and a status of
    deprecated leave it active, because the specification says a concept that is
    deprecated but not inactive can still be used
    (https://hl7.org/fhir/R5/codesystem-concept-properties.html). $expand with
    activeOnly=true then leaves the concept out and activeOnly=false lists it
    with contains.inactive = true, $validate-code answers result = true with
    inactive = true, and $lookup returns the properties as written. The R5
    property set is data on every version prefix, so the answers match on R4, R4B,
    R5, and R6. Where two markers disagree the one that says inactive wins, a
    retirementDate that is not a readable dateTime reads as a retirement
    already in force, and the status output of $validate-code reports a status
    only where the resource stated one.
  • crates/terminology-syndication, a client for the Atom syndication dialect
    national terminology services publish (#580). It reads a feed with the NCTS Atom
    Syndication Format extensions into a typed model, keeps a category term it
    does not name rather than dropping it, decides what a run takes (snapshot
    only, the binary index refused by name, Ontoserver's newer-date replace
    rule), and streams one content item to a file with its SHA-256 or MD5
    verified before it counts as fetched. The Source trait is the seam a
    service add-on implements, with the listing and the download authenticating
    separately. The crate names no country, operator, or code system.
  • A path in FERROTERM_INDEX may name an index root as well as a single
    artifact (#592). A path holding no manifest.json is a root, and every child
    directory that holds one is opened as an artifact. The server lists the roots
    again on every reload, so a release directory that appeared is opened and one
    that disappeared is dropped, without a restart. A child without a manifest is
    passed over, which makes the stage-then-rename step a synchronisation job uses
    race-free; a child whose manifest the server cannot read refuses the reload
    and leaves the served set answering. Two releases of one system under a root
    are both served, and the greatest version stays the default.
  • scripts/checks/addon-boundary.sh, the guard that keeps addons/* optional
    (#582). A source add-on is code the sync service compiles in and nothing else
    links, so the check reads the declared workspace edges from cargo metadata
    and fails when a member outside app/ferroterm-sync depends on an add-on, or
    when an add-on depends on a workspace crate other than
    crates/terminology-syndication and the leaf crates it names by hand. A dev
    dependency is test scaffolding and stays legal. --self-test judges synthetic
    metadata, so a tree whose every real edge is legal still shows the guard
    bites. It runs in CI beside the viewer boundary, and
    scripts/checks/comment-style.sh --all now runs there as a job of its own
    rather than only in the per-edit hook.
  • addons/nts, the Nationale Terminologie Server source add-on and the first
    member of the new addons/* tree (#581). It reads the token endpoint from the
    service's SMART configuration document, logs in with the documented password
    grant (or the client-credentials grant when a client secret is configured),
    refreshes the access token inside a one-minute margin, and logs in again from
    the stored credentials when the refresh is refused, so an unattended run on
    any day needs no person. Credentials come from a file or the environment and
    never from the configuration body, and no rendering of the configuration, the
    credentials, or the source prints a secret. The subscription names systems by
    canonical identifier: an entry outside it is reported as skipped, and a system
    the service offers only as Ontoserver's binary index is reported as not
    syndicable. A FHIR resource file whose experimental element is the string
    "true" or "false" gets the boolean FHIR declares, with the correction
    returned beside the file; a file that needs nothing lands byte-identical. The
    add-on is verified against fixtures only until an account exists.

Changed

  • fhir-types 0.1.105 (#611). The generated codec now refuses a primitive
    outside its lexical...
Read more

0.1.3

Choose a tag to compare

@github-actions github-actions released this 12 Sep 14:22
Immutable release. Only release title and notes can be modified.
v0.1.3
a976640

Added

  • ferroterm healthcheck, a subcommand that sends one GET /health to the
    server's own listen address and exits 0 on 200 OK, 1 otherwise with the
    reason on stderr. The distroless image has no shell or HTTP client, so this
    is what its new HEALTHCHECK runs, and the shipped compose.yaml states the
    same block: docker compose up --wait now waits for a serving instance, and
    another service sequences itself after the server with
    depends_on: ferroterm: condition: service_healthy, as the shipped proxy
    service now does. Because the server binds only after every artifact is
    open, a passing probe means the deployment answers terminology requests
    (#565).

Changed

  • The FHIR model now comes from crates.io. fhir-types and the generator that
    emits it, with the five vendored HL7 packages behind it, moved to the
    FerroBRIDGE repository, which publishes the crate; FerroTERM depends on it by
    version like any other dependency (#300). Consumers of the crate are
    unaffected: the same crate line continues on crates.io, published from
    FerroBRIDGE, and the published 0.1.98 source is byte for byte what this
    repository generated at 0.1.97. What leaves here is the generator, its
    vendored input, the codegen-drift CI job, and the fhir-types entry of the
    publish order. What replaces the drift job is a pin check:
    scripts/checks/versions.sh fails when the requirement in Cargo.toml, the
    row in docs/VERSIONS.md, and the version in Cargo.lock disagree, so a new
    release reaches the build only in a change that records it, and Dependabot
    opens that pull request on its own.

0.1.2

Choose a tag to compare

@github-actions github-actions released this 12 Sep 06:27
Immutable release. Only release title and notes can be modified.
v0.1.2
f730c4a

Added

  • The viewer has a design system, and every screen is drawn from it. Colour is named by role on :root and redefined once under :root.dark, so a theme decision is never taken per element; type is six steps, spacing four on an 8-point grid, and motion one duration. A screen names a role and a step and nothing else, which scripts/checks/viewer-tokens.sh enforces as its own CI job: a palette entry, a per-element dark: variant, a raw type size, an off-scale spacing value, or a bracketed value fails the build. The point is measurement rather than tidiness: a pairing defined once is checked once, and the WCAG 2.2 AA contrast pass covers every pairing the viewer draws in both themes and both densities.

  • A command bar sits above every screen, and the navigation is four labelled groups. One field takes a code, a canonical, or free text, decides which it is from the string's shape before any request, and offers what this root can do with it; every offer is a real link, so the keyboard contract is the browser's own and an offer can be shared. The screens are grouped by what a reader came to do rather than by resource type.

  • Every runner picks its code system rather than waiting for a canonical to be typed. GET /{version}/{type}?_elements=url,name,title,version is about four kilobytes where the whole search is a quarter of a megabyte, so each control offers what this root publishes, the name leading and the canonical as the value, with the versions beside it being the ones that canonical was published in. A canonical the root does not publish is still typed, and the control opens on the text field for one rather than snapping back to a list it is not in. The screens are named for the question rather than the operation that answers it: Check a code, List a value set, Map a code.

  • The overview reads a twenty-system deployment on one screen. It is one table rather than a card each, sortable by any system-level column, each row leading with the name its code system was published under and the canonical beside it, quieter and still selectable. Absence is a mark with the sentence behind it rather than a sentence in every cell. The top bar and the sidebar stay put while the screen scrolls under them.

  • _elements is answered on every served version. The parameter is defined by every release this server speaks (http://hl7.org/fhir/SearchParameter/Resource-elements) and was refused as unsupported; a search now returns the elements it names plus the ones every resource keeps, and marks what it sends SUBSETTED so a client cannot store a subset back as the whole resource. Reading eleven code systems by name costs 4 KB where the whole search costs 255 KB.

  • SNOMED CT's normalForm and normalFormTerse are generated and served. Both are the Necessary Normal Form of a concept, which the FHIR SNOMED CT page defines and does not say how to produce. The generation is a rendering rather than a computation: the necessary normal form of a precoordinated concept is what the release's inferred view already holds (the SNOMED CT Release File Specification, Appendix D), so the focus concepts are the concept's inferred is a parents and the attributes are its other inferred rows, and no reasoner and no supertype walk runs. The syntax is the Compositional Grammar specification §5, the definition status prefix its §6.7: === where the RF2 definitionStatusId says the concept is sufficiently defined, <<< where it does not, both always written because the grammar assumes === when none is. Three shapes are this server's own, because no specification fixes them, and each is recorded: the order of what is rendered, which term a reference carries, and the layout. A request naming no property is answered as before, which is what "the server chooses what to return" admits.

  • An openEHR archetype's local terminology is served through the ordinary operations. An archetype constrains most of its coded fields with an archetype-local at-code list, and nothing publishes those as FHIR resources. The scope decision is recorded: this server ingests the resources a producer derives from an archetype and reads no openEHR artefact, so it keeps no openEHR dependency. The at-codes as a CodeSystem, an ac-code as a ValueSet, and term_bindings as a ConceptMap load through FERROTERM_CODESYSTEMS like any other supplied resource, and $lookup, $expand, $validate-code and $translate answer over them with no openEHR-specific request shape. No openEHR specification defines a canonical URI for a local value set, so whoever produces the resources mints the URL from the archetype id, and this server reads it as an opaque canonical: two deployers minting the same archetype under their own domains are served side by side, and two directories naming one canonical at one version refuse to load rather than picking a winner.

  • The brand tokens state what each is safe for. Three of them are safe for the mark and not for text, and nothing said so: teal reaches 3.58:1 on the light surface, which clears the 3:1 a graphic needs and falls short of the 4.5:1 a label needs. Each token carries its measured ratio against both grounds, --ferroterm-brand-text is the brand colour as text (deep teal on light, teal on the tile, each at 4.5:1 or better), and scripts/checks/brand-contrast.sh fails when a token's own comment or the published table stops matching what the value measures. The mark is unchanged.

Fixed

  • An unresolvable exclude.valueSet is named rather than passed over. ValueSet/$validate-code walked the excludes only after an include had held the code, so an exclude naming a value set this server does not hold was never reached and the answer was a silent result = false over a definition it could not fully evaluate. The excludes are walked either way now, which changes what is reported and no answer: an exclude removes codes, so a false it could not evaluate was never going to become a true. The terminology ecosystem settles the reached case as an answer rather than a refusal, with the canonical named in an issue of tx-issue-type#not-found, and it requires $expand and $validate-code to differ in shape over one such value set; a refusal stays reserved for a definition that cannot be processed at all, which is a cycle.

  • A cycle names the value set that closed it. The reference chain pushed the root as url|version and every reference as the bare reference string, so the two spellings could never match: a value set referencing itself was caught one hop late, and a two-hop loop was reported under the value set it reached rather than the one that closed it. One rule now decides whether a reference re-enters a chain entry, comparing the url and the versions where both name one, because a reference naming no version means the version the server resolves to.

  • check-system-version is compared against the version an include resolved to. The check ran against the version or pattern a value set wrote, before that version had been resolved against what the server serves, so a value set naming 1.x.x was reported as failing a 1.0.x check under the pattern's own spelling rather than under the 1.2.0 it selects, and a value set naming a version nothing serves was reported as failing the check rather than as naming a version that does not exist. The pin now applies the defaults and the forces alone and the checks run over the versions the expansion actually used, so a version that resolves to nothing fails as unknown first, which is what it is. An answer that says a version is unknown also names the versions the server does hold.

0.1.1

Choose a tag to compare

@github-actions github-actions released this 08 Sep 15:13
Immutable release. Only release title and notes can be modified.
v0.1.1
aeb436c

Added

  • The viewer runs $validate-code and $subsumes. /ui/validate validates one code against a code system or a value set, and asks how two codes of one system relate. What the form offers is the root's own capability statement: a resource type it does not declare $validate-code on is not offered, and where a root declares the instance level a stored resource's id stands in for the canonical. The answer states the verdict, the message the server gave, the display the system holds, the version it validated in, and whether the concept is inactive, with every issue the server itemised and the codes beside its text. Both runners put their parameters in the address, so a case is a link you can share.

  • The viewer compares the four served roots. /ui/versions reads GET /{version}/metadata from /r4, /r4b, /r5, and /r6 and draws two tables: what each root says about itself, and which of the terminology operations it declares and at which levels. The screen exists because the four genuinely differ; R4 and R4B declare $lookup at the type level and R5 added the instance level, and the viewer offers on every other screen only what the root you are on declares. Each root's own operation.documentation is drawn below, which is where a release states what it takes beyond its own OperationDefinition.

  • The viewer carries the conformance and benchmark evidence this build ships. /ui/evidence is the one screen that asks the server nothing: every figure was read out of a committed file by the build script and names that file on the page, so a reader who doubts a number opens it in the repository. It draws the HL7 terminology ecosystem suite mode by mode with the cases each passed of the cases it ran, the latency bars the project claims with the run recorded against each, and the newest committed benchmark run system by system. The figures describe the FerroTERM build the bundle came from, never the deployment answering the page, and the screen says so.

  • The viewer's screens sit in a left sidebar, each with its own icon, and the FHIR version selector stays in the top bar. The selector changes what every screen reads rather than moving you to a screen of its own, which is why it is not in the list. On a narrow window the sidebar folds behind a button. The icons are the viewer's own, drawn as inline SVG with no icon package in the bundle.

  • The FHIR core code systems and value sets the specification defines are served without configuration. A deployment that loads no artifact still answers $lookup, $validate-code, and $expand for the code systems FHIR publishes itself, so a resource bound to administrative-gender or publication-status validates against the version of FHIR it was written for.

  • Every ISO 3166 code form is a code of urn:iso:std:iso:3166. FHIR selects all three from it with a code regex per form, [A-Z]{2} for ValueSet/iso3166-1-2, [A-Z]{3} for -1-3, and [0-9]{3} for -1-N, so an alpha-3 or numeric country code the specification binds has to validate. Only the alpha-2 form was a code, and NLD and 528 were refused against a system that holds NL. Each territory is three concepts now, sharing a display and carrying all three forms as properties.

  • The viewer browses the concepts of one code system. /ui/browse searches a system, reads one concept with CodeSystem/$lookup, and walks the hierarchy the served version declares. What the screen offers is read from TerminologyCapabilities alone. A version that declares a filter carrying child-of, the operator FHIR defines as "all concept ids that have a direct parent that matches", gets the taxonomy tree on the property that filter is declared on; a version that declares other hierarchy operators, or none at all, gets the flat searchable list with a sentence naming what it does declare. Every search and every level of the tree is a POST ValueSet/$expand whose value set travels inline as the operation's own valueSet input, so the screen names no implicit canonical and browses any served system the same way; each request asks for excludeNested so one level arrives as one list. The tree follows the ARIA tree view pattern: one tab stop that follows the browser's own focus, the arrow keys walking and opening, Enter selecting, and each row carrying its level, its position among its siblings, and whether it is expanded. A row is keyed by the path of codes above it, so one concept sitting under two parents draws two rows that never share state, exactly one of them is announced as selected, and an answer that loops back stops at the repeat. A node whose children the server counts higher than it listed says so on the node, so a level cut short by the page size never reads as the whole of it. Every pane renders the request its answer belongs to rather than the address as it stands, so a code is never shown paired with the concept that was read before it. The concept pane draws every designation and property the server answered, and links the parents and children it declares. Every parameter lives in the address, so a concept you are reading, the filter you typed, and the nodes you opened are all one link you can share, and a refusal renders the server's own OperationOutcome.

  • The viewer lists the concept maps a root holds and translates codes with them. /ui/conceptmaps reads GET /{version}/ConceptMap the way the value set screen reads its own type, draws every fact a map's publisher declared, and lists the groups it maps with the code count of each. The scope elements are read under both spellings, because R4 and R4B carry source[x] and target[x] and R5 renamed them sourceScope[x] and targetScope[x]. The $translate runner below appears only where the root's CapabilityStatement declares translate on ConceptMap, so the screen never offers a run the server then refuses. Every parameter is sent under the name that FHIR version's own OperationDefinition declares: R4 and R4B take code, system, version, and targetsystem, R5 renamed the code to sourceCode and the target system to targetSystem, and the R6 ballot renamed system and version to sourceSystem and sourceVersion. The answer is read the same way: a match's relation is labelled Equivalence where R4 and R4B answered one and Relationship where R5 and the R6 ballot did, over two different sets of codes, so one version's answer is never drawn as another's. Each match also draws the target Coding, the map it came from, the source concept and comments, and any product, dependsOn, or property the answer carried. Matches keep the server's own order, which is the edition's concept order for a reverse translation. Every parameter of a run lives in the address, so a translation is a link you can share, and a refusal renders the server's own OperationOutcome.

  • The viewer lists the value sets a root holds. /ui/valuesets reads GET /{version}/ValueSet, filtered by the url and version search parameters, and reads one of them by id. The filter, the page, and the resource you opened all live in the address, so a list is a link you can share and the back button walks it. This server answers a search in one searchset and pages none of it, so the pages the screen walks are its own over the answer that arrived, and it says so. A value set you open draws every fact its publisher declared, the clauses its compose selects with, and a link that opens its canonical in the expansion runner. A resource that carries no id says so rather than drawing a link that would answer nothing, and a refusal renders the server's own OperationOutcome. The screen only reads: the server answers create, update, and delete on this resource type and the viewer calls none of them.

  • The viewer runs $expand and walks its pages. /ui/expand takes a value set canonical with filter, count, offset, displayLanguage, activeOnly, and includeDesignations, sends each one only when you set it, and draws the concepts with their designations, expansion.total, and the parameters the server says it applied. Every parameter lives in the address, so a page is a link you can share and the back button walks the run; an implicit canonical carrying its own query string is percent-encoded into the one parameter it belongs to. An expansion the server marks with valueset-unclosed says so above the table, in the server's own words where it states a reason, because a code missing from an unclosed list is not a code the server rejects. A refusal renders the OperationOutcome verbatim, with the codes of issue.details beside the text, and a too-costly refusal adds what to do about it: ask for a page size and the runner walks the selection instead. The screen names no code system.

  • The viewer's first screen answers what a deployment loaded. /ui reads GET /{version}/metadata?mode=terminology and draws one card per code system: the canonical, every served version with the default marked, whether the server reads the system's compositional grammar, the designation languages, the declared filters with their operators, the artifact the version was read from with the release the offline build recorded, the content mode where the FHIR version has that element, and whether subsumption is supported. R4 and R4B declare no codeSystem.content, so the card says the version does not carry it rather than showing nothing, and the R6 ballot's version.value is read where R4, R4B, and R5 use version.code. A system that declares no version, no language, or no filter states the absence, and a system read from no artifact, such as a registry the server carries, says that rather than reading as a gap. The screen names no code system: every card is the capability statement rendered, so a system this server has never served draws with no change to the viewer, and a test proves it by feeding a document naming an invented system. Each section dis...

Read more

0.1.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 22:59
Immutable release. Only release title and notes can be modified.
v0.1.0
71b21de

Added

  • FERROTERM_BASE_URL: the URL clients reach the server at, stated per version as CapabilityStatement.implementation.url and in the terminology capabilities. A server behind a reverse proxy answers on an address it never sees, so the endpoint is configured rather than guessed from a socket or a forgeable header; a deployment that names none states no URL. compose.yaml gains a proxied profile: Caddy terminates TLS in front and the server publishes no host port.
  • GET /metrics: the Prometheus exposition of the requests answered (a counter and a duration histogram per method, matched route, and status) and the code system versions loaded (one gauge each). The route is off the FHIR base path, so a scrape is never a terminology request, and the labels name the matched route rather than the URI, so the series count stays bounded whatever codes clients ask about.
  • X-Request-Id on every response: the client's own value when it sent a usable one, a fresh UUID otherwise, and the same id in the request's log line. An id that is empty, over 128 characters, or not printable ASCII is replaced rather than echoed.
  • $batch-validate-code on ValueSet and CodeSystem, on every served version: one POST carries the shared inputs once, a validation parameter per validation, each a Parameters of that validation's own inputs, and the answer repeats validation in the same order with that validation's $validate-code outputs. A validation states its own value for an input the request also states, and a validation the server cannot run answers an OperationOutcome in its own slot while the others still answer. No OperationDefinition declares the operation anywhere, so the contract is the terminology ecosystem suite's cases, whose batch/batch-validate now passes on /r4b, /r4 and /r5.
  • The fuzz targets under fuzz/: every parser an untrusted input reaches (ECL, the FHIR JSON and XML bodies, the SNOMED CT implicit ?fhir_vs= and ?fhir_cm= forms, and the RF2 file names, effective times, and identifiers) is fed arbitrary bytes by cargo-fuzz, weekly in CI and by hand for longer runs. The seeds are committed; a crash uploads the input that reproduces it.

Fixed

  • The terminology capabilities no longer claim SNOMED CT post-coordination. CodeSystem.compositional is "The code system defines a compositional (post-coordination) grammar" and TerminologyCapabilities.codeSystem.version.compositional is "If the compositional grammar defined by the code system is supported", two different statements that one flag answered, so a client was told it could send expressions and then got "no such concept" for every one. A provider now declares the grammar and this server's support for it separately: SNOMED CT defines the grammar and this server evaluates none, UCUM, BCP 13, BCP 47, and the ICD-11 linearizations parse the grammars they declare, and a CodeSystem resource that declares a grammar is served without one. A post-coordinated code is refused as a grammar this server does not evaluate (not-supported) rather than as a concept the edition lacks.
  • A code system the deployment loaded reads at its own id and is found by a search. GET CodeSystem/{id} on the id the server prints at startup was a 404 and GET CodeSystem?url=… answered total 0, while the capability statement declared read and search-type and the configuration page documented the id, so a documented instance path addressed nothing. Every loaded version now answers both, under every version prefix: a system held behind an index answers its definition with content = not-present, and one loaded as a CodeSystem resource answers with the content it declared. A supplement stays applied to the system it supplements and is not served as an instance.
  • CodeSystem/{id}/$lookup answers under /r5 and /r6, whose OperationDefinition declares the operation at the instance level. The engine refused the instance level on every version; the level is now the served version's own declaration, so /r4 and /r4b, which declare the type level only, still offer no such route.
  • A SNOMED CT implicit value set or concept map based on a non-default edition answers when several editions are loaded. The FHIR SNOMED CT page lets any edition version stand as the base URL, and the edition in the base decides the membership, but the server only asked the default version of the system, so every ?fhir_vs form and ?fhir_cm= on another loaded edition was refused as a malformed URI. The registry now asks every loaded version, default first, so the bare http://snomed.info/sct base still answers from the default edition. A base naming an edition version no loaded version serves is a not-found on that version rather than vs-invalid.
  • $translate over a SNOMED CT map reference set returns a target Coding carrying both a system and a code. R4B needs ConceptMap.group.target, the "absolute URI that identifies the target system that the concepts will be mapped to", unless the target value set names a single system or every target equivalence is unmatched, and an implicit map states neither. RF2 records the scheme nowhere on a member row, so the reference set names it: the ICD-10 extended map (447562003), the ICD-9-CM equivalence map (447563008), the ICD-O map (446608001), the CTV3 map (900000000000497000), and the US Edition's ICD-10-CM map (6011000124106) each state the URI FHIR publishes for their scheme, on the served ConceptMap and on every match. A map reference set outside that table answers not-supported instead of a code with no system. An association reference set names SNOMED CT on both sides, whichever reference set it is.
  • The LOINC and RxNorm builds name a property key they never registered instead of reporting "too many concepts": a defect in the build no longer surfaces as a capacity error. The RxNorm relationship-type lookup is split the same way, and TooMany keeps its own meaning.
  • An expression constraint of deeply nested brackets no longer takes the server down. The ECL parser descends with the grammar, so an unbounded nesting exhausted the stack and aborted the process, which no OperationOutcome survives; the parser now refuses past sct_ecl::NESTING_LIMIT (64) and the server answers the refusal as an OperationOutcome. Found by the ecl_parse fuzz target.

Changed

  • A flat $expand answers in the order the compose selected: the includes in order, each include's named concepts as it named them, each filter's concepts in the code system's own order, and the first occurrence winning where includes overlap. count and offset page over that order. No FHIR version fixes the order of an expansion, and ValueSet.compose.include.concept says an expansion typically follows the compose; a compose of one filter still pages off the selection bitmap without reading a concept. The ICD-11 postcoordination axis now answers in the WHO's own scale order, which the terminology ecosystem suite's icd-11/expand-pcs-count and icd-11/expand-pcs-offset cases assert together.

0.0.11

Choose a tag to compare

@github-actions github-actions released this 05 Sep 05:45
Immutable release. Only release title and notes can be modified.
v0.0.11
20908d7

Added

  • The build layers derivative reference set packages onto the edition they depend on: ferroterm-build --rf2 <edition> --rf2-refset <package> takes the flag repeatably, and each package's concepts, descriptions, and language reference set members join the edition's while its reference sets serve as implicit value sets over it. Every package's module dependency is checked first, so an edition older than the date a package was authored against is refused, naming the module and both dates, and so is a package naming a module the edition does not contain. The manifest records what was layered. The SNOMED CT ICNP Nursing Practice package is the shape this serves.
  • ConceptMap/$closure maintains a named transitive closure table for a client, over any code system with subsumption: name alone creates or empties the table at version 0, name with concepts answers the relationships the client did not have at a new version, and name with a version replays everything sent after it. The tables live in the database FERROTERM_RESOURCES names and outlive a restart. Naming a table the server was not asked to create is a 404, and a table whose code systems changed under it answers 422 with must be reinitialized. The relationship is read from target to source, and each version states it in its own vocabulary. The R6 ballot ships no ConceptMap-closure definition, so /r6 offers no $closure and declares none.
  • The batch interaction: POST [base] with a Bundle of type batch runs every entry and answers a batch-response with one entry per request, in order, on every served version. A GET entry carries the operation's inputs in the query of request.url and a POST entry in a Parameters resource; every terminology operation is reachable, at the type level and at the instance level. The entries are independent: one that fails answers the OperationOutcome the same request would have answered on its own, the rest still answer, and the batch answers 200. A transaction Bundle is refused with not-supported, and the capability statement declares the batch system interaction.
  • Persisted CodeSystem, ValueSet, and ConceptMap resources: a deployment that names a database in FERROTERM_RESOURCES accepts create, update, read, version read, search by url and version, and delete on every served version, with the FHIR status codes and the ETag and Last-Modified headers. meta.versionId starts at 1 and rises with every write, If-Match makes a write conditional, a deleted resource reads as 410 Gone and keeps its history. Every operation sees a persisted resource exactly as it sees one loaded from FERROTERM_CODESYSTEMS, on every version, and the resources are served again after a restart. A deployment that names no database refuses every write with a 422 and declares no write interaction in its capability statement.
  • SNOMED CT implicit concept maps: ConceptMap/$translate answers url=http://snomed.info/sct?fhir_cm=[sctid] over the reference sets of the loaded edition, on the bare system URI or on the edition or version URI. The four association reference sets the FHIR SNOMED CT page lists (POSSIBLY EQUIVALENT TO, REPLACED BY, SAME AS, ALTERNATIVE) each carry the relationship that page gives them, and the map follows the page's template. A map reference set answers from mapTarget, with the complex and extended columns (mapGroup, mapPriority, mapRule, mapAdvice, correlationId, mapCategoryId) as product parts; no RF2 file records which system a mapTarget belongs to, so the group names no target system. A $translate that names no map and finds none falls back to the historical associations of an inactive concept, so a retired code answers with its SAME AS and REPLACED BY successors. Where the specifications are silent, the choice is marked in the code as our own design.
  • $expand carries the code system's hierarchy: a compose of one include that selects a whole system or an is-a subtree nests its expansion.contains, with total counting every concept and count and offset paging over the pre-order flattening. excludeNested keeps it flat, and so does a text filter over a whole system, whose matches have no root to hang from.

Changed

  • The four loader pipelines SonarQube reports as most complex are split along their phases, with no change in behaviour: the LOINC and RxNorm builds and the DHD thesaurus and G-Standaard readers each drop from roughly 250 lines to under 60, and the four too_many_lines suppressions they carried are gone. The designation and property-key accumulators become small owned structs rather than closures over the enclosing scope.