@@ -279,15 +279,6 @@ fn explicit_capability_extension(
279279 . filter ( |extension_id| !extension_id. is_empty ( ) )
280280}
281281
282- fn linked_extensions (
283- component : & Component ,
284- ) -> Result < & HashMap < String , crate :: core:: component:: ScopedExtensionConfig > > {
285- component
286- . extensions
287- . as_ref ( )
288- . ok_or_else ( || no_extensions_error ( component) )
289- }
290-
291282pub fn extract_component_extension_settings (
292283 component : & Component ,
293284 extension_id : & str ,
@@ -310,9 +301,29 @@ pub fn resolve_extension_for_capability(
310301 component : & Component ,
311302 capability : ExtensionCapability ,
312303) -> Result < String > {
313- let extensions = linked_extensions ( component) ?;
304+ match resolve_extension_for_capability_if_available ( component, capability) ? {
305+ Some ( extension_id) => Ok ( extension_id) ,
306+ None if component
307+ . extensions
308+ . as_ref ( )
309+ . is_none_or ( |extensions| extensions. is_empty ( ) ) => Err ( no_extensions_error ( component) ) ,
310+ None => Err ( capability_missing_error ( component, capability) ) ,
311+ }
312+ }
313+
314+ /// Resolve a capability only when one of the component's linked extensions
315+ /// advertises it. This lets optional consumers skip a capability that the
316+ /// component has not opted into without weakening validation of explicit or
317+ /// ambiguous capability ownership.
318+ fn resolve_extension_for_capability_if_available (
319+ component : & Component ,
320+ capability : ExtensionCapability ,
321+ ) -> Result < Option < String > > {
322+ let Some ( extensions) = component. extensions . as_ref ( ) else {
323+ return Ok ( None ) ;
324+ } ;
314325 if extensions. is_empty ( ) {
315- return Err ( no_extensions_error ( component ) ) ;
326+ return Ok ( None ) ;
316327 }
317328
318329 if let Some ( extension_id) = explicit_capability_extension ( component, capability) {
@@ -349,7 +360,7 @@ pub fn resolve_extension_for_capability(
349360 ) ) ;
350361 }
351362
352- return Ok ( extension_id. to_string ( ) ) ;
363+ return Ok ( Some ( extension_id. to_string ( ) ) ) ;
353364 }
354365
355366 let mut matching = Vec :: new ( ) ;
@@ -362,8 +373,8 @@ pub fn resolve_extension_for_capability(
362373 }
363374
364375 match matching. len ( ) {
365- 0 => Err ( capability_missing_error ( component , capability ) ) ,
366- 1 => Ok ( matching. remove ( 0 ) ) ,
376+ 0 => Ok ( None ) ,
377+ 1 => Ok ( Some ( matching. remove ( 0 ) ) ) ,
367378 _ => Err ( capability_ambiguous_error ( component, capability, & matching) ) ,
368379 }
369380}
@@ -373,6 +384,28 @@ pub fn resolve_execution_context(
373384 capability : ExtensionCapability ,
374385) -> Result < ExtensionExecutionContext > {
375386 let extension_id = resolve_extension_for_capability ( component, capability) ?;
387+ execution_context_for_extension ( component, capability, extension_id)
388+ }
389+
390+ /// Resolve an execution context when a linked extension provides `capability`.
391+ /// A missing optional capability is represented as `Ok(None)`; malformed
392+ /// explicit ownership and ambiguous providers remain validation errors.
393+ pub ( crate ) fn resolve_execution_context_if_available (
394+ component : & Component ,
395+ capability : ExtensionCapability ,
396+ ) -> Result < Option < ExtensionExecutionContext > > {
397+ let Some ( extension_id) = resolve_extension_for_capability_if_available ( component, capability) ?
398+ else {
399+ return Ok ( None ) ;
400+ } ;
401+ execution_context_for_extension ( component, capability, extension_id) . map ( Some )
402+ }
403+
404+ fn execution_context_for_extension (
405+ component : & Component ,
406+ capability : ExtensionCapability ,
407+ extension_id : String ,
408+ ) -> Result < ExtensionExecutionContext > {
376409 let manifest = load_extension ( & extension_id) ?;
377410 let script_path = capability
378411 . script_path ( & manifest)
0 commit comments