luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4093269911


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message 
state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions 
through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = 
"META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry bean name or fully qualified implementation class 
name, without a reference prefix. Registry
+     * lookup takes precedence over class resolution. Absent selects the sole 
advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own 
selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || 
expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must 
reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), 
predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : 
context.resolvePropertyPlaceholders(adapter);
+        if (configured != null) {
+            if (configured.isBlank() || configured.startsWith("#")) {
+                throw new IllegalArgumentException("Semantic adapter must be a 
bean name or class name without a # prefix");
+            }
+            Object bean = context.getRegistry().lookupByName(configured);
+            if (bean != null) {
+                if (!(bean instanceof SemanticAdapter found)) {
+                    throw new IllegalArgumentException(
+                            "Semantic adapter bean does not implement 
SemanticAdapter: " + configured);
+                }
+                selectedAdapter = found;
+                return found;
+            }
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = 
context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);
+                while (resources.hasMoreElements()) {
+                    try (BufferedReader reader = new BufferedReader(
+                            new InputStreamReader(
+                                    resources.nextElement().openStream(), 
StandardCharsets.UTF_8))) {
+                        reader.lines().map(line -> line.split("#", 
2)[0].trim()).filter(line -> !line.isEmpty())
+                                .forEach(candidates::add);
+                    }
+                }
+                if (candidates.size() != 1) {
+                    throw new IllegalArgumentException(
+                            "Semantic language requires exactly one advertised 
adapter; found "
+                                                       + candidates + ". 
Configure camel.language.semantic.adapter explicitly");
+                }
+                className = candidates.iterator().next();
+            }
+            Class<?> resolved = 
context.getClassResolver().resolveClass(className);
+            if (resolved == null) {
+                throw new IllegalArgumentException("No semantic adapter bean 
or class found: " + className);
+            }
+            Class<? extends SemanticAdapter> type = 
resolved.asSubclass(SemanticAdapter.class);
+            synchronized (context.getRegistry()) {

Review Comment:
   Addressed in `7aa7aed20d23`. Adapter registration and identity-checked 
unbinding use a private context-local monitor. Concurrency tests cover 
competing language instances, public registry-monitor avoidance and a blocked 
constructor in one context while another context continues independently.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.semantic;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Resource;
+
+/** Context-local named questions, replaced atomically per source when a route 
resource is reloaded. */
+public final class SemanticQuestions {
+    private final Map<String, Map<String, SemanticQuestion>> sources = new 
HashMap<>();
+    private final Map<String, Resource> resources = new HashMap<>();
+    private volatile Map<String, SemanticQuestion> questions = Map.of();
+
+    public static SemanticQuestions get(CamelContext context) {
+        synchronized (context) {

Review Comment:
   Addressed in `7aa7aed20d23`. Question-registry creation now uses a private 
static creation lock instead of the CamelContext monitor. Tests verify that 
concurrent callers receive the same instance and that creation completes while 
another thread holds the public context monitor.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message 
state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions 
through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = 
"META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry bean name or fully qualified implementation class 
name, without a reference prefix. Registry
+     * lookup takes precedence over class resolution. Absent selects the sole 
advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own 
selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || 
expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must 
reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), 
predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : 
context.resolvePropertyPlaceholders(adapter);
+        if (configured != null) {
+            if (configured.isBlank() || configured.startsWith("#")) {
+                throw new IllegalArgumentException("Semantic adapter must be a 
bean name or class name without a # prefix");
+            }
+            Object bean = context.getRegistry().lookupByName(configured);
+            if (bean != null) {
+                if (!(bean instanceof SemanticAdapter found)) {
+                    throw new IllegalArgumentException(
+                            "Semantic adapter bean does not implement 
SemanticAdapter: " + configured);
+                }
+                selectedAdapter = found;
+                return found;
+            }
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = 
context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   Addressed in `f39755996d0c`. Replaced the manual JDK service-file parser 
with Camel FactoryFinder discovery. Providers now advertise a standard 
semantic-adapter descriptor with class=..., generated for TypeSafe AI by 
@JdkService. A small preflight reads the standard properties to preserve the 
requirement that different advertised adapters fail before construction; 
repeated declarations of the same class remain valid. FactoryFinder resolves 
the selected class, and Camel's Injector performs construction after type 
checking. Tests cover ambiguity, malformed descriptors, duplicate declarations, 
a mismatched custom finder, constructor injection, explicit selection and the 
generated TypeSafe descriptor.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message 
state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions 
through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = 
"META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry reference (#bean:name) or implementation class (plain 
FQCN or #class:FQCN). Absent selects the
+     * sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own 
selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || 
expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must 
reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), 
predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : 
context.resolvePropertyPlaceholders(adapter);
+        if (configured != null && configured.startsWith("#") && 
!configured.startsWith("#class:")) {
+            String name = configured.startsWith("#bean:") ? 
configured.substring(6) : configured.substring(1);
+            if (name.contains(":")) {
+                throw new IllegalArgumentException("Semantic adapter reference 
must use #bean:name, #name or #class:FQCN");
+            }
+            Object bean = context.getRegistry().lookupByName(name);
+            if (!(bean instanceof SemanticAdapter found)) {
+                throw new IllegalArgumentException("Semantic adapter bean is 
missing or does not implement SemanticAdapter");
+            }
+            selectedAdapter = found;
+            return found;
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = 
context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   Addressed in `f39755996d0c`. Replaced the manual JDK service-file parser 
with Camel FactoryFinder discovery. Providers now advertise a standard 
semantic-adapter descriptor with class=..., generated for TypeSafe AI by 
@JdkService. A small preflight reads the standard properties to preserve the 
requirement that different advertised adapters fail before construction; 
repeated declarations of the same class remain valid. FactoryFinder resolves 
the selected class, and Camel's Injector performs construction after type 
checking. Tests cover ambiguity, malformed descriptors, duplicate declarations, 
a mismatched custom finder, constructor injection, explicit selection and the 
generated TypeSafe descriptor.
   
   A native-image build was not run, so this does not claim native-image 
verification.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.yaml.deserializers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before 
route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = 
"map:org.apache.camel.dsl.yaml.deserializers.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport 
implements ConstructNode {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", 
"uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including 
declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    public static void configure(CamelContext context, 
YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate 
semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        SemanticQuestions.get(context).replace(dc.getResource(), definitions);
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires 
only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in 
semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is 
required: " + name);
+            }
+            SemanticQuestion.Type type = 
SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));

Review Comment:
   Addressed in `7aa7aed20d23` and `9ac532851f76`. Enum parsing now uses the 
common YAML `asEnum` helper, with the question name, field and original cause 
retained in a located exception. Constructor validation and structural errors 
also carry YAML source marks. Tests exercise invalid/empty enums, constructor 
validation and malformed declarations with schema validation disabled.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to