From 37817db6f782847473175ec0f28cd4cab10fec91 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 15 Apr 2026 16:18:09 +0200
Subject: [PATCH 1/6] build(deps): bump com.fasterxml.jackson.core:jackson-core
(#32)
Bumps [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) from 2.18.0 to 2.18.6.
- [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.18.0...jackson-core-2.18.6)
---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-core
dependency-version: 2.18.6
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
linter-core/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/linter-core/pom.xml b/linter-core/pom.xml
index 6186007..17ab91c 100644
--- a/linter-core/pom.xml
+++ b/linter-core/pom.xml
@@ -79,7 +79,7 @@
com.fasterxml.jackson.corejackson-core
- 2.18.0
+ 2.18.6
From b58d97b624caccf01e877b907df5f175a55400ee Mon Sep 17 00:00:00 2001
From: khalilmalla95
Date: Mon, 20 Apr 2026 15:16:48 +0200
Subject: [PATCH 2/6] Expand `LintingType` with additional error types for
input coding validation.
---
.../src/main/java/dev/dsf/linter/output/LintingType.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/linter-core/src/main/java/dev/dsf/linter/output/LintingType.java b/linter-core/src/main/java/dev/dsf/linter/output/LintingType.java
index 374b8d2..2132fd7 100644
--- a/linter-core/src/main/java/dev/dsf/linter/output/LintingType.java
+++ b/linter-core/src/main/java/dev/dsf/linter/output/LintingType.java
@@ -177,7 +177,10 @@ public enum LintingType {
FHIR_TASK_INPUT_INSTANCE_COUNT_EXCEEDS_MAX("Task input instance count exceeds maximum."),
FHIR_TASK_INPUT_SLICE_COUNT_BELOW_SLICE_MIN("Task input slice count below slice minimum."),
FHIR_TASK_INPUT_SLICE_COUNT_EXCEEDS_SLICE_MAX("Task input slice count exceeds slice maximum."),
- FHIR_TASK_UNKNOWN_CODE("Task has unknown code."),
+ FHIR_TASK_UNKNOWN_CODE("Task has unknown code (outside Task.input.type.coding)."),
+ FHIR_TASK_INPUT_CODING_SYSTEM_UNKNOWN("Task.input.type.coding.system is not a known CodeSystem URI."),
+ FHIR_TASK_INPUT_CODING_SYSTEM_NOT_IN_VALUE_SET("Task.input.type.coding.system is not allowed by the expected ValueSet binding context."),
+ FHIR_TASK_INPUT_CODING_CODE_UNKNOWN_FOR_SYSTEM("Task.input.type.coding.code is unknown in the specified CodeSystem."),
FHIR_TASK_REQUESTER_ID_NOT_EXIST("Task requester ID does not exist."),
FHIR_TASK_REQUESTER_ID_NO_PLACEHOLDER("Task requester ID missing placeholder."),
FHIR_TASK_RECIPIENT_ID_NOT_EXIST("Task recipient ID does not exist."),
From 849432479f93a4747d183912f0286a38748eb275 Mon Sep 17 00:00:00 2001
From: khalilmalla95
Date: Mon, 20 Apr 2026 15:19:52 +0200
Subject: [PATCH 3/6] Enhance FHIR cache with ValueSet seeding and system
tracking
- Added support for loading ValueSet files and indexing `compose.include.system` URIs.
- Introduced utilities for checking system inclusion in ValueSets and retrieving associated systems.
- Refactored resource scanning logic to handle both CodeSystems and ValueSets.
---
.../util/resource/FhirAuthorizationCache.java | 217 ++++++++++++++++--
1 file changed, 200 insertions(+), 17 deletions(-)
diff --git a/linter-core/src/main/java/dev/dsf/linter/util/resource/FhirAuthorizationCache.java b/linter-core/src/main/java/dev/dsf/linter/util/resource/FhirAuthorizationCache.java
index 5d28635..68d972c 100644
--- a/linter-core/src/main/java/dev/dsf/linter/util/resource/FhirAuthorizationCache.java
+++ b/linter-core/src/main/java/dev/dsf/linter/util/resource/FhirAuthorizationCache.java
@@ -87,6 +87,12 @@ public final class FhirAuthorizationCache
public static final String CS_ORG_ROLE = "http://dsf.dev/fhir/CodeSystem/organization-role";
+ /**
+ * DSF core CodeSystem URI for BPMN message slices ({@code message-name}, {@code business-key},
+ * {@code correlation-key}).
+ */
+ public static final String CS_BPMN_MESSAGE = "http://dsf.dev/fhir/CodeSystem/bpmn-message";
+
/**
* FHIR Task URI for Task status values.
*/
@@ -96,6 +102,25 @@ public final class FhirAuthorizationCache
private static final Map> CODES_BY_SYSTEM = new ConcurrentHashMap<>();
+ /**
+ * Set of CodeSystem URIs that are referenced by at least one known ValueSet's
+ * {@code compose.include.system} entry. Populated during
+ * {@link #seedFromProjectAndClasspath(File)}.
+ */
+ private static final Set SYSTEMS_IN_VALUE_SETS = ConcurrentHashMap.newKeySet();
+
+ /**
+ * Index of known ValueSets keyed by their canonical {@code url}.
+ * Each entry maps to the set of {@code compose.include.system} URIs
+ * declared by that ValueSet. Populated during
+ * {@link #seedFromProjectAndClasspath(File)}.
+ *
+ *
Used for binding-driven terminology checks, e.g.,
+ * verifying that a {@code Task.input.type.coding.system} is allowed by the
+ * specific ValueSet referenced in a profile's {@code binding.valueSet}.
+ */
+ private static final Map> SYSTEMS_PER_VALUE_SET = new ConcurrentHashMap<>();
+
static
{
// Register official DSF codes (release v1.7)
@@ -121,6 +146,10 @@ public final class FhirAuthorizationCache
"draft", "requested", "received", "accepted", "rejected", "ready",
"cancelled", "in-progress", "on-hold", "failed", "completed", "entered-in-error"));
+ register(CS_BPMN_MESSAGE, Set.of("message-name", "business-key", "correlation-key"));
+
+ // The bpmn-message CodeSystem is always included in DSF's well-known ValueSets
+ SYSTEMS_IN_VALUE_SETS.add(CS_BPMN_MESSAGE);
}
private FhirAuthorizationCache() { /* Utility class – no instantiation */ }
@@ -285,12 +314,14 @@ private static void dumpStatistics()
public static void seedFromProjectAndClasspath(File projectRoot)
{
Objects.requireNonNull(projectRoot, "projectRoot");
+
+ // ---- CodeSystem seeding ----
Set allCodeSystemFiles = new LinkedHashSet<>(findCodeSystemsOnDisk(projectRoot));
// 2) Classpath scan: fhir/CodeSystem/*.xml and *.json from dependency JARs or directories
try {
ClassLoader cl = getOrCreateProjectClassLoader(projectRoot);
- allCodeSystemFiles.addAll(findCodeSystemsOnClasspath(cl));
+ allCodeSystemFiles.addAll(findResourcesOnClasspath(cl, "fhir/CodeSystem"));
} catch (Exception e) {
logger.debug("[CodeSystem-Cache] Failed to scan classpath: " + e.getMessage());
// keep going; disk results might still be sufficient
@@ -301,36 +332,65 @@ public static void seedFromProjectAndClasspath(File projectRoot)
loadCodeSystemFile(cs);
}
+ // ---- ValueSet seeding (compose.include.system → SYSTEMS_IN_VALUE_SETS) ----
+ Set allValueSetFiles = new LinkedHashSet<>(findValueSetsOnDisk(projectRoot));
+ try {
+ ClassLoader cl = getOrCreateProjectClassLoader(projectRoot);
+ allValueSetFiles.addAll(findResourcesOnClasspath(cl, "fhir/ValueSet"));
+ } catch (Exception e) {
+ logger.debug("[ValueSet-Cache] Failed to scan classpath: " + e.getMessage());
+ }
+ for (File vs : allValueSetFiles) {
+ loadValueSetFile(vs);
+ }
+
dumpStatistics();
}
// ---- Helper methods ----
/**
- * Returns CodeSystem files under typical project locations (no changes to your current logic).
+ * Returns CodeSystem files under typical project locations.
*/
- private static Collection findCodeSystemsOnDisk(File projectRoot)
- {
- List candidates = List.of(
+ private static Collection findCodeSystemsOnDisk(File projectRoot) {
+ return findFhirResourcesOnDisk(projectRoot, List.of(
"src/main/resources/fhir/CodeSystem",
"target/classes/fhir/CodeSystem",
- "fhir/CodeSystem" // exploded plugin root case
- );
+ "fhir/CodeSystem"));
+ }
+
+ /**
+ * Returns ValueSet files under typical project locations.
+ */
+ private static Collection findValueSetsOnDisk(File projectRoot) {
+ return findFhirResourcesOnDisk(projectRoot, List.of(
+ "src/main/resources/fhir/ValueSet",
+ "target/classes/fhir/ValueSet",
+ "fhir/ValueSet"));
+ }
+
+ /**
+ * Generic disk scanner: lists {@code .xml} and {@code .json} files under the given
+ * relative subdirectories of {@code projectRoot}.
+ */
+ private static Collection findFhirResourcesOnDisk(File projectRoot, List candidates) {
List out = new ArrayList<>();
for (String dir : candidates) {
File d = new File(projectRoot, dir);
- File[] xmls = d.isDirectory() ? d.listFiles(f -> f.isFile() && (f.getName().endsWith(".xml") || f.getName().endsWith(".json"))) : null;
- if (xmls != null) out.addAll(Arrays.asList(xmls));
+ File[] files = d.isDirectory()
+ ? d.listFiles(f -> f.isFile() && (f.getName().endsWith(".xml") || f.getName().endsWith(".json")))
+ : null;
+ if (files != null) out.addAll(Arrays.asList(files));
}
return out;
}
/**
- * Finds CodeSystem XMLs and JSONs on the classpath under "fhir/CodeSystem" (both directories and JARs).
+ * Finds FHIR resource files on the classpath under {@code basePath}
+ * (both directories and JARs), materializing JAR entries to temp files.
*/
- private static Collection findCodeSystemsOnClasspath(ClassLoader cl) throws IOException
+ private static Collection findResourcesOnClasspath(ClassLoader cl, String basePath) throws IOException
{
- final String basePath = "fhir/CodeSystem";
List out = new ArrayList<>();
// A) enumerate basePath URLs (dirs or inside JARs)
@@ -342,8 +402,9 @@ private static Collection findCodeSystemsOnClasspath(ClassLoader cl) throw
if ("file".equals(protocol)) {
// Directory on classpath -> list *.xml and *.json
File dir = new File(url.getPath());
- File[] files = dir.isDirectory() ? dir.listFiles(f -> f.isFile() &&
- (f.getName().endsWith(".xml") || f.getName().endsWith(".json"))) : null;
+ File[] files = dir.isDirectory()
+ ? dir.listFiles(f -> f.isFile() && (f.getName().endsWith(".xml") || f.getName().endsWith(".json")))
+ : null;
if (files != null) out.addAll(Arrays.asList(files));
} else if ("jar".equals(protocol)) {
// JAR -> iterate entries
@@ -356,7 +417,7 @@ private static Collection findCodeSystemsOnClasspath(ClassLoader cl) throw
&& (e.getName().endsWith(".xml") || e.getName().endsWith(".json"))) {
// materialize to temp file
String fileName = Paths.get(e.getName()).getFileName().toString();
- Path tmp = Files.createTempFile("cs-", "-" + fileName);
+ Path tmp = Files.createTempFile("fhir-", "-" + fileName);
try (InputStream in = jar.getInputStream(e)) {
Files.copy(in, tmp, StandardCopyOption.REPLACE_EXISTING);
}
@@ -366,8 +427,7 @@ private static Collection findCodeSystemsOnClasspath(ClassLoader cl) throw
}
}
} catch (IOException ioe) {
- logger.debug("[CodeSystem-Cache] Failed to read JAR: " + ioe.getMessage());
- // ignore this JAR and keep going
+ logger.debug("[FHIR-Cache] Failed to read JAR (" + basePath + "): " + ioe.getMessage());
}
}
}
@@ -376,6 +436,74 @@ private static Collection findCodeSystemsOnClasspath(ClassLoader cl) throw
return out.stream().distinct().collect(Collectors.toList());
}
+ /**
+ * Parses a ValueSet file (XML or JSON) and registers each
+ * {@code compose.include.system} URI into {@link #SYSTEMS_IN_VALUE_SETS}.
+ */
+ private static void loadValueSetFile(File f) {
+ String name = f.getName().toLowerCase();
+ if (name.endsWith(".xml")) loadValueSetXml(f.toPath());
+ else if (name.endsWith(".json")) loadValueSetJson(f.toPath());
+ }
+
+ private static void loadValueSetXml(Path xml) {
+ try (FileInputStream fis = new FileInputStream(xml.toFile())) {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setNamespaceAware(true);
+ Document doc = dbf.newDocumentBuilder().parse(fis);
+ if (!"ValueSet".equals(doc.getDocumentElement().getLocalName())) return;
+
+ String vsUrl = (String) XPathFactory.newInstance().newXPath()
+ .compile("/*[local-name()='ValueSet']/*[local-name()='url']/@value")
+ .evaluate(doc, XPathConstants.STRING);
+
+ NodeList systems = (NodeList) XPathFactory.newInstance().newXPath()
+ .compile("/*[local-name()='ValueSet']/*[local-name()='compose']" +
+ "/*[local-name()='include']/*[local-name()='system']/@value")
+ .evaluate(doc, XPathConstants.NODESET);
+ if (systems == null) return;
+ for (int i = 0; i < systems.getLength(); i++) {
+ String sys = systems.item(i).getTextContent();
+ registerValueSetSystem(sys);
+ indexValueSetSystem(vsUrl, sys);
+ logger.debug("[Cache-DEBUG] ValueSet " + xml.getFileName() + " (" + vsUrl + ") includes system: " + sys);
+ }
+ } catch (Exception ignore) { /* invalid or non-parsable file */ }
+ }
+
+ private static void loadValueSetJson(Path json) {
+ try (InputStream in = Files.newInputStream(json)) {
+ com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
+ com.fasterxml.jackson.databind.JsonNode root = mapper.readTree(in);
+ if (root == null || !"ValueSet".equals(root.path("resourceType").asText())) return;
+ String vsUrl = root.path("url").asText(null);
+ com.fasterxml.jackson.databind.JsonNode includes = root.path("compose").path("include");
+ if (includes.isArray()) {
+ for (com.fasterxml.jackson.databind.JsonNode inc : includes) {
+ String sys = inc.path("system").asText(null);
+ if (sys != null && !sys.isBlank()) {
+ registerValueSetSystem(sys);
+ indexValueSetSystem(vsUrl, sys);
+ logger.debug("[Cache-DEBUG] ValueSet " + json.getFileName() + " (" + vsUrl + ") includes system: " + sys);
+ }
+ }
+ }
+ } catch (Exception ignore) { /* invalid or non-parsable file */ }
+ }
+
+ /**
+ * Indexes a single {@code compose.include.system} URI under the given ValueSet canonical URL.
+ *
+ * @param vsUrl canonical URL of the ValueSet (may be {@code null} or blank; in that case the entry is skipped)
+ * @param system CodeSystem URI referenced in {@code compose.include.system}
+ */
+ private static void indexValueSetSystem(String vsUrl, String system) {
+ if (vsUrl == null || vsUrl.isBlank() || system == null || system.isBlank()) return;
+ SYSTEMS_PER_VALUE_SET
+ .computeIfAbsent(vsUrl, k -> ConcurrentHashMap.newKeySet())
+ .add(system);
+ }
+
/**
* Single-file load hook that delegates to existing XML/JSON parsing logic.
*/
@@ -389,6 +517,61 @@ private static void loadCodeSystemFile(File f)
}
}
+ // ---- ValueSet system tracking ----
+
+ /**
+ * Registers a CodeSystem URI as being referenced by a known ValueSet.
+ * Called during ValueSet scanning in {@link #seedFromProjectAndClasspath(File)}.
+ *
+ * @param system the CodeSystem URI referenced in a ValueSet's {@code compose.include.system}
+ */
+ public static void registerValueSetSystem(String system) {
+ if (system != null && !system.isBlank())
+ SYSTEMS_IN_VALUE_SETS.add(system);
+ }
+
+ /**
+ * Returns {@code true} if the given CodeSystem URI is referenced by at least one
+ * known ValueSet's {@code compose.include.system}.
+ *
+ * @param system the CodeSystem URI to check
+ * @return {@code true} if the system is included in at least one known ValueSet
+ */
+ public static boolean isSystemInAnyValueSet(String system) {
+ return system != null && SYSTEMS_IN_VALUE_SETS.contains(system);
+ }
+
+ /**
+ * Returns {@code true} if a ValueSet with the given canonical URL has been loaded.
+ * Version suffixes (everything after {@code |}) are stripped before comparison.
+ *
+ * @param valueSetUrl canonical URL of the ValueSet, optionally versioned ({@code url|version})
+ * @return {@code true} if the ValueSet is known to the cache
+ */
+ public static boolean isValueSetLoaded(String valueSetUrl) {
+ if (valueSetUrl == null || valueSetUrl.isBlank()) return false;
+ return SYSTEMS_PER_VALUE_SET.containsKey(stripVersion(valueSetUrl));
+ }
+
+ /**
+ * Returns the set of CodeSystem URIs referenced by the given ValueSet's
+ * {@code compose.include.system} entries. Version suffixes on {@code valueSetUrl}
+ * are stripped before lookup.
+ *
+ * @param valueSetUrl canonical URL of the ValueSet, optionally versioned ({@code url|version})
+ * @return an immutable view of the referenced system URIs; empty if the ValueSet is not loaded
+ */
+ public static Set getSystemsInValueSet(String valueSetUrl) {
+ if (valueSetUrl == null || valueSetUrl.isBlank()) return Collections.emptySet();
+ Set s = SYSTEMS_PER_VALUE_SET.get(stripVersion(valueSetUrl));
+ return s == null ? Collections.emptySet() : Collections.unmodifiableSet(s);
+ }
+
+ private static String stripVersion(String canonical) {
+ int pipe = canonical.indexOf('|');
+ return pipe >= 0 ? canonical.substring(0, pipe) : canonical;
+ }
+
/** True if we have any codes cached for this CodeSystem URL. */
public static boolean containsSystem(String system) {
return system != null && CODES_BY_SYSTEM.containsKey(system);
From aae7f24261aa0f331e527009ff278c8f57f8a905 Mon Sep 17 00:00:00 2001
From: khalilmalla95
Date: Mon, 20 Apr 2026 15:33:46 +0200
Subject: [PATCH 4/6] Expand `FHIR_TASK_INPUT` validation with terminology and
slice binding checks
- Added granular error types to `LintingType` for `Task.input.type.coding` validation.
- Introduced slice cardinality and terminology checks for `Task.input`.
- Refactored `lintInputs` and implemented `lintInputTypeCodingTerminology` to handle `Task.input` coding validation.
- Enhanced profile-loading logic to extract binding metadata and fixed coding constraints.
---
.../dev/dsf/linter/fhir/FhirTaskLinter.java | 240 +++++++++++++++++-
1 file changed, 230 insertions(+), 10 deletions(-)
diff --git a/linter-core/src/main/java/dev/dsf/linter/fhir/FhirTaskLinter.java b/linter-core/src/main/java/dev/dsf/linter/fhir/FhirTaskLinter.java
index 304d400..8afab44 100644
--- a/linter-core/src/main/java/dev/dsf/linter/fhir/FhirTaskLinter.java
+++ b/linter-core/src/main/java/dev/dsf/linter/fhir/FhirTaskLinter.java
@@ -144,7 +144,10 @@
*
{@link LintingType#FHIR_TASK_INPUT_INSTANCE_COUNT_EXCEEDS_MAX} – too many {@code Task.input} elements
{@link LintingType#FHIR_TASK_INPUT_CODING_SYSTEM_UNKNOWN} – {@code Task.input.type.coding.system} not a known CodeSystem URI
+ *
{@link LintingType#FHIR_TASK_INPUT_CODING_SYSTEM_NOT_IN_VALUE_SET} – {@code Task.input.type.coding.system} not allowed by the expected ValueSet binding context
+ *
{@link LintingType#FHIR_TASK_INPUT_CODING_CODE_UNKNOWN_FOR_SYSTEM} – {@code Task.input.type.coding.code} unknown in the specified CodeSystem
*
{@link LintingType#FHIR_TASK_COULD_NOT_LOAD_PROFILE} – StructureDefinition could not be loaded (warning)
*
*
Successful validations are reported with {@link LinterSeverity#INFO} for completeness and traceability.
@@ -177,7 +180,7 @@ public final class FhirTaskLinter extends AbstractFhirInstanceLinter {
private static final String INPUT_XP = TASK_XP + "/*[local-name()='input']";
private static final String CODING_SYS_XP = "./*[local-name()='type']/*[local-name()='coding']/*[local-name()='system']/@value";
private static final String CODING_CODE_XP = "./*[local-name()='type']/*[local-name()='coding']/*[local-name()='code']/@value";
- private static final String SYSTEM_BPMN_MSG = "http://dsf.dev/fhir/CodeSystem/bpmn-message";
+ private static final String SYSTEM_BPMN_MSG = FhirAuthorizationCache.CS_BPMN_MESSAGE;
private static final String SYSTEM_ORG_ID = "http://dsf.dev/sid/organization-identifier";
private static final String TASK_IDENTIFIER_SID = "http://dsf.dev/sid/task-identifier";
private static final Set STATUSES_NEED_BIZKEY = Set.of("in-progress", "completed", "failed");
@@ -213,8 +216,14 @@ public List lint(Document doc, File resFile) {
checkMetaAndBasic(doc, resFile, ref, issues);
checkPlaceholders(doc, resFile, ref, issues);
lintTaskIdentifier(doc, resFile, ref, issues);
- lintInputs(doc, resFile, ref, issues);
+
+ // Load slice metadata once and reuse for structural + terminology checks
+ String profileUrl = val(doc, TASK_XP + "/*[local-name()='meta']/*[local-name()='profile']/@value");
+ Map cards = loadInputCardinality(determineProjectRoot(resFile), profileUrl);
+
+ lintInputs(doc, resFile, ref, issues, cards);
lintTerminology(doc, resFile, ref, issues);
+ lintInputTypeCodingTerminology(doc, resFile, ref, issues, cards);
lintRequesterAuthorization(doc, resFile, ref, issues);
lintRecipientAuthorization(doc, resFile, ref, issues);
@@ -370,11 +379,9 @@ private void lintTaskIdentifier(Document doc, File f, String ref, List out) {
- String profileUrl = val(doc, TASK_XP + "/*[local-name()='meta']/*[local-name()='profile']/@value");
- Map cards = loadInputCardinality(determineProjectRoot(f), profileUrl);
-
+ private void lintInputs(Document doc, File f, String ref, List out, Map cards) {
if (cards == null) {
+ String profileUrl = val(doc, TASK_XP + "/*[local-name()='meta']/*[local-name()='profile']/@value");
out.add(new FhirElementLintItem(LinterSeverity.WARN, LintingType.FHIR_TASK_COULD_NOT_LOAD_PROFILE, f, ref,
"StructureDefinition for profile '" + profileUrl + "' not found → cardinality check skipped."));
}
@@ -504,11 +511,23 @@ else if (cnt > card.max())
}
}
+ /**
+ * Generic terminology check for all {@code coding} nodes in the Task document
+ * except {@code Task.input.type.coding} entries, which are validated
+ * separately with granular error types in {@link #lintInputTypeCodingTerminology}.
+ */
private void lintTerminology(Document doc, File f, String ref, List out) {
NodeList codings = xp(doc, "//coding");
if (codings == null) return;
for (int i = 0; i < codings.getLength(); i++) {
Node c = codings.item(i);
+ // Skip Task.input.type.coding — handled by lintInputTypeCodingTerminology
+ Node parent = c.getParentNode();
+ if (parent != null && "type".equals(parent.getLocalName())) {
+ Node grandParent = parent.getParentNode();
+ if (grandParent != null && "input".equals(grandParent.getLocalName()))
+ continue;
+ }
String sys = val(c, "./*[local-name()='system']/@value");
String code = val(c, "./*[local-name()='code']/@value");
if (FhirAuthorizationCache.isUnknown(sys, code))
@@ -517,6 +536,164 @@ private void lintTerminology(Document doc, File f, String ref, ListThree distinct checks are performed per input. Later checks depend on earlier ones:
+ *
+ *
System known – {@code coding.system} must be registered in
+ * {@link FhirAuthorizationCache} (i.e., correspond to a loaded CodeSystem resource).
+ * If unknown, {@link LintingType#FHIR_TASK_INPUT_CODING_SYSTEM_UNKNOWN} is emitted
+ * and further checks for that input are skipped.
+ *
System in expected ValueSet context – driven by the profile's
+ * StructureDefinition:
+ *
+ *
If the matching slice declares a {@code fixedUri} at
+ * {@code Task.input:sliceName.type.coding.system}, the input's system must
+ * equal it literally.
+ *
Else, if the slice declares a {@code binding.valueSet} and that ValueSet is
+ * loaded, the input's system must appear in that ValueSet's
+ * {@code compose.include.system}.
+ *
Else, if the binding context cannot be resolved, validation fails explicitly
+ * (no permissive fallback to unrelated ValueSets).
+ *
+ * On mismatch, {@link LintingType#FHIR_TASK_INPUT_CODING_SYSTEM_NOT_IN_VALUE_SET} is emitted.
+ *
Code valid for system – {@code coding.code} must be a known code
+ * under the given {@code coding.system}. Only executed if Check 2 passed.
+ * If not, {@link LintingType#FHIR_TASK_INPUT_CODING_CODE_UNKNOWN_FOR_SYSTEM} is emitted.
+ *
+ *
+ *
Inputs that already failed structural validation (missing system or code) in
+ * {@link #lintInputs} are skipped here to avoid duplicate reporting.
+ *
+ * @param cards per-slice cardinality and binding metadata from the StructureDefinition
+ * (may be {@code null} if the profile could not be loaded)
+ */
+ private void lintInputTypeCodingTerminology(Document doc, File f, String ref,
+ List out,
+ Map cards) {
+ NodeList inputs = xp(doc, INPUT_XP);
+ if (inputs == null || inputs.getLength() == 0) return;
+
+ for (int i = 0; i < inputs.getLength(); i++) {
+ Node in = inputs.item(i);
+ String sys = val(in, CODING_SYS_XP);
+ String code = val(in, CODING_CODE_XP);
+
+ // Structural errors (missing system/code) are already reported by lintInputs
+ if (blank(sys) || blank(code)) continue;
+
+ // Check 1: coding.system must be a known CodeSystem
+ if (!FhirAuthorizationCache.containsSystem(sys)) {
+ out.add(new FhirElementLintItem(LinterSeverity.ERROR,
+ LintingType.FHIR_TASK_INPUT_CODING_SYSTEM_UNKNOWN, f, ref,
+ "Task.input.type.coding.system '" + sys + "' is not a known CodeSystem URI."));
+ continue; // checks 2 and 3 require the system to be known
+ }
+
+ // Check 2: coding.system must match the expected ValueSet context for this slice
+ SliceCard slice = findSliceByCode(cards, code);
+ if (!isSystemAllowedByBinding(slice, sys, out, f, ref)) {
+ // Check 3 is only executed when Check 2 passed.
+ continue;
+ }
+
+ // Check 3: coding.code must be a valid code in the given system
+ if (FhirAuthorizationCache.isUnknown(sys, code)) {
+ out.add(new FhirElementLintItem(LinterSeverity.ERROR,
+ LintingType.FHIR_TASK_INPUT_CODING_CODE_UNKNOWN_FOR_SYSTEM, f, ref,
+ "Task.input.type.coding.code '" + code + "' is unknown in CodeSystem '" + sys + "'."));
+ continue;
+ }
+
+ out.add(ok(f, ref, "Task.input.type.coding: system='" + sys + "' code='" + code + "' OK."));
+ }
+ }
+
+ /**
+ * Locates the slice metadata that matches the given input code.
+ *
+ *