Skip to content

feat(validator): warn when a known extension sits outside its declared context - #616

Open
angela-helios wants to merge 5 commits into
mainfrom
feat/615-extension-context
Open

feat(validator): warn when a known extension sits outside its declared context#616
angela-helios wants to merge 5 commits into
mainfrom
feat/615-extension-context

Conversation

@angela-helios

Copy link
Copy Markdown
Contributor

Closes #615. The gap surfaced in Steve and Angela's extension-authoring session: the picker constrains placement, $validate never did.

What it does

The walk checks each extension/modifierExtension item whose url resolves to a registry schema with declared contexts against the index-free parent element path (Patient, Patient.name), and reports a warning-severity ExtensionContext issue on mismatch. Element-type contexts name one exact spot — Patient is the resource root, not "anywhere in Patient".

Deliberately conservative

Judged only when every declared context reads as a resource-rooted element path. Datatype-named contexts (HumanName), the Element/Resource/DomainResource catch-alls, and FHIRPath expressions make the extension unjudgeable here (the parent's type is not in hand during the walk) and pass silently — as do unknown urls and relative sub-extension urls. No false positives by construction; the common miss (a Patient extension pasted into an Observation) is caught.

Severity plumbing

  • Warnings map to warning-severity OperationOutcome issues at $validate (existing rest/validation.rs mapping) and never trip enforce mode (which rejects on error-severity only).
  • The Guided editor's issue count blocks saving, so it now retains only error-severity issues — context guidance belongs to $validate, not between the user and the save button.

Tests

  • extension_context_mismatch_warns: birthPlace at the Patient root → silent; on an Observation → exactly one warning at Observation.extension.0; unknown url → silent. Runs against the real R4 core registry.
  • Full cargo test -p helios-fhir-validator (conformance fixtures included) and -p helios-ui green; clippy clean.

…d context

Per FHIR's extension-definition rules, StructureDefinition.context pins
where an extension may be used — an element-type context names one exact
spot (Patient is the resource root, Patient.name that element), not
'anywhere in the resource'. The editor picker already honored this; the
validation engine never looked, so a resource carrying a known extension
in the wrong place passed $validate silently.

The walk now checks each extension/modifierExtension item whose url
resolves to a schema with declared contexts against the index-free parent
element path, and reports a warning-severity ExtensionContext issue on
mismatch. Deliberately conservative: only contexts that read as
resource-rooted element paths are judged — datatype names, the
Element/Resource/DomainResource catch-alls, and FHIRPath expressions make
the extension unjudgeable here and pass silently, as do unknown and
relative (sub-extension) urls. Warnings map to warning-severity
OperationOutcome issues at $validate and never trip enforce mode.

The editor's issue count blocks saving, so it now keeps only
error-severity issues — context guidance belongs to $validate, not
between the user and their save button.

Closes #615
…perationOutcome arm

CI caught what the local check runs missed: the context check's membership
test reads as contains(), and rest's exhaustive ErrorKind-to-IssueType
match needs an arm for ExtensionContext — mapped to invalid, placement
being a content problem rather than a structural one.
@angela-helios
angela-helios marked this pull request as draft August 20, 2026 19:18
The spec-examples sweep falsified the always-on design twice over.
Element-level judging broke on recursive elements (an instance path
Questionnaire.item.item never equals the declared Questionnaire.item —
matching needs the StructureDefinition path, which the walk does not
have). And even root-only judging flagged hundreds of published spec
resources: HL7's own publisher places the structuredefinition-wg/-fmm/
-standards-status metadata extensions on CodeSystem/ValueSet roots
throughout the spec, so a faithful check marks canonical content
wholesale.

The check now judges resource-root extensions only and runs behind
ValidationOptions::check_extension_context, default off — available to a
profile author who asks, invisible to everyone else. The sweep pins the
default path: all three baselines unchanged. Also fixes the duplicated
test attribute the first push carried.
@angela-helios
angela-helios force-pushed the feat/615-extension-context branch from 72b4d93 to 57c888a Compare August 20, 2026 19:48
@angela-helios

Copy link
Copy Markdown
Contributor Author

The spec-examples sweep rejected the always-on design, and it was right to — updating the PR's claims:

  1. Element-level judging is unsound without SD-path mapping. An instance path Questionnaire.item.item never equals the declared context Questionnaire.item; matching needs the StructureDefinition path (recursion collapsed via contentReference), which the walk does not have. Hundreds of false positives across the questionnaire examples.
  2. Even root-only judging flags canonical HL7 content wholesale. The publisher places structuredefinition-wg/-fmm/-standards-status/-normative-version (declared context: StructureDefinition) on CodeSystem/ValueSet roots throughout the published spec — e.g. every codesystem-*.json example.

So the check landed as: resource-root extensions only, behind ValidationOptions::check_extension_context, default off. A profile author can opt in; a default $validate is unchanged, and the sweep pins that (all three baselines intact with the option off, and the option-on path is unit-tested). Wiring the option to something reachable — an HFS_VALIDATION_* knob or a $validate parameter — can ride a follow-up if we want it exposed.

Also for the record: an earlier push accidentally committed the re-downloaded R6 example corpus (1,375 files of line-ending churn from crates/fhir/build.rs); it was force-replaced with the clean history before review.

@angela-helios
angela-helios marked this pull request as ready for review August 20, 2026 20:21
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.04545% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/fhir-validator/src/engine/walk.rs 92.75% 5 Missing ⚠️
crates/fhir-validator/src/engine/path.rs 90.90% 1 Missing ⚠️
crates/rest/src/validation.rs 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

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.

validator: extension context (placement) is not checked at validation

2 participants