Skip to content

0.0.9

Choose a tag to compare

@github-actions github-actions released this 05 Oct 18:14
· 261 commits to main since this release
Immutable release. Only release title and notes can be modified.
v0.0.9
95659ac

Added

  • The operator views of the operator console (#276). After sign-in, which
    now exchanges the code at the provider's token endpoint with the PKCE
    verifier and checks the ID Token's signature, issuer, single audience,
    expiry and nonce before a session begins (the new [oidc] keys token_endpoint and
    jwks_uri), the console shows the member endpoints with their membership
    standing and last observed health (/members), the integrity incidents
    and the creating_system_id routing table (/integrity), the stored
    queries with their AQL (/stored-queries), and the gateway's
    self-description (/federation), each rendered on the server from the
    gateway's surface with the operator's own token. A view without a
    signed-in session sends the browser to sign-in, and a refusal shows the
    gateway's status and stable error code, a 401 with a link back to
    sign-in. The routing table and the stored queries are paged, 100 rows a
    page. With secure_cookie on, the console's cookies carry the __Host-
    prefix, and a new sign-in ends the session the browser held.

  • A read-only operator surface on the gateway: GET {base}/operator/incidents
    (each incident kind's count and the last 25 incidents of each kind),
    /operator/creating-systems (the routing table) and
    /operator/stored-queries (every held version, as the ITS-REST
    StoredQuery), admitted only for a token carrying the operator_scope its
    [[auth.issuer]] entry names as one whole scope token. The last two answer
    one page, {"items", "offset", "total"}, under the optional offset and
    limit parameters, with limit at most 100. It answers
    routing ids, counts and stored definitions only, and an incident names an
    ehr_id only when it is a bare UUID (§5.4.1, N33).

  • ferrofed conformance run --node-profile reports CP-18, CP-19 and CP-27
    against every active member (#573; §16.2; N26, N27, N34). The
    Federation-Node profile checks are the gateway's own: each member is read
    through its node client, with its onward credentials and the outbound
    gate, for the patient's EHR by its ehr_id alone, an EHR created with no
    subject, and its error statuses for an unknown EHR and an unparsable
    query, and the admission check runs as before. The access and consent
    checks need a refusal arranged with the node's own policy, so a run
    records them not observable with that reason. The harness runs the same
    checks against FerroEHR and EHRbase. The run also scores a new track 3
    scenario: the ENDPOINT attributes a client selects are each row's
    endpoint id and system_id (§9.4, N12, CP-37).

  • Every release attaches the conformance seed data,
    ferrofed-conformance-seed-data.json, with its SHA-256 in
    ferrofed-conformance-seed-data.json.sha256sum (#573). The file holds the
    vendored template and compositions conformance run writes, with their
    Apache-2.0 licence and notice, and --seed-data takes it in place of the
    vendored directory, checking each file it holds by SHA-256.

  • A mutual-TLS token endpoint alias on a host you name (#560; RFC 8705 §5).
    A node's [credentials."<id>"] section takes mtls_alias_hosts, each a
    host name with an optional port, and a fapi2 grant that uses mutual TLS
    then takes a token_endpoint of mtls_endpoint_aliases on one of those
    hosts, over https alone, as well as on the issuer's origin. An alias on
    any other host is still refused before a token request is sent. An entry
    that is no host, or the key in a section with no fapi2 grant over mutual
    TLS, refuses the configuration, naming the key. oauth-server-metadata
    0.0.2 adds AliasHost and Issuer::mtls_alias.

  • Mutual TLS toward a node and its authorization server, with RFC 8705
    client authentication and certificate-bound tokens (#492; §13.1, §13.4,
    N25, CP-17; FAPI 2.0 Security Profile §5.3.2.1). A node's
    [credentials."<id>"] section takes client_identity_file and
    trust_roots_file, the keys and the TLS type every identity service
    takes, and the node and its token endpoint are reached over one transport
    that presents that certificate, https under every profile; a section
    may name that material alone. An oauth2 grant, and a fapi2 grant, may
    authenticate with client_auth = "tls_client_auth" or
    "self_signed_tls_client_auth", sending client_id and no assertion
    (RFC 8705 §2), and with tls_client_certificate_bound_access_tokens = true take only tokens bound to the certificate (§3): a token whose cnf
    names another certificate, in the token response or as a JWT claim, is
    refused before the node is sent anything (§3.1, §3.2). It composes with
    token exchange per caller, whose actor token the gateway still signs. A
    fapi2 grant over mutual TLS uses the mtls_endpoint_aliases token
    endpoint (§5), needs the metadata to list tls_client_auth and state
    tls_client_certificate_bound_access_tokens as the grant uses them, and
    needs a client key only for a token exchange. A grant that sets both dpop_key_file and certificate binding,
    either without a client identity, an identity with no certificate, an
    http token endpoint or node, and TLS material in a service's own
    credentials section refuse the configuration. RFC 8705 is vendored
    under docs/specs/ietf-oauth/.

  • The operator console's skeleton, app/ferrofed-viewer (#275): a Leptos app
    rendered on the server and hydrated in the browser, with a
    backend-for-frontend, as its own binary, ferrofed-viewer, and its own
    image, ghcr.io/ferrohealth/ferrofed-viewer. It reaches the gateway over
    HTTP alone, as any client does, and holds no clinical data. Built so far: a
    landing page, GET /health and the healthcheck command, operator sign-in
    at an OpenID Provider with the authorization code grant, a nonce and
    PKCE, its state, nonce and verifier held on the server behind an
    opaque HttpOnly cookie in a bounded, short-lived pool of pending
    sign-ins that a flood of login starts cannot grow and that never touches
    the separate, bounded pool of signed-in sessions with their idle and
    absolute timeouts, configuration refusals that name a key and a line and
    never a value, and a typed client of the gateway's OPTIONS {base}/
    and ITS-REST surface called with the operator's own token. Every answer
    carries a per-response script nonce in its Content-Security-Policy. The
    console reads a TOML file with FERROFED_VIEWER__ overrides and a
    client_secret_file. The code exchange, the operator views (#276) and the
    query console (#277) are planned. The release lane builds, attests and
    pushes the image with the same SLSA provenance and SBOM attestations as
    the gateway image, CI lints the browser half for wasm32 and builds the
    site bundle, and the book's "The operator console (planned screens)" page
    says what is built and what is planned.

  • The NVI localizer authenticates with the Nuts grant (#539; Annex B §B.1,
    §B.4; the IG's Localization page, GF-Authentication, GFI-004 and
    GFI-005). [nl_gf.nvi.credentials.nuts] takes the table a node's onward
    credentials take and runs the same grant: a Verifiable Presentation of the
    gateway's credentials for a token bound to its DPoP key, cached until 30
    seconds before it expires and dropped on the NVI's 401. Every search
    carries the token with a proof of the key over the search URL without its
    query, and a nonce the NVI demands is answered once (RFC 9449 §7.1, §9).
    The token goes to the NVI alone. A refused grant leaves the localization
    unavailable, so the query fails closed (§14.1). An oauth2 or fapi2
    table under [nl_gf.nvi.credentials] is refused, naming its key, since the
    IG defines no such grant for the Localization Service. The
    nl-generic-functions NVI client takes an authorizer that makes each
    request's headers and is handed the request URL without the query that
    holds the pseudonym (0.0.12).

  • ferrofed conformance run --config <file> scores a configured deployment
    against the Connectathon tracks 1 to 7, 9 and 11 over ITS-REST (#546;
    §16.3, §16.4). It starts the gateway in-process from the configuration, or
    drives one already serving at --gateway <url>, as a client holding a
    bearer token from --token-file. It seeds a synthetic patient in the
    urn:oid:2.999 arc at every member the deployment's own cross-reference
    names, through each node's ITS-REST writes: the patient's EHR, the vendored
    template and one vendored composition, three EHRs with no subject, and the
    writes the scenarios make. It reads no EHR it did not create and removes
    nothing. It refuses to start without --allow-writes, against a profile
    other than development without
    --i-understand-this-writes-synthetic-data-to-the-nodes, for a patient
    outside the example arc, and for seed files other than the vendored ones,
    which it checks by SHA-256. The --gateway URL and every member endpoint
    are held to https, or http to a loopback host under the development
    profile alone, which the report names; the gateway client follows no
    redirect, and the caller's token is held as a secret that no report, log
    line or error carries. It writes the per-track and per-point report the
    harness run writes, with the same columns and results, and reports every
    scenario that needs fault injection, node-side capture or a gateway
    configured for it not-run with the reason, never pass; --node-profile
    adds the admission check of every member as the node profile findings. The
    end-to-end tracks call the same scenario checks, and a run against the
    harness reads what the harness scores and nothing more.

  • An end-to-end check that the AVG the gateway declares decomposable in
    OPTIONS {base}/ is the mean weighted by each node's count (§11.6.3, N39,
    CP-10, CP-32), behind FERROFED_E2E. With two values at one FerroEHR node
    and one at the other, each node is asked its SUM and COUNT, and the
    answer equals the mean weighted by the counts each node returns for the
    query it was sent, which differs from a mean of the node means.

  • The gateway serves the did:web DID document of each Nuts grant's
    holder (#503; Annex B §B.4, Nuts RFC021 §4.2). It is served at the path
    the DID resolves to (the did:web Method Specification, Read (Resolve)),
    such as /.well-known/did.json, under any base URL, as
    application/did+ld+json and with no client authentication, like the JWK
    Set. The document is built from the holder keys the nuts sections name
    and nothing else, one JsonWebKey2020 verification method per kid, so
    a key change shows up on the next reload with no file to edit. A DID
    whose document would sit under {base}/v1/, two DIDs that share one
    document path, or two keys under one kid refuse the configuration.
    nl-generic-functions 0.0.13 adds the nuts_auth::did_document module,
    HolderKey::public and Did::document_path.

  • A differential run against the Federation Tier reference implementation,
    behind FERROFED_E2E (#94; §16.3 tracks 1 to 7 and 9). The testkit builds
    syntaric/openehr-federation-ref at its pinned commit from the vendored
    source and the manifest held to its recorded sha256, on digest-pinned Maven
    and Java images, and runs it beside FerroFED over the same two FerroEHR
    nodes, each gateway behind capturing proxies of its own. Some thirty
    requests go to both, and the run compares the status, the federation
    headers, the envelope against the vendored schema, meta.federation, the
    rows and what reached each node, after normalising only what the
    specification leaves free. Every difference is held in a register with its
    verdict and where it is recorded: a new or vanished difference fails the
    run. The e2e (containers) job uploads the Markdown and TSV report as the
    differential-report artifact. No difference is a FerroFED defect; three
    reference divergences and one specification question are new upstream
    reports (#212 T190 to T193).

  • A fapi2 grant's client key rotates with an overlap (#514): the new
    previous_client_key_file names the previous P-256 client key, which
    the JWK Set at {base}/.well-known/jwks.json publishes after the current
    key until it is removed from the configuration. Only the current key
    signs. An authorization server that fetches the set during the rotation
    verifies both the new assertions and one the previous key signed before
    it (FAPI 2.0 Security Profile §5.4.2). A previous key that is no P-256
    key, or is the current key, refuses the configuration, naming the key.

  • [signing] holds a P-256 key as well as a P-384 key, and reads the
    algorithm from the key: a P-256 key signs ES256 and a P-384 key ES384
    (RFC 7518 §3.4) (#513). The current key signs the
    openEHR-federation-client conveyance and every oauth2 client assertion
    with its own algorithm, and the JWK Set publishes each key with its alg,
    through the same previous-key rotation window, which may cross curves. A
    federation whose nodes hold to the FAPI 2.0 Security Profile, which admits
    ES256 and not ES384 (§5.4.1), signs with a P-256 key. A key on any other
    curve refuses the configuration, naming the key.

  • The Federation-Node profile runs against a second CDR product, EHRbase,
    behind FERROFED_E2E (#549; §16.2, N26, N27, N34, CP-18, CP-19, CP-27,
    CP-33a). The testkit pins ehrbase/ehrbase 2.36.0 and the
    ehrbase/ehrbase-v2-postgres 16.2 database image by digest, each a
    docs/VERSIONS.md row the versions guard compares, and every node profile
    check runs against it in the e2e (containers) job and writes its findings
    beside FerroEHR's. For the access check (CP-18) an EHRbase started with
    Basic authentication withholds the request path of one EHR from its user
    role, the narrowest refusal its security.additionalAuthorizations
    expresses; it records no consent decision, so the consent check (CP-19)
    reports not observable with that reason. The conformance report shows the
    findings of each product side by side under the node class, then each
    product's evidence. EHRbase passes the error pass-through and subjectless
    EHR checks. It refuses an EHR_STATUS subject in a namespace such as
    urn:oid:2.999.1.1, which the BASE OBJECT_REF.namespace pattern admits.
    The admission check therefore creates no test EHR there, and its integrity
    conditions fail on that cause. Its EHR read omits ehr_access, which the RM
    EHR class makes mandatory. As at FerroEHR, both ehr_id-scoped query
    forms release the EHR whose read the node refuses. The invocation finding
    names why an EHR body does not decode. No specification governs which
    products the harness runs: our own design.

  • Each IHE audit record the gateway makes for a client's request names the
    verified caller (#500; PIXm §2:3.83.5.2.1, BALP 1.1.4 §3:5.7.5.4, ITI TF-2
    §3.55.5.1.1, IUA ITI TF-2 §3.72.5.1). The ITI-83, ITI-78 and ITI-119
    AuditEvent carries the BALP agent:user with the token's iss and
    sub, requestor true and its purposes of use, and an Application agent
    with its client_id; the ITI-55 DICOM message carries the Human Requestor
    with the sub as UserID and aud<sub@iss> as UserName, as its one
    requestor. A record the gateway makes on its own behalf (the admission
    check, the mCSD directory reads, the PMIR subscription and feed) names no
    caller. The identity reaches the Audit Record Repository alone: the log
    destinations record only whether a record was made for a caller or by the
    gateway, and no metric carries it.
    ihe-iti 0.0.23 takes whom each PIXm, PDQm and XCPD exchange is made for
    (ihe_iti::user::OnBehalfOf), and the resolver, localizer and
    demographics roles take it too. An ITI-83 answer a localization read is
    reused only by the resolution of the same caller, so another caller's
    access sends, and records, an ITI-83 of its own. The security event
    edge-identity-asserted names the caller by subject_ref and
    client_ref, keyed HMAC-SHA256 references stable for the process, in
    place of the subject and the client, and a verified caller's Debug
    shows neither them nor the issuer.

  • Track 11, the integrity suite, against the two FerroEHR nodes of the
    harness behind FERROFED_E2E (#91; §16.3 track 11, §12.5.1, §12.5.2,
    §12b, N41, N42, N42a, CP-33, CP-33a). The harness seeds one ehr_id at
    both nodes for two different synthetic patients. A read and a query scoped
    to it are refused 409 ehr-id-collision listing both claimants, with one
    integrity incident, and the proxy journals show the ask-all probe as the
    only request either node received. Once two targeted reads teach the
    ehr_id index both claimants, which raises the index-insert alarm once, a
    read and two writes are refused the same way and reach neither node. A
    versioned write no earlier step of §12.5.1 routes is refused 400
    target-required and probes nobody, although its If-Match names node
    A's system_id. As the federation operator, the harness then applies the
    admission check to a FerroEHR candidate deployed with member A's
    system_id: recorded under that system_id, the registry load refuses it
    as a duplicate, and recorded under one of its own, the check fails the
    system_id condition naming member A. The conformance matrix records
    track 11 as covered.

  • The Connectathon tracks 1 to 7 and 9 of §16.3 run as scenarios in the
    end-to-end harness over two FerroEHR nodes: transparency, resolution
    through the PIX Manager, the directed query and the endpoint pin, partial
    results under node faults, de-duplication and cross-node shaping,
    follow-up routing, onward authentication with consent left to the node,
    and the REST surface with its self-description. Track 8 stays deferred,
    as the specification marks it provisional (#92).

  • scripts/conformance/report.sh writes the report of §16.4 from a test
    run: every track and every conformance point as pass, fail, not run,
    deferred, not applicable or open, with the issue and reason of each
    deferral. CI writes it from the offline and the container runs and uploads
    it as the conformance-report artifact (#92).

  • The Federation-Node profile of §16.2 as operator and harness tooling
    (#93; N26, N27, N34, CP-18, CP-19, CP-27). The testkit's node_profile
    checks what a member CDR's ITS-REST interface shows of each node
    obligation. They check that an EHR, its EHR_STATUS and its compositions
    answer to the ehr_id alone, that an EHR created with no subject is read
    and queried by its ehr_id, and that an unknown ehr_id and an
    unparsable query answer the ITS-REST error status. They also check that an
    EHR the node's own policy withholds is refused on the read and on both
    ehr_id-scoped query forms, and that a consent refusal an operator
    arranges is held the same way. Each check records pass, fail or
    not-observable with its evidence. The end-to-end run executes every check
    against FerroEHR, including a FerroEHR started with Basic authentication
    and restricted per-EHR access. It also runs the admission check of §12b.2
    against a FerroEHR member. The conformance report shows the Node and
    Operator points as their own class, scored from those findings and never
    as a gateway pass, and lists every finding per product.

  • A PIX Manager can be asked by POST (#494). [[pixm.manager]] takes
    method = "post" beside "get", the default: the gateway then posts the
    ITI-83 sourceIdentifier and targetSystem parameters in a Parameters
    body to [base]/Patient/$ihe-pix (FHIR R4 Operations §3.2.0.1), so the
    patient identifier is in no request URL a proxy or an access log records.
    get stays the default because PIXm 3.1.0 §2:3.83.4.1.2 prescribes the
    GET and requires no Manager to accept a POST. Any other value refuses
    the configuration naming the key. The ITI-83 audit record of a POST holds
    its request line, media type and body. ihe-iti 0.0.22 adds
    pixm::Invocation and PixmClient::invoked_by, and the harness PIX
    Manager answers a posted query.

  • Each regional binding is one Cargo feature of ferrofed-server:
    binding-ihe carries the IHE binding of Annex A ([pixm], [pdqm],
    [xcpd], [registry.mcsd], [pmir] and [audit]) and binding-nl the
    Dutch Generic Functions of Annex B ([nl_gf] and the Nuts grant of
    [credentials."<id>".nuts]); [dev] is always built. Both features are on
    by default, so the released binary and the container image behave as
    before. A build without one refuses its sections as unknown keys (#489).

  • The book has a page on adding a country: a specification crate, a binding
    module and a feature line (#489).

  • The query console of the operator console (#277), at /query. An AQL
    query, or a stored query by name and optional version, runs through the
    gateway's POST {base}/v1/query/aql or /v1/query/{name} as the
    signed-in operator, with the targeting, dedup and best-effort headers the
    gateway's self-description offers. The answer shows the rows, every
    endpoint's status, latency and error from meta.federation, and says in
    words when complete is false; a 504 or 424 all-or-nothing failure
    shows its diagnostic envelope, and a refusal its status and stable code.
    A patient is named through a parameter in the request body, and nothing
    entered in the console reaches a URL, the browser history or a log. A
    query runs once the page has loaded; its answer is rendered on the server
    with every value a node sent escaped, and is never cached or compressed.

  • Operator sign-out in the operator console (#584). The navigation bar's
    "Sign out" posts to POST /logout, which ends the server-side session,
    removes the session cookie and, where the new [oidc] key
    end_session_endpoint names the provider's end-session endpoint, sends
    the browser there with the ID Token hint, the client id and the optional
    post_logout_redirect_uri (OpenID Connect RP-Initiated Logout 1.0). The
    sign-out and every server function the views and the query console call
    are taken only from the console's own pages (Sec-Fetch-Site, Origin
    or Referer); any other request is a 403 before the session is read.

  • Browser journeys for the operator console (#608): headless Chrome, driven
    over WebDriver from Rust with thirtyfour, signs in at a test OpenID
    Provider, opens each operator view and follows its pagers, runs a query
    that shows every node's status and whether the answer is complete, sends a
    query the gateway refuses, and signs out, against a running gateway over
    stub nodes and the console serving its release site bundle. Each journey
    fails on any error the browser logs. They run behind
    FERROFED_JOURNEYS=1 in the new journeys (browser) CI job, at the Chrome
    for Testing release docs/VERSIONS.md pins.

  • The conformance statement (#95, #89): a book page under Evaluate that claims
    the Federation-Gateway profile of the Federation Tier with AQL 0.9.0 at
    commit 7162d0c, with every §17 point and §16.3 track and how each is
    scored, every deferral with its actor and reason, and the FerroEHR and
    EHRbase images the tests ran against. Its generated part and the new page of
    marked tests come from scripts/conformance/matrix.sh --statement-write,
    and the docs build and the conformance-matrix guard fail when either
    disagrees with the matrix. The guard now refuses a planned point or
    track, so a re-pin scores or defers every point it adds. Six rows of the
    obligations checklist that still read planned after their work shipped now
    read tested and name their tests.

Changed

  • The Licensor and copyright holder of FerroFED's own work is Cadasto B.V., replacing Vernum Projecten B.V. (#602). The licence, its Additional Use Grant and its Change Date do not change. A new guard, scripts/checks/copyright-holder.sh, fails any first-party header that names another holder.

  • The openehr-* family moves from 0.0.83 to 0.0.84 (FerroEHR #3565,
    #574). A re-send answering a node's DPoP nonce challenge that no proof
    could be made for is the node's node-error, with the node counted as
    asked, read from the client's DpopProof { sent }. The node clients no
    longer keep a record of each call's sends. openehr-federation 0.0.40
    takes the new family.

  • The openehr-* family moves from 0.0.82 to 0.0.83 (FerroEHR #3560,
    #566). A deadline that passes before the re-send answering a node's
    DPoP nonce challenge is the node's time-out, with the node counted as
    asked, read from the client's DeadlineElapsed { sent } in place of the
    call's prover record (#470). The record still reads a re-send that no
    DPoP proof could be made for, which the client reports with no such
    field. openehr-federation 0.0.39 takes the new family.

  • The harness and the quickstart run FerroEHR 4.3.3, pinned by digest at
    its new home under ghcr.io/ferrohealth, node and database image alike
    (#566). That release makes AQL honour EHR_ACCESS on every query form
    (FerroEHR #3562), which the node profile's "access decided at the node"
    check (CP-18) reads.

  • A consent pre-filter that did not ask its service says so, with a closed
    reason, in place of answering as one that was asked and denied nothing
    (#496). Mitz is not asked about a patient named outside the BSN, such as by
    a pseudonymised BSN (reason namespace), or for a caller whose token does
    not state the requester claims (reason caller-claims). Such a call is
    counted in ferrofed_consent_prefilter_requests_total with
    outcome="not-asked" and its reason, no longer as no-signal, and it
    leaves the pre-filter's state on GET /health/dependencies as it was.
    The client's answer is unchanged: every candidate is asked, nothing appears
    in meta.federation.consent, and under [federation.consent] disclose = false the answer is the one a pre-filter that found nothing gives.

  • Two sections that fill a role exactly one may, two resolvers, two consent
    pre-filters or two localizers of a binding's own, are refused with one
    error that names every section, such as [nl_gf.nvi] and [xcpd] are both localizers; set one, in place of one error per pair. A localizer section
    under node_selection = "ask-all" is refused naming its section, as
    before (#489).

  • The configuration resolved log line names the compiled bindings, and
    each binding logs what it is configured to reach on a binding configured
    line of its own; the registry directory, PIX Manager, PMIR and PDQm fields
    move from the first line to the IHE binding's (#489).

  • A build without a binding feature compiles none of that binding's crate:
    binding-ihe and binding-nl turn on new ihe and nl features of
    ferrofed-identity and an nl feature of ferrofed-engine, so
    cargo build -p ferrofed-server --no-default-features builds neither
    ihe-iti nor nl-generic-functions. The registry document in FHIR form
    (registry.format = "fhir"), read with the mCSD reader, is part of
    binding-ihe (#551).

  • The authorization server metadata checks of RFC 8414 move out of
    nl-generic-functions into a crate of their own, oauth-server-metadata
    0.0.1, which the OAuth 2.0 issuer audience, the FAPI 2.0 grant and the
    Nuts grant share. nl-generic-functions 0.0.11 drops its
    oauth-metadata feature (#551).

  • An error that needs a cross-reference resolver names the resolvers the
    build carries, [dev] or [pixm] by default and [dev] without
    binding-ihe (#551).

  • [federation.consent] disclose = false keeps a consent exclusion out of
    what a client sees, for Regulation (EU) 2025/327 Art 8, under which the
    fact that a patient restricted access "shall not be visible to healthcare
    providers". A member the consent pre-filter excludes is still never sent a
    request, and a federated query reports it not-resolved, with the same
    error as a member the cross-reference does not know the patient at, so
    meta.federation.complete stays false and nothing fails. A read of an
    EHR by subject that only an excluded member could serve answers
    404 subject-unavailable, the same answer as for a subject no member
    knows, a new code in the error vocabulary. OPTIONS {base}/ declares the
    choice as federation.consent.disclose, the pre-filter metrics still count
    every exclusion for the operator, and the default stays the
    specification's consent-denied (N27a) (#493). The setting covers a
    node's own consent refusal too, a 403 with a code the registry lists in
    consent_refusal_codes: a federated query reports the node not-resolved
    with no latency_ms, and a read by subject, a routed request and an
    ask-all probe answer 404 subject-unavailable. On a request under
    {base}/v1/ehr/, the creation of an EHR and a DEMOGRAPHIC request, a
    node's own 404 gets that same answer in this mode, so it never stands
    for a refusal alone; every subject-unavailable carries one fixed
    message. The node request metrics now count such a refusal as
    consent-denied on every path, whatever the setting.

  • nl-generic-functions 0.0.16 adds BSN_SYSTEMS and is_bsn_system to
    identification, beside PSEUDO_BSN_SYSTEM and URA_SYSTEM: the naming
    systems of the BSN itself, the IG's $bsn system and its OID as a URN and
    dotted (#587).

  • The changelog is written as fragments (#598). A pull request with a
    user-visible effect adds one file under changelog.d/, named
    <issue>-<kebab-slug>.<section>.md and holding its entry, instead of editing
    CHANGELOG.md, so two pull requests no longer conflict on one file. The
    release cut runs scripts/release/changelog.sh --assemble <version> <date>,
    which writes the fragments and the entries already under [Unreleased] into
    the new version's section and removes the fragments. The new changelog
    job of CI runs changelog.sh --check over every fragment, and the
    changelog-guard job fails a pull request that adds no fragment and leaves
    CHANGELOG.md untouched, unless it carries the no-changelog label.

  • The operator console serves its site bundle brotli- or gzip-compressed, as
    the browser's Accept-Encoding chooses, with Vary: Accept-Encoding, so a
    browser downloads about 220 KB of WebAssembly where it downloaded 770 KB;
    pages and server function answers stay uncompressed (#600). The release
    bundle names no directory of the host that built it: the build remaps the
    cargo home, the toolchain and the checkout, and CI fails a bundle that
    names a home, runner, registry or toolchain path.

  • ferrofed-identity names the PMIR subscriber's error ihe::pmir::PmirConfigError,
    after its module, where it was LifecycleConfigError, and the log target both
    audit log recorders write to is ihe::audit::AUDIT_TARGET, where it was
    ihe::xcpd::AUDIT_TARGET; its value, ferrofed::audit, is unchanged. In
    nl-generic-functions 0.0.18, mitz::BSN_ROOT takes its value from the BSN
    OID of identification::BSN_SYSTEMS, which is unchanged (#606).

  • The operator console refuses to start with [session] secure_cookie = false
    unless its [oidc] redirect_uri is an http URL on a loopback host
    (localhost, 127.0.0.0/8 or ::1), so a deployed console never sends its
    session cookie over plain HTTP; the refusal names session.secure_cookie
    (#615).

  • A commercial licence, and any other business or licensing question, goes to
    Cadasto B.V., the Licensor, at info@cadasto.com or
    https://www.cadasto.com/contact/ (#616). Every LICENSE copy names that
    contact where it named the maintainer, so openehr-federation 0.0.42,
    ihe-iti 0.0.25, nl-generic-functions 0.0.17 and oauth-server-metadata
    0.0.4 carry it. The maintainer stays the contact for technical questions.
    The licence terms do not change.

Fixed

  • A caller whose token states the requester claims in a form the Mitz
    question does not take, or a patient value Mitz does not take as a BSN,
    is no longer reported as a Mitz outage, although Mitz was never asked
    (#568). The pre-filter answers that it did not ask, with the reason
    caller-claims-invalid or patient-value, so the call is counted as
    not-asked, GET /health/dependencies keeps the pre-filter's state, and
    the client's answer carries no meta.federation.consent.error. No claim
    value reaches a label, a log line or an error.
  • A stored-query definition that a PostgreSQL or redb store holds is now
    checked on every read, as the files backend checks each file (#586). A
    row written past the gateway, by a restore, a manual insert or an older
    version, that names its patient by a literal or does not admit as a
    definition is never served or run: the gateway refuses to start over a
    store holding one, and over a shared PostgreSQL store a read, a listing or
    an invocation that reaches such a row answers 500. The security log
    records a held-definition-refused event naming the definition's name and
    version, never its text.
  • The operator console's sign-out reaches the provider's end-session
    endpoint in the browser: the Content-Security-Policy's form-action now
    names that endpoint's origin beside the console, where before the browser
    blocked the redirect the sign-out form is answered with (#608). The console
    also serves the brand favicon at /favicon.ico, which every browser asks
    for on its first page and which answered 404 before.
  • Every operator console page the gateway fills is whole in the HTML the
    server sends, so it reads with no script: the pages render once every
    answer is in, where before a page could arrive with its loading notice and
    the content in a template that only a script moved into place (#608). A
    query or a view the gateway refuses, and a query form the console cannot
    send, are shown with the gateway's status and code or the field at fault
    as before, and the browser no longer logs them as a failed request.