Skip to content

ValueSet R5→R4/R3 down-conversion leaks the R5-only 'descendent-leaf' filter operator (leading-space typo) #249

@jmandel

Description

@jmandel

Summary

On ValueSet R5→R4/R3 down-conversion, the R5-only filter operator descendent-leaf leaks through unchanged into the R4/R3 output (invalid FHIR), because of a leading-space typo in the R5-only-operator list. The sibling operator child-of (no typo) is stripped correctly.

// tx/xversion/xv-valueset.js — isR5OnlyFilterOperator
const r5OnlyOperators = [ 'child-of', ' descendent-leaf' ];  // <-- leading space on entry 2

'descendent-leaf' never matches ' descendent-leaf', so valueSetR5ToR4/R3 never strips it.

Spec basis

Published http://hl7.org/fhir/ValueSet/filter-operator:

  • R4 (4.0.1): =, is-a, descendent-of, is-not-a, regex, in, not-in, generalizes, exists
  • R5 (5.0.0) added: child-of, descendent-leaf

So op: "descendent-leaf" is not a valid R4 FilterOperator — emitting it in an R4 ValueSet is invalid FHIR. The converter is meant to strip it (and demonstrably does for child-of).

Reproduction

Reproduced on a local instance of FHIRsmith v0.9.5 (the published source). The public store is read-only, so the triggering content is supplied inline; the bug is in the outbound response converter (res.jsontransformResourceForVersionvalueSetFromR5valueSetR5ToR4), reachable on any /r4 ValueSet response. Supplying a ValueSet that already carries an expansion makes $expand return it early (no filter execution) while includeDefinition=true keeps compose.

PostedPOST /r4/ValueSet/$expand?includeDefinition=true:

{"resourceType":"Parameters","parameter":[
 {"name":"valueSet","resource":{"resourceType":"ValueSet","url":"http://example.org/vs/f24","status":"active",
   "compose":{"include":[{"system":"http://snomed.info/sct","filter":[
     {"property":"concept","op":"child-of","value":"73211009"},
     {"property":"concept","op":"descendent-leaf","value":"73211009"}]}]},
   "expansion":{"timestamp":"2026-06-06T00:00:00Z","total":0,"contains":[]}}},
 {"name":"includeDefinition","valueBoolean":true}]}

Observed (response compose.include[0].filter):

[
 { "property":"concept","value":"73211009",
   "_op":{"extension":"...extension-ValueSet.compose.include.filter.op","valueCode":"child-of"} },
 { "property":"concept","op":"descendent-leaf","value":"73211009" }
]
  • child-ofcorrectly stripped (moved to _op) → proves the strip path runs.
  • descendent-leafleaks as op:"descendent-leaf" (invalid R4) → isolates the typo as the cause.

(Baseline: POSTing the same body to /r5/...$expand returns both ops verbatim — correct for R5.)

Expected

On R4/R3 downgrade, descendent-leaf should be stripped from op (like child-of), so the R4 output contains no R5-only operator.

Source / notes

  • tx/xversion/xv-valueset.js isR5OnlyFilterOperator (the ' descendent-leaf' typo).
  • The TS port preserves this verbatim (documented).
  • Related (separate, not reproduced here): the CodeSystem converter tx/xversion/xv-codesystem.js uses a different R5-only set — it lists generalizes (valid in R3/R4/R5, so wrongly stripped) and omits child-of/descendent-leaf — worth a look while in this area, though it isn't injectable over HTTP in this build.

Reproduced on a local FHIRsmith v0.9.5 instance, 2026-06-07; not run against the public tx.fhir.org store (read-only).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions