Summary
Expressions evaluated against an IModuleNodeItem graph (rather than an instance document) have no reliable way to produce a definition-level result when they pass through functions that exist to load or dereference external instance data (fn:doc, oscal:resolve-reference, oscal:resolve-profile, fn:resolve-uri). As a consequence, let statements and constraint expressions that use these functions either throw InvalidTypeFunctionException (FOTY0013) during module walking or cannot be resolved to a specific global definition.
Example
At module walk, OSCAL's external constraints declare let expressions such as:
<let var="all-imports"
expression="import-component-definition ! recurse-depth(
'doc(resolve-uri(Q{http://csrc.nist.gov/ns/oscal/1.0}resolve-reference(@href)))/component-definition')"/>
The @href flag on a definition node has no typed value, so atomization throws FOTY0013 before resolve-reference even runs. recurse-depth has no way to know what kind of node doc(...)/component-definition would produce, so it cannot navigate the module graph to the matching global assembly definition.
Root cause
fn:doc, oscal:resolve-reference, oscal:resolve-profile, and fn:resolve-uri do not declare a model-level return type. Without a declared target type, module-walking consumers (visitors, recurse-depth, constraint targets) cannot resolve an instance-resolving chain to the corresponding global definition in the module graph.
Proposed direction
- Add a model-return-type declaration to
IFunction (e.g. a named NCName target, a sentinel such as module-root-wildcard / pass-through, or a function of arg types to return type).
- Add a module-walk flag to
DynamicContext (analogous to disablePredicateEvaluation).
- In module-walk mode, functions with a declared model-return-type resolve to the corresponding global definition; functions without one return an empty sequence and emit a one-time info-level log per function so authors know to declare a model-return-type.
- Let
recurse-depth (and similar) consult the declared model-return-type of the compiled inner expression instead of attempting to load documents.
- Update affected declarations in metaschema-java core (
FnDoc, FnResolveUri, MpRecurseDepth) and in liboscal-java (ResolveReference, ResolveProfile).
Context
Lazy let evaluation in AllowedValueCollectingNodeItemVisitor (separate PR) sidesteps the symptom for list-allowed-values because OSCAL's problematic lets are referenced only by <index> and <expect> constraints, not by <allowed-values>. This issue tracks the root-cause fix so any visitor or constraint consumer walking the module graph behaves correctly regardless of which constraint kind references the let.
Scope
metaschema-java/core — IFunction, DynamicContext, function implementations (FnDoc, FnResolveUri, MpRecurseDepth), atomization on no-data node items.
liboscal-java — ResolveReference, ResolveProfile declarations.
- Warrants a PRD given cross-cutting impact and constraint-author-visible changes.
Summary
Expressions evaluated against an
IModuleNodeItemgraph (rather than an instance document) have no reliable way to produce a definition-level result when they pass through functions that exist to load or dereference external instance data (fn:doc,oscal:resolve-reference,oscal:resolve-profile,fn:resolve-uri). As a consequence,letstatements and constraint expressions that use these functions either throwInvalidTypeFunctionException(FOTY0013) during module walking or cannot be resolved to a specific global definition.Example
At module walk, OSCAL's external constraints declare
letexpressions such as:The
@hrefflag on a definition node has no typed value, so atomization throws FOTY0013 beforeresolve-referenceeven runs.recurse-depthhas no way to know what kind of nodedoc(...)/component-definitionwould produce, so it cannot navigate the module graph to the matching global assembly definition.Root cause
fn:doc,oscal:resolve-reference,oscal:resolve-profile, andfn:resolve-urido not declare a model-level return type. Without a declared target type, module-walking consumers (visitors,recurse-depth, constraint targets) cannot resolve an instance-resolving chain to the corresponding global definition in the module graph.Proposed direction
IFunction(e.g. a named NCName target, a sentinel such asmodule-root-wildcard/pass-through, or a function of arg types to return type).DynamicContext(analogous todisablePredicateEvaluation).recurse-depth(and similar) consult the declared model-return-type of the compiled inner expression instead of attempting to load documents.FnDoc,FnResolveUri,MpRecurseDepth) and inliboscal-java(ResolveReference,ResolveProfile).Context
Lazy let evaluation in
AllowedValueCollectingNodeItemVisitor(separate PR) sidesteps the symptom forlist-allowed-valuesbecause OSCAL's problematic lets are referenced only by<index>and<expect>constraints, not by<allowed-values>. This issue tracks the root-cause fix so any visitor or constraint consumer walking the module graph behaves correctly regardless of which constraint kind references the let.Scope
metaschema-java/core—IFunction,DynamicContext, function implementations (FnDoc,FnResolveUri,MpRecurseDepth), atomization on no-data node items.liboscal-java—ResolveReference,ResolveProfiledeclarations.