Skip to content

0.1.4

Choose a tag to compare

@github-actions github-actions released this 23 Sep 05:09
· 57 commits to main since this release
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 form at decode time, with the regex read per FHIR
    version from the HL7 package (structuredefinition-regex): a dateTime
    that is no date, a code with a leading space, a uri with whitespace. On
    the write path a POST or PUT of such a resource answers 400 invalid
    with the element path in issue.expression (a resource that "failed basic
    FHIR validation rules", https://hl7.org/fhir/R4B/http.html), and the
    path names the resource type (CodeSystem.date). R4, R4B, and the R6
    ballot require the timezone offset when a dateTime carries a time; R5
    makes it optional, as the packages state.
  • Engine fixes, in one change: a concept property whose value element carries
    no value is a property the resource does not state, never an empty string, a
    zero, or false, and a concept or property without a code is refused as a
    defective resource (#610); a deprecation stated through the standard
    concept properties (status = deprecated, a past deprecated or
    deprecationDate) earns the DEPRECATED_CONCEPT_FOUND warning while the
    concept stays active (#609); the wall clock is read through one accessor,
    fhir_terminology::clock::now, with the panic contract recorded once (#612);
    the manifest file name has one definition, in concept-store (#597); the
    testkit's sctid takes a typed Item, so a bare ordinal no longer
    compiles where an item number belongs (#594).
  • Repository hygiene, in one change: the versions.sh check reads the one
    published-crate list from scripts/release/publish-crates.sh and accepts a
    member declared unpublished with a reason; the ECL grammar and registry
    fetchers write to the renamed crate paths again, vendored trees are
    linguist-vendored, and scripts/checks/vendor-dests.sh fails when a
    fetcher's destination is missing; every workspace member is named in the
    repo map and the architecture table, kept so by scripts/checks/repo-map.sh;
    docs/claims.md cites symbols and tests instead of line numbers, kept so by
    scripts/checks/claims.sh; the install page and compose.yaml mount an
    index root; zizmor moves to 1.30.1 with its self-repository audit ignored
    by recorded reason while actionlint rejects the $/ form; and the review
    rule names GitHub's Copilot code-scanning lane as advisory.
  • The server binary carries no HTTP client, and a guard keeps it that way
    (#585). The WHO ICD-API walker is the one client in the workspace and now
    sits behind the icd11 crate's api feature, which only the offline build
    tool enables, so reqwest leaves the server's dependency tree.
    scripts/checks/no-client-in-server.sh reads that tree on every pull request
    and fails when a client reaches it. The health probe keeps its loopback
    GET /health, which speaks to the server's own listener.
  • The Licensor and copyright holder of the project's own work is Vernum
    Projecten B.V. (#569). Every Licensor:, copyright and
    SPDX-FileCopyrightText line names the company. The licence terms are
    unchanged, and maintainer credit stays a person.

Security

  • rustls moves to 0.23.45 for RUSTSEC-2026-0285, TLS 1.3 handshake
    messages incorrectly accepted across encryption level boundaries (#576).
    The lockfile moves; no source changes.