diff --git a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs
index 0a412721e..92d5dac2b 100644
--- a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs
+++ b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssembly.cs
@@ -47,7 +47,8 @@ public static class CapabilityAssembly
/// 扫描指定程序集集合(缺省 = 当前 AppDomain 全部已加载程序集;同 Id 多实现 fail-fast)。
///
/// 程序集集合(null = 全部已加载)。
- /// 发现的能力模块(按程序集名 + 类型名稳定排序;同 Id 多实现抛 )。
+ /// 发现的能力模块(按程序集名 + 类型名稳定排序;同 Id 多实现抛 ;
+ /// 程序集类型无法完整加载抛 )。
public static IReadOnlyList DiscoverModules(IEnumerable assemblies = null)
{
var targetAssemblies = assemblies ?? AppDomain.CurrentDomain.GetAssemblies();
@@ -144,7 +145,19 @@ private static void CollectModuleTypes(Assembly assembly, List moduleTypes
catch (ReflectionTypeLoadException exception)
{
// 命中命名约定的能力包类型无法完整加载(如依赖缺失)= 部署残缺——带定位信息 fail-fast,不再静默跳过。
- throw new InvalidOperationException(string.Format(LocalizationService.GetString(Localization.Keys.Capability.Hosting.ModuleTypeScanFailed), assembly.GetName().Name, exception.LoaderExceptions == null ? 0 : exception.LoaderExceptions.Length));
+ // 抛 CapabilityAssemblyException:组合入口统一经 RenderFailures 逐条打 Error,首条为程序集定位,其后逐条保留底层加载原因。
+ var loaderExceptions = exception.LoaderExceptions ?? Array.Empty();
+ var problems = new List(loaderExceptions.Length + 1);
+ problems.Add(string.Format(LocalizationService.GetString(Localization.Keys.Capability.Hosting.ModuleTypeScanFailed), assembly.GetName().Name, loaderExceptions.Length));
+ foreach (var loaderException in loaderExceptions)
+ {
+ if (loaderException != null)
+ {
+ problems.Add(loaderException.Message);
+ }
+ }
+
+ throw new CapabilityAssemblyException(problems, exception);
}
foreach (var type in types)
diff --git a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs
index 7cbcd72a1..2c30b0395 100644
--- a/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs
+++ b/Capability/GameFrameX.Capability.Hosting.Core/CapabilityAssemblyException.cs
@@ -50,6 +50,17 @@ public CapabilityAssemblyException(IReadOnlyList problems)
Problems = problems;
}
+ ///
+ /// 初始化 (保留触发问题的原始异常,供诊断堆栈与底层异常对象)。
+ ///
+ /// 全部问题条目(已本地化;至少一条)。
+ /// 触发问题的原始异常。
+ public CapabilityAssemblyException(IReadOnlyList problems, Exception innerException)
+ : base(BuildMessage(problems), innerException)
+ {
+ Problems = problems;
+ }
+
///
/// 获取全部问题条目(已本地化)。
///
diff --git a/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs b/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs
index 106e1424e..40ad77c9f 100644
--- a/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs
+++ b/Tests/GameFrameX.Tests/Online/CapabilityDeploymentDiscoveryTests.cs
@@ -89,7 +89,8 @@ public void LoadDeploymentAssemblies_CorruptArtifact_FailsFastWithProblems()
}
///
- /// 类型无法完整加载的能力包(依赖缺失)不再静默跳过:带程序集名定位信息 fail-fast。
+ /// 类型无法完整加载的能力包(依赖缺失)不再静默跳过:抛 ,
+ /// 首条问题带程序集名定位,其后逐条保留底层类型加载原因(组合入口据此经 RenderFailures 一次报全)。
///
[Fact]
public void DiscoverModules_UnloadableTypes_FailsFastWithAssemblyName()
@@ -100,8 +101,11 @@ public void DiscoverModules_UnloadableTypes_FailsFastWithAssemblyName()
{
var assembly = Assembly.LoadFrom(path);
- var exception = Assert.Throws(() => CapabilityAssembly.DiscoverModules(new[] { assembly, }));
- Assert.Contains("CapabilityDeploymentScanProbe.Broken", exception.Message);
+ var exception = Assert.Throws(() => CapabilityAssembly.DiscoverModules(new[] { assembly, }));
+ Assert.Contains("CapabilityDeploymentScanProbe.Broken", exception.Problems[0]);
+ Assert.True(exception.Problems.Count > 1);
+ Assert.Contains(exception.Problems.Skip(1), problem => problem.Contains("CapabilityDeploymentScanProbe.Missing"));
+ Assert.IsType(exception.InnerException);
}
finally
{