Skip to content

IGDD-3285 - FHIR searchset: label joins as include, return only what the caller asked for - #283

Merged
austinmoody merged 7 commits into
developfrom
IGDD-3285
Aug 19, 2026
Merged

IGDD-3285 - FHIR searchset: label joins as include, return only what the caller asked for#283
austinmoody merged 7 commits into
developfrom
IGDD-3285

Conversation

@austinmoody

@austinmoody austinmoody commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

HUMAN REVIEWER - see comment notes in https://izgateway.atlassian.net/browse/IGDD-3285 for examples and info

Why

Branch carries two OpenSpec changes, both in FhirController's searchset filter (post-processes
every FHIR query response):

  1. fix-fhir-searchset-include-mode — every _include/_revinclude hit was labelled
    search.mode = "match". Client could not tell hits from joined supporting resources.
  2. fhir-searchset-strict-includes — first change also auto-retained referenced resources
    (Patient on every query, schedule Organization and Z42 evaluated history on
    /ImmunizationRecommendation) to close dangling mandatory 1..1 references. That made response
    shape unpredictable from the query. Second change reverts to strict contract: caller gets what
    caller asked for.

Net behavior vs develop

  • Searchset contains only: requested type (match), OperationOutcome (outcome), resources the
    caller joined in (include). Everything else removed.
  • No auto-retain. Reference to a resource outside the searchset keeps its literal value,
    identifier, display — same as develop. Caller resolves or ignores.
  • Z42 evaluated history opt-in:
    _include=ImmunizationRecommendation:patient&_revinclude=Immunization:patient, plus
    _include=Immunization:authority to resolve protocolApplied.authority in-bundle.
    _include=*:*&_revinclude=Immunization reproduces the old payload that I had which returned everything by default.
  • _revinclude resolves only from resources already retained — evaluated history and forecast
    Observation reference the Patient, so retain the Patient first.
  • Conversion-created resources stay opt-in: _include=Resource:source:<type> / Resource:source:*,
    or a forward _include reaching them.
  • Wildcard * for types and search names; unknown names match nothing, not an error.
  • Bumps v2tofhir to 2.5.2-SNAPSHOT for the history/forecast split changes (IGDD-3285: fix Z42 history/forecast split for eHealth Exchange pilot v2tofhir#52) and also fixes for Location labeling (fix: assign Location.physicalType codes to the correct PL/LA2 components v2tofhir#53).

Tests

252 tests, 0 failures; 70 in FhirControllerTests with Z32/Z42 fixtures covering match/include
labelling, strict default, wildcard includes, revinclude anchoring, and literal-reference
preservation.

A couple new Postman tests.

KNOWN GAP our mock data DOES NOT have Z42. Ticket entered into backlog to have that added so that we can add /ImmunizationRecommendation tests to Postman.

Docs / Spec

docs/fhir/fhir-api.md and docs/fhir/rsp-to-fhir.md rewritten for the strict contract. Both
changes archived under openspec/changes/archive/; openspec/specs/fhir-searchset-filtering
synced (9 requirements).
IGDD-3285

R4 defines include as "added to the results because of a join", so a
client filtering on match could not tell the hits from the resources
joined in to support them.

Also close the reference graph: the mandatory 1..1 patient and the
authority Organization resolved to nothing in the delivered bundle. A
target that cannot be retained drops its reference element, keeping
identifier and display.

A recommendation query now returns the Z42 evaluated history as
include. Its dose number, series doses, authority and program
eligibility reach no other call, because /Immunization sends Z34 and
receives Z32.

Bumps v2tofhir to 2.5.1-SNAPSHOT for the history/forecast split.

BREAKING CHANGE: _include and _revinclude results report
search.mode=include, not match.

IGDD-3285
@austinmoody
austinmoody marked this pull request as ready for review August 17, 2026 01:17
@keithboone

Copy link
Copy Markdown
Contributor

note: "Driven through the mocked hub. The captures are deliberately not committed — they carry live
vendor IIS patient demographics."

The Mocked Hub does NOT contain live vendor IIS patient demographics, that is all TEST patient data produced from download generated by AIRA.

@keithboone keithboone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are TWO SonarCube comments on this PR that were NOT addressed by it.

I would like to review the content with @austinmoody more thoroughly.

@keithboone keithboone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to fix SonarQube issues.

TL;DR: a FHIR query now returns the resource type you asked for and nothing
else. Want the Patient, the Organization, the Location, or the Z42 evaluated
history too? Ask for them with _include / _revinclude.

What was happening
A plain GET /Immunization also returned the Patient. A plain
GET /ImmunizationRecommendation also returned the Patient, the schedule
Organization, and every Immunization in the evaluated history. Nobody asked for
any of it. That was added to stop mandatory 1..1 references like
Immunization.patient from pointing at a resource the bundle did not contain, but
it meant you could not predict a response from the query that produced it.

What changed
- Removed retainReferencedResources: being referenced is no longer a reason to
  be returned.
- Removed the Z42 branch in preFilter: the evaluated history is no longer
  returned unasked.
- Removed clearUnresolvableReferences: a reference whose target is not in the
  bundle keeps the value the conversion produced, instead of being stripped down
  to identifier + display. This is what develop already did, and no defect was
  ever raised against it.

Kept from b714b59: an _include / _revinclude hit is still labelled
search.mode=include, not match, so filtering on match still gives you exactly
the hits you asked for.

To get the old payload back
GET .../ImmunizationRecommendation?_include=*:*&_revinclude=Immunization
Same entries as before. One difference: the PractitionerRole -> Practitioner
reference now keeps its literal value.

Three things we learned running this
1. _revinclude only works from a resource already in the bundle. The code finds
   "things pointing at you" via bookkeeping stored on the pointed-at resource,
   so that resource has to be there first. The old auto-retain hid this by
   always including the Patient. This is why _revinclude=Immunization needs
   _include=ImmunizationRecommendation:patient beside it.
2. _revinclude never reaches a resource that only Resource:source white-listed.
   A white-listed resource is returned but never walked, so _revinclude=Provenance
   returns nothing - and never did, on this branch or on develop.
3. protocolApplied.authority resolves via _include=Immunization:authority, not
   the recommendation's. That Organization is registered on the Immunization.

Also bumps v2tofhir to 2.5.2-SNAPSHOT, the version every behavior above was
verified against.

Scope: FHIR REST responses only. No change to the SOAP/HL7 v2 path, the outbound
query, the transformation pipeline, or any organization configuration.

Tests: 251 pass, Checkstyle clean, OWASP high finding is 5.3 (under the 7.0 gate).

BREAKING CHANGE: FHIR query responses no longer include resources the caller did
not request. Callers relying on the Patient, Organization, Location, or Z42
evaluated history arriving unasked must add the matching _include / _revinclude
parameters. See docs/fhir/fhir-api.md.

Refs: openspec/changes/fhir-searchset-strict-includes
…ta key

SonarQube flagged the "Resource" literal in matchesSource as duplicating
RESOURCE_KEY. Merging them would be wrong: they spell the same by coincidence
and mean unrelated things.

- RESOURCE_KEY is an internal v2tofhir user-data key. ParserUtils.createReference
  stores a Reference's target under it, and v2tofhir is free to rename it.
- The literal in matchesSource is the resource-type token in the caller's URL -
  the "Resource" in _include=Resource:source:<type>. It is public API surface,
  documented in docs/fhir/rsp-to-fhir.md.

Sharing one constant would let a rename of either silently change the other,
with no compile error. So this adds SOURCE_INCLUDE_TYPE for the URL token, with
a comment recording that the collision is coincidental and must stay separate.

Neither line was touched by fcb3e77; SonarCloud surfaced a pre-existing issue
because that commit removed the third RESOURCE_KEY usage.

No behavior change. 251 tests pass, Checkstyle clean.
@austinmoody

Copy link
Copy Markdown
Contributor Author

Be sure to fix SonarQube issues.

SonarQube items taken care of. Running through some other tests.

…b sends Z42

TS_TC_07f queried /ImmunizationRecommendation and asserted a forecast came back.
IZ Gateway Hub does not currently respond with Z42, so no dev fixture produces a
forecast, and the case failed with no ImmunizationRecommendation in the bundle:

  Verify the recommendation is the match
    expected undefined to equal 'match'

Not a code problem. A missing requested type cannot be caused by this branch -
the removed auto-retain only ever added entries, so pre-change code would still
have returned the recommendation as match. The bundle genuinely had no forecast
in it, because nothing asked the Hub for one before: the collection sends Z34 in
four places and Z44 nowhere.

Removed from both the Certificate and JWT Okta folders. TS_TC_07d (no Patient by
default) and TS_TC_07e (Patient arrives with _include) stay - both pass against
dev, and 07e passing confirms dev is running this branch, since pre-change code
labelled an _include hit match rather than include.

The recovery recipe stays covered by the unit test
recoveryParametersReproduceThePreChangePayload, which drives the Z42 fixture
directly. Re-add the integration case once the Hub returns Z42.
Said white-listed resources are not traversed. They are. Real cause:
v2tofhir gives Provenance a bare id, so a type-qualified _revinclude
compares against null. Wildcard works.

Also: 15 Provenance was 23; "the registered names" listed 11 of 26;
three _revinclude rows need a forward _include first.

Spec: include-labelling requirement moved to MODIFIED - its scenario
would have contradicted the new reverse-include rule after archive.
@austinmoody austinmoody changed the title IGDD-3285 - FHIR changes for eHealthExchange pilot IGDD-3285 - FHIR searchset: label joins as include, return only what the caller asked for Aug 18, 2026
@sonarqubecloud

Copy link
Copy Markdown

@austinmoody
austinmoody merged commit 3cc682a into develop Aug 19, 2026
2 checks passed
@austinmoody
austinmoody deleted the IGDD-3285 branch August 19, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants