Repository navigation
0.1.4
·
57 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
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), onPOST /run, or asferroterm-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-buildinto 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 forPOST /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
ntssource kind.docker/Dockerfile.synccarries the service and the
builder on the same distroless base, andcompose.sync.yamlruns 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). Withfhir_base_urlset it reads every locally authored
ValueSetandConceptMapover the server's public FHIR API, with
ValueSet/$validate-codeandValueSet/$expandand theiractiveOnly
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/metricscarriesferroterm_sync_revalidation_findingsas 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_ISSUERturns 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 onPOST,PUT, andDELETEof
CodeSystem,ValueSet, andConceptMapon 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.cto create,.uto update,.dto delete, the
combinedcudandcrudsforms accepted), and the version 1.writeand
.*forms are accepted as the specification maps them
(https://hl7.org/fhir/smart-app-launch/scopes-and-launch-context.html);
FERROTERM_OIDC_ADMIN_SCOPEnames the scope the admin listener requires. A
request with no token answers401with aWWW-Authenticate: Bearer
challenge, a token that does not verify answers401with
error="invalid_token", and a token without the scope answers403with
error="insufficient_scope", each with anOperationOutcome(RFC 6750 §3).
The signature is checked against the issuer's JWKS with an asymmetric
algorithm only, andiss,exp, andnbfare required, as isaudwhen
FERROTERM_OIDC_AUDIENCEnames one; atypheader 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 ishttps, and a cleartext one is refused unless its
host is the loopback.
[base]/.well-known/smart-configurationis served per version from the
issuer's document, and the capability statements declareSMART-on-FHIRwith
theoauth-urisextension
(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).
SIGHUPand
POST /reloadboth make it readFERROTERM_INDEXandFERROTERM_CODESYSTEMS
again, open everything read-only, and swap the whole registry; a request in
flight finishes on the set it started on.POST /reloadis served by an admin
listener bound toFERROTERM_ADMIN_LISTEN, which serves nothing else and is
absent when the variable is unset; the FHIR listener answers/reloadwith
thenot-foundOperationOutcomeany 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 a500naming it.
FERROTERM_RESOURCESis carried over rather than reopened./metricsgains
ferroterm_reloads_total{outcome="ok"|"failed"}, and
ferroterm_code_system_loadednow describes the set being served rather than
the set loaded at start. - A concept of a
CodeSystemresource the server loaded or a client persisted
is inactive when its standard concept properties say so (#584).inactive = true, astatusofretired, and aretirementDatethe request is behind
each retire the concept;deprecated, adeprecationDate, and astatusof
deprecatedleave 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).$expandwith
activeOnly=truethen leaves the concept out andactiveOnly=falselists it
withcontains.inactive = true,$validate-codeanswersresult = truewith
inactive = true, and$lookupreturns 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
retirementDatethat is not a readabledateTimereads as a retirement
already in force, and thestatusoutput of$validate-codereports 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. TheSourcetrait 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_INDEXmay name an index root as well as a single
artifact (#592). A path holding nomanifest.jsonis 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 keepsaddons/*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 fromcargo metadata
and fails when a member outsideapp/ferroterm-syncdepends on an add-on, or
when an add-on depends on a workspace crate other than
crates/terminology-syndicationand the leaf crates it names by hand. A dev
dependency is test scaffolding and stays legal.--self-testjudges 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 --allnow 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 newaddons/*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 whoseexperimentalelement 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-types0.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): adateTime
that is no date, acodewith a leading space, auriwith whitespace. On
the write path aPOSTorPUTof such a resource answers400 invalid
with the element path inissue.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 adateTimecarries 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, orfalse, 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 pastdeprecatedor
deprecationDate) earns theDEPRECATED_CONCEPT_FOUNDwarning 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, inconcept-store(#597); the
testkit'ssctidtakes a typedItem, so a bare ordinal no longer
compiles where an item number belongs (#594). - Repository hygiene, in one change: the
versions.shcheck reads the one
published-crate list fromscripts/release/publish-crates.shand 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, andscripts/checks/vendor-dests.shfails when a
fetcher's destination is missing; every workspace member is named in the
repo map and the architecture table, kept so byscripts/checks/repo-map.sh;
docs/claims.mdcites symbols and tests instead of line numbers, kept so by
scripts/checks/claims.sh; the install page andcompose.yamlmount an
index root; zizmor moves to 1.30.1 with itsself-repositoryaudit 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 theicd11crate'sapifeature, which only the offline build
tool enables, soreqwestleaves the server's dependency tree.
scripts/checks/no-client-in-server.shreads 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). EveryLicensor:, copyright and
SPDX-FileCopyrightTextline names the company. The licence terms are
unchanged, and maintainer credit stays a person.
Security
rustlsmoves 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.