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.json → transformResourceForVersion → valueSetFromR5 → valueSetR5ToR4), 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.
Posted → POST /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-of → correctly stripped (moved to _op) → proves the strip path runs.
descendent-leaf → leaks 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).
Summary
On
ValueSetR5→R4/R3 down-conversion, the R5-only filter operatordescendent-leafleaks through unchanged into the R4/R3 output (invalid FHIR), because of a leading-space typo in the R5-only-operator list. The sibling operatorchild-of(no typo) is stripped correctly.'descendent-leaf'never matches' descendent-leaf', sovalueSetR5ToR4/R3never strips it.Spec basis
Published
http://hl7.org/fhir/ValueSet/filter-operator:=, is-a, descendent-of, is-not-a, regex, in, not-in, generalizes, existschild-of,descendent-leafSo
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 forchild-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.json→transformResourceForVersion→valueSetFromR5→valueSetR5ToR4), reachable on any/r4ValueSet response. Supplying a ValueSet that already carries anexpansionmakes$expandreturn it early (no filter execution) whileincludeDefinition=truekeepscompose.Posted →
POST /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-of→ correctly stripped (moved to_op) → proves the strip path runs.descendent-leaf→ leaks asop:"descendent-leaf"(invalid R4) → isolates the typo as the cause.(Baseline: POSTing the same body to
/r5/...$expandreturns both ops verbatim — correct for R5.)Expected
On R4/R3 downgrade,
descendent-leafshould be stripped fromop(likechild-of), so the R4 output contains no R5-only operator.Source / notes
tx/xversion/xv-valueset.jsisR5OnlyFilterOperator(the' descendent-leaf'typo).tx/xversion/xv-codesystem.jsuses a different R5-only set — it listsgeneralizes(valid in R3/R4/R5, so wrongly stripped) and omitschild-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).