diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
index 3c81fbfb..74a4ac44 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
@@ -226,11 +226,11 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
}
///
- /// 验证当 generated request invoker provider 返回实例方法时,
- /// dispatcher 会显式拒绝该描述符,而不是在后续绑定阶段静默接受非法合同。
+ /// 验证当 generated request invoker provider 暴露实例方法时,
+ /// registrar 会放弃该 generated registry 并回退到运行时反射路径。
///
[Test]
- public void SendAsync_Should_Throw_When_Generated_Request_Invoker_Is_Not_Static()
+ public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Generated_Request_Invoker_Is_Not_Static()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(NonStaticRequestInvokerProviderRegistry),
@@ -241,10 +241,8 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
container.Freeze();
var context = new ArchitectureContext(container);
- var exception = Assert.ThrowsAsync(async () =>
- await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false));
- Assert.That(exception, Is.Not.Null);
- Assert.That(exception!.Message, Does.Contain("non-static invoker method"));
+ var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
+ Assert.That(response, Is.EqualTo("runtime:payload"));
}
///
@@ -270,11 +268,11 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
}
///
- /// 验证当 generated stream invoker provider 返回实例方法时,
- /// dispatcher 会在首次建流时显式拒绝该描述符。
+ /// 验证当 generated stream invoker provider 暴露实例方法时,
+ /// registrar 会放弃该 generated registry 并回退到运行时反射路径。
///
[Test]
- public void CreateStream_Should_Throw_When_Generated_Stream_Invoker_Is_Not_Static()
+ public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Generated_Stream_Invoker_Is_Not_Static()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(NonStaticStreamInvokerProviderRegistry),
@@ -285,10 +283,8 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
container.Freeze();
var context = new ArchitectureContext(container);
- var exception = Assert.ThrowsAsync(async () =>
- await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false));
- Assert.That(exception, Is.Not.Null);
- Assert.That(exception!.Message, Does.Contain("non-static invoker method"));
+ var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
+ Assert.That(results, Is.EqualTo([3, 4]));
}
///
@@ -313,6 +309,46 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Assert.That(exception!.Message, Does.Contain("incompatible invoker"));
}
+ ///
+ /// 验证当 generated request invoker provider 实现枚举契约、但返回空描述符集合时,
+ /// dispatcher 仍会回退到既有反射路径。
+ ///
+ [Test]
+ public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Request_Descriptor_Enumeration_Is_Empty()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(EmptyEnumeratingRequestInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.EmptyEnumeratingRequestInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
+ Assert.That(response, Is.EqualTo("runtime:payload"));
+ }
+
+ ///
+ /// 验证当 generated stream invoker provider 实现枚举契约、但返回空描述符集合时,
+ /// dispatcher 仍会回退到既有流式反射路径。
+ ///
+ [Test]
+ public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Stream_Descriptor_Enumeration_Is_Empty()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(EmptyEnumeratingStreamInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.EmptyEnumeratingStreamInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
+ Assert.That(results, Is.EqualTo([3, 4]));
+ }
+
///
/// 模拟返回实例 request invoker 方法的 generated registry。
///
@@ -321,15 +357,6 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
ICqrsRequestInvokerProvider,
IEnumeratesCqrsRequestInvokerDescriptors
{
- private static readonly CqrsRequestInvokerDescriptorEntry DescriptorEntry = new(
- typeof(GeneratedRequestInvokerRequest),
- typeof(string),
- new CqrsRequestInvokerDescriptor(
- typeof(IRequestHandler),
- typeof(NonStaticRequestInvokerProviderRegistry).GetMethod(
- nameof(InvokeGenerated),
- BindingFlags.NonPublic | BindingFlags.Instance)!));
-
///
public void Register(IServiceCollection services, ILogger logger)
{
@@ -347,9 +374,16 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Type responseType,
out CqrsRequestInvokerDescriptor? descriptor)
{
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
{
- descriptor = DescriptorEntry.Descriptor;
+ descriptor = new CqrsRequestInvokerDescriptor(
+ typeof(IRequestHandler),
+ typeof(NonStaticRequestInvokerProviderRegistry).GetMethod(
+ nameof(InvokeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Instance)!);
return true;
}
@@ -360,7 +394,17 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
///
public IReadOnlyList GetDescriptors()
{
- return [DescriptorEntry];
+ return
+ [
+ new CqrsRequestInvokerDescriptorEntry(
+ typeof(GeneratedRequestInvokerRequest),
+ typeof(string),
+ new CqrsRequestInvokerDescriptor(
+ typeof(IRequestHandler),
+ typeof(NonStaticRequestInvokerProviderRegistry).GetMethod(
+ nameof(InvokeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Instance)!))
+ ];
}
private ValueTask InvokeGenerated(object handler, object request, CancellationToken cancellationToken)
@@ -403,6 +447,9 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Type responseType,
out CqrsRequestInvokerDescriptor? descriptor)
{
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
{
descriptor = DescriptorEntry.Descriptor;
@@ -433,15 +480,6 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
ICqrsStreamInvokerProvider,
IEnumeratesCqrsStreamInvokerDescriptors
{
- private static readonly CqrsStreamInvokerDescriptorEntry DescriptorEntry = new(
- typeof(GeneratedStreamInvokerRequest),
- typeof(int),
- new CqrsStreamInvokerDescriptor(
- typeof(IStreamRequestHandler),
- typeof(NonStaticStreamInvokerProviderRegistry).GetMethod(
- nameof(InvokeGenerated),
- BindingFlags.NonPublic | BindingFlags.Instance)!));
-
///
public void Register(IServiceCollection services, ILogger logger)
{
@@ -459,9 +497,16 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
{
- descriptor = DescriptorEntry.Descriptor;
+ descriptor = new CqrsStreamInvokerDescriptor(
+ typeof(IStreamRequestHandler),
+ typeof(NonStaticStreamInvokerProviderRegistry).GetMethod(
+ nameof(InvokeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Instance)!);
return true;
}
@@ -472,7 +517,17 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
///
public IReadOnlyList GetDescriptors()
{
- return [DescriptorEntry];
+ return
+ [
+ new CqrsStreamInvokerDescriptorEntry(
+ typeof(GeneratedStreamInvokerRequest),
+ typeof(int),
+ new CqrsStreamInvokerDescriptor(
+ typeof(IStreamRequestHandler),
+ typeof(NonStaticStreamInvokerProviderRegistry).GetMethod(
+ nameof(InvokeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Instance)!))
+ ];
}
private object InvokeGenerated(object handler, object request, CancellationToken cancellationToken)
@@ -515,6 +570,9 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
{
descriptor = DescriptorEntry.Descriptor;
@@ -567,6 +625,9 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Type responseType,
out CqrsRequestInvokerDescriptor? descriptor)
{
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
{
descriptor = Descriptor;
@@ -608,6 +669,9 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
{
descriptor = Descriptor;
@@ -619,6 +683,84 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
}
}
+ ///
+ /// 模拟实现 request descriptor 枚举契约、但当前不暴露任何 descriptor 的 generated registry。
+ ///
+ private sealed class EmptyEnumeratingRequestInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsRequestInvokerProvider,
+ IEnumeratesCqrsRequestInvokerDescriptors
+ {
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IRequestHandler),
+ typeof(GeneratedRequestInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsRequestInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return Array.Empty();
+ }
+ }
+
+ ///
+ /// 模拟实现 stream descriptor 枚举契约、但当前不暴露任何 descriptor 的 generated registry。
+ ///
+ private sealed class EmptyEnumeratingStreamInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsStreamInvokerProvider,
+ IEnumeratesCqrsStreamInvokerDescriptors
+ {
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IStreamRequestHandler),
+ typeof(GeneratedStreamInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsStreamInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return Array.Empty();
+ }
+ }
+
///
/// 创建带有 generated request invoker registry 元数据的程序集替身。
///
diff --git a/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs
index 8d14cca1..94f86b8c 100644
--- a/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs
@@ -32,6 +32,9 @@ internal sealed class HiddenImplementationGeneratedStreamInvokerProviderRegistry
///
/// 承载处理器映射的服务集合。
/// 用于记录注册诊断的日志器。
+ ///
+ /// 当 或 为 时抛出。
+ ///
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
@@ -50,11 +53,17 @@ internal sealed class HiddenImplementationGeneratedStreamInvokerProviderRegistry
/// 流式响应元素类型。
/// 命中时返回的描述符。
/// 若类型对匹配当前测试流式请求则返回 。
+ ///
+ /// 当 或 为 时抛出。
+ ///
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
if (requestType == typeof(HiddenImplementationStreamInvokerContainer.VisibleStreamRequest)
&& responseType == typeof(int))
{
diff --git a/GFramework.Cqrs/CqrsRequestInvokerDescriptor.cs b/GFramework.Cqrs/CqrsRequestInvokerDescriptor.cs
index cc7cce14..d05c6b2e 100644
--- a/GFramework.Cqrs/CqrsRequestInvokerDescriptor.cs
+++ b/GFramework.Cqrs/CqrsRequestInvokerDescriptor.cs
@@ -19,6 +19,9 @@ public sealed class CqrsRequestInvokerDescriptor(
Type handlerType,
MethodInfo invokerMethod)
{
+ private static readonly string NonStaticInvokerMessage =
+ "CQRS request invoker descriptors require an open static invoker method so generated metadata can be bound deterministically.";
+
///
/// 获取请求处理器在容器中的服务类型。
///
@@ -27,5 +30,22 @@ public sealed class CqrsRequestInvokerDescriptor(
///
/// 获取执行请求处理器的开放静态方法。
///
- public MethodInfo InvokerMethod { get; } = invokerMethod ?? throw new ArgumentNullException(nameof(invokerMethod));
+ public MethodInfo InvokerMethod { get; } = ValidateInvokerMethod(invokerMethod);
+
+ ///
+ /// 在描述符构造阶段拒绝实例方法,避免非法 generated metadata 延迟到首次分发时才暴露。
+ ///
+ /// 待验证的 generated invoker 方法。
+ /// 通过校验的静态方法。
+ /// 当 为 时抛出。
+ /// 当 不是静态方法时抛出。
+ private static MethodInfo ValidateInvokerMethod(MethodInfo invokerMethod)
+ {
+ ArgumentNullException.ThrowIfNull(invokerMethod);
+
+ if (!invokerMethod.IsStatic)
+ throw new ArgumentException(NonStaticInvokerMessage, nameof(invokerMethod));
+
+ return invokerMethod;
+ }
}
diff --git a/GFramework.Cqrs/CqrsRequestInvokerDescriptorEntry.cs b/GFramework.Cqrs/CqrsRequestInvokerDescriptorEntry.cs
index 679652b6..81e36731 100644
--- a/GFramework.Cqrs/CqrsRequestInvokerDescriptorEntry.cs
+++ b/GFramework.Cqrs/CqrsRequestInvokerDescriptorEntry.cs
@@ -3,10 +3,39 @@ namespace GFramework.Cqrs;
///
/// 描述单个 request/response 类型对与其 generated invoker 元数据之间的映射条目。
///
-/// 请求运行时类型。
-/// 响应运行时类型。
-/// 对应的 generated request invoker 描述符。
-public sealed record CqrsRequestInvokerDescriptorEntry(
- Type RequestType,
- Type ResponseType,
- CqrsRequestInvokerDescriptor Descriptor);
+public sealed record CqrsRequestInvokerDescriptorEntry
+{
+ ///
+ /// 初始化 request invoker 描述符映射条目。
+ ///
+ /// 请求运行时类型。
+ /// 响应运行时类型。
+ /// 对应的 generated request invoker 描述符。
+ ///
+ /// 当 、 或 为 时抛出。
+ ///
+ public CqrsRequestInvokerDescriptorEntry(
+ Type requestType,
+ Type responseType,
+ CqrsRequestInvokerDescriptor descriptor)
+ {
+ RequestType = requestType ?? throw new ArgumentNullException(nameof(requestType));
+ ResponseType = responseType ?? throw new ArgumentNullException(nameof(responseType));
+ Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ ///
+ /// 获取请求运行时类型。
+ ///
+ public Type RequestType { get; }
+
+ ///
+ /// 获取响应运行时类型。
+ ///
+ public Type ResponseType { get; }
+
+ ///
+ /// 获取对应的 generated request invoker 描述符。
+ ///
+ public CqrsRequestInvokerDescriptor Descriptor { get; }
+}
diff --git a/GFramework.Cqrs/CqrsStreamInvokerDescriptor.cs b/GFramework.Cqrs/CqrsStreamInvokerDescriptor.cs
index 4f9f67e1..fd8d5a3c 100644
--- a/GFramework.Cqrs/CqrsStreamInvokerDescriptor.cs
+++ b/GFramework.Cqrs/CqrsStreamInvokerDescriptor.cs
@@ -19,6 +19,9 @@ public sealed class CqrsStreamInvokerDescriptor(
Type handlerType,
MethodInfo invokerMethod)
{
+ private static readonly string NonStaticInvokerMessage =
+ "CQRS stream invoker descriptors require an open static invoker method so generated metadata can be bound deterministically.";
+
///
/// 获取流式请求处理器在容器中的服务类型。
///
@@ -27,5 +30,22 @@ public sealed class CqrsStreamInvokerDescriptor(
///
/// 获取执行流式请求处理器的开放静态方法。
///
- public MethodInfo InvokerMethod { get; } = invokerMethod ?? throw new ArgumentNullException(nameof(invokerMethod));
+ public MethodInfo InvokerMethod { get; } = ValidateInvokerMethod(invokerMethod);
+
+ ///
+ /// 在描述符构造阶段拒绝实例方法,避免非法 generated metadata 延迟到首次建流时才暴露。
+ ///
+ /// 待验证的 generated invoker 方法。
+ /// 通过校验的静态方法。
+ /// 当 为 时抛出。
+ /// 当 不是静态方法时抛出。
+ private static MethodInfo ValidateInvokerMethod(MethodInfo invokerMethod)
+ {
+ ArgumentNullException.ThrowIfNull(invokerMethod);
+
+ if (!invokerMethod.IsStatic)
+ throw new ArgumentException(NonStaticInvokerMessage, nameof(invokerMethod));
+
+ return invokerMethod;
+ }
}
diff --git a/GFramework.Cqrs/CqrsStreamInvokerDescriptorEntry.cs b/GFramework.Cqrs/CqrsStreamInvokerDescriptorEntry.cs
index ffff9fb8..b760fb7f 100644
--- a/GFramework.Cqrs/CqrsStreamInvokerDescriptorEntry.cs
+++ b/GFramework.Cqrs/CqrsStreamInvokerDescriptorEntry.cs
@@ -3,10 +3,39 @@ namespace GFramework.Cqrs;
///
/// 描述单个 stream request/response 类型对与其 generated invoker 元数据之间的映射条目。
///
-/// 流式请求运行时类型。
-/// 流式响应元素类型。
-/// 对应的 generated stream invoker 描述符。
-public sealed record CqrsStreamInvokerDescriptorEntry(
- Type RequestType,
- Type ResponseType,
- CqrsStreamInvokerDescriptor Descriptor);
+public sealed record CqrsStreamInvokerDescriptorEntry
+{
+ ///
+ /// 初始化 stream invoker 描述符映射条目。
+ ///
+ /// 流式请求运行时类型。
+ /// 流式响应元素类型。
+ /// 对应的 generated stream invoker 描述符。
+ ///
+ /// 当 、 或 为 时抛出。
+ ///
+ public CqrsStreamInvokerDescriptorEntry(
+ Type requestType,
+ Type responseType,
+ CqrsStreamInvokerDescriptor descriptor)
+ {
+ RequestType = requestType ?? throw new ArgumentNullException(nameof(requestType));
+ ResponseType = responseType ?? throw new ArgumentNullException(nameof(responseType));
+ Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ ///
+ /// 获取流式请求运行时类型。
+ ///
+ public Type RequestType { get; }
+
+ ///
+ /// 获取流式响应元素类型。
+ ///
+ public Type ResponseType { get; }
+
+ ///
+ /// 获取对应的 generated stream invoker 描述符。
+ ///
+ public CqrsStreamInvokerDescriptor Descriptor { get; }
+}
diff --git a/ai-plan/public/cqrs-rewrite/archive/todos/cqrs-rewrite-validation-history-rp063-through-rp074.md b/ai-plan/public/cqrs-rewrite/archive/todos/cqrs-rewrite-validation-history-rp063-through-rp074.md
new file mode 100644
index 00000000..6a890501
--- /dev/null
+++ b/ai-plan/public/cqrs-rewrite/archive/todos/cqrs-rewrite-validation-history-rp063-through-rp074.md
@@ -0,0 +1,111 @@
+# CQRS 重写迁移验证归档(RP-063 至 RP-074)
+
+## 说明
+
+- 本文件承接 `cqrs-rewrite-validation-history-through-rp062.md` 之后的详细验证历史。
+- active tracking 只保留当前权威验证批次、最近 PR 锚点与下一恢复点;更早的命令级明细统一归档到这里。
+
+## 验证记录
+
+- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
+ - 结果:通过
+ - 备注:确认当时当前分支对应 `PR #305`,并定位到仍需本地复核的 CodeRabbit / Greptile open thread
+- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;本轮确认 XML 文档补齐、`NonParallelizable`、`_syncRoot` 命名与 `ai-plan` 收敛未引入新增编译问题
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests|FullyQualifiedName~CqrsArchitectureContextIntegrationTests.Handler_Can_Access_Architecture_Context|FullyQualifiedName~CqrsArchitectureContextAdvancedFeaturesTests.Request_With_Retry_Behavior_Should_Succeed_On_First_Attempt|FullyQualifiedName~CqrsArchitectureContextAdvancedFeaturesTests.Transient_Error_Request_Should_Succeed_Without_Simulated_Errors"`
+ - 结果:通过
+ - 备注:`5/5` passed;覆盖 generated invoker provider、真实上下文注入与两条重命名高级行为测试
+- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently|FullyQualifiedName~PublishAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently|FullyQualifiedName~CreateStream_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently"`
+ - 结果:通过
+ - 备注:`3/3` passed;确认并发首次解析测试在失败路径释放调整后保持通过
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~Emits_Direct_Type_Fallback_Metadata_When_All_Fallback_Handlers_Are_Referenceable_And_Runtime_Type_Contract_Is_Available|FullyQualifiedName~Emits_Mixed_Direct_Type_And_String_Fallback_Metadata_When_Runtime_Allows_Multiple_Fallback_Attributes"`
+ - 结果:通过
+ - 备注:`3/3` passed;确认 provider 生成分支注释与断言顺序修正未改变生成语义
+- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
+ - 结果:通过
+ - 备注:构建成功;并行验证期间出现过 `MSB3026` 拷贝重试噪音,属于同时运行多个 `dotnet` 命令时的输出文件竞争,不是持久性编译 warning
+- `bash scripts/validate-csharp-naming.sh`
+ - 结果:通过
+ - 备注:使用显式 `GIT_DIR` / `GIT_WORK_TREE` 绑定重跑后,`1045` 个 tracked C# 文件的命名校验全部通过;本轮 `_syncRoot` 改名未引入命名规则回归
+- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;本轮确认 notification publisher seam、README 与文档更新未引入 `GFramework.Cqrs` 构建告警
+- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认 stream invoker provider 生成与显式枚举接口实现未引入生成器编译问题
+- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认 stream invoker provider fixture 与回归断言可以编译通过
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
+ - 结果:通过
+ - 备注:`4/4` passed;覆盖 generated request / stream invoker provider 的 registrar 接线与 dispatcher 消费语义
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
+ - 结果:通过
+ - 备注:`2/2` passed;确认 generated registry 会同时发射 request / stream invoker provider 描述符与静态 invoker 方法
+- `GIT_DIR=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/.git/worktrees/GFramework-cqrs GIT_WORK_TREE=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework-WorkTree/GFramework-cqrs bash scripts/validate-csharp-naming.sh`
+ - 结果:通过
+ - 备注:`1059` 个 tracked C# 文件命名校验全部通过;本轮新增 stream invoker 类型与测试命名未引入回归
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
+ - 结果:通过
+ - 备注:`4/4` passed;确认 hidden implementation + visible interface 场景也会继续发射 request / stream invoker provider 元数据
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
+ - 结果:通过
+ - 备注:`8/8` passed;补齐 hidden implementation + visible interface 场景后,确认 generated request / stream invoker 在 runtime 侧也会优先命中 provider descriptor
+- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认本轮 precise reflected invoker provider 合同回归未引入 generator 编译告警
+- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;并行验证时曾出现过 `MSB3026` 输出文件竞争噪音,随后已串行重跑并得到干净构建结果
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_For_Precise_Reflected_Request_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_For_Precise_Reflected_Stream_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface"`
+ - 结果:通过
+ - 备注:`4/4` passed;串行确认 visible-interface hidden-implementation 仍发射 provider 元数据,而 precise reflected 注册继续保持“不发射 provider descriptor”的当前合同
+- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
+ - 结果:通过
+ - 备注:并行执行 build/test 时出现 `MSB3026` 输出文件竞争噪音;无真实编译错误,后续以串行 test 结果作为本轮 authoritative 行为验证
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Enumerator|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Enumerator|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
+ - 结果:通过
+ - 备注:`6/6` passed;锁定 request / stream provider gate 依赖“provider 接口 + descriptor 枚举接口”同时存在,且原有 happy-path 发射仍保持通过
+- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
+ - 结果:通过
+ - 备注:并行执行 build/test 时出现 `MSB3026` 输出文件竞争噪音;当前已确认没有新增 analyzer warning,`GFramework.Cqrs.Tests` 仍能完成 Release 构建
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Throw_When_Generated_Request_Invoker_Is_Not_Static|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Throw_When_Generated_Request_Invoker_Is_Incompatible|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Throw_When_Generated_Stream_Invoker_Is_Not_Static|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Throw_When_Generated_Stream_Invoker_Is_Incompatible|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Use_Generated_Request_Invoker_When_Provider_Is_Registered|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Use_Generated_Stream_Invoker_When_Provider_Is_Registered"`
+ - 结果:通过
+ - 备注:`6/6` passed;确认 request / stream 的非法 generated invoker 现统一抛出 `InvalidOperationException`,且原有 happy-path 未回归
+- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认新增 non-enumerating provider 回归未引入构建告警
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
+ - 结果:通过
+ - 备注:`14/14` passed;确认 request / stream 的 generated happy-path、异常路径与 non-enumerating provider 反射回退语义均保持通过
+- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认 `CqrsRuntimeModule` 接线变更未引入 `GFramework.Core` 模块构建问题
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsNotificationPublisherTests"`
+ - 结果:通过
+ - 备注:`5/5` 通过;覆盖自定义 publisher 顺序、上下文注入、零处理器、首错即停与默认接线复用
+- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
+ - 结果:通过
+ - 备注:`41/41` 通过;确认 CQRS 基础设施默认接线与容器行为未回归
+- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
+ - 结果:通过
+ - 备注:`42/42` 通过;本轮新增 legacy alias 回填回归后,确认正式 seam 与旧命名空间 alias 仍指向同一实例
+- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认 legacy alias helper 收敛与文档更新未引入 `GFramework.Core` 模块构建告警
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests|FullyQualifiedName~CqrsHandlerRegistrarTests|FullyQualifiedName~CqrsDispatcherCacheTests"`
+ - 结果:通过
+ - 备注:`22/22` 通过;确认 generated request invoker provider 的 registrar 接线、dispatcher 消费与现有 request/notification/stream cache 语义未回归
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
+ - 结果:通过
+ - 备注:`1/1` 通过;锁定 generator 会在 runtime 合同可用时发射 request invoker provider 成员
+- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认 request invoker provider seam 与 dispatcher/registrar 接线未引入新增构建告警
+- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
+ - 结果:通过
+ - 备注:`0 warning / 0 error`;确认三份 `Mediator` 命名收口后的 CQRS 测试项目构建仍然干净
+- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureContextTests"`
+ - 结果:通过
+ - 备注:`22/22` 通过;新增 `PublishAsync` / `CreateStream` 并发首次访问只解析一次 `ICqrsRuntime` 的回归
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index 1908139c..ee254359 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,7 +7,7 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-074`
+- 恢复点编号:`CQRS-REWRITE-RP-075`
- 当前阶段:`Phase 8`
- 当前焦点:
- 已完成一轮 `CQRS vs Mediator` 只读评估归档,结论已沉淀到 `archive/todos/cqrs-vs-mediator-assessment-rp063.md`
@@ -81,6 +81,12 @@ CQRS 迁移与收敛。
- 已完成一轮 non-enumerating provider reflection fallback 回归:
- `GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` 现新增 request / stream 两条回归,锁定当 registry 只暴露 provider 接口、但不实现 `IEnumeratesCqrs*InvokerDescriptors` 时,registrar 不会预热 dispatcher 缓存,后续 dispatch 会继续回退到既有反射路径
- 当前回归明确区分“provider 已注册”和“descriptor 已枚举入缓存”这两个阶段,避免后续把 `TryGetDescriptor(...)` 的存在误当成 dispatcher 会主动查询 provider 的合同
+ - 已完成 `PR #307` review follow-up 的当前批次:
+ - `CqrsRequestInvokerDescriptor` / `CqrsStreamInvokerDescriptor` 现会在构造阶段拒绝实例方法,避免非法 generated metadata 延迟到首次分发时才暴露
+ - `CqrsRequestInvokerDescriptorEntry` / `CqrsStreamInvokerDescriptorEntry` 现补齐公开入口空值防御,保持 request / stream 描述符合同一致
+ - `CqrsGeneratedRequestInvokerProviderTests` 现补齐空 descriptor 枚举回退回归,并把“非静态 invoker”语义收敛为 registrar 放弃 generated registry 后回退到反射路径
+ - `docs/zh-CN/core/cqrs.md` 已改正文档里“元数据异常会回退到反射”的错误表述,并将源码阅读表中的 `Internal/` 路径文案改为语义标签
+ - active tracking 已把 `RP-063` 至 `RP-074` 的命令级验证明细迁移到 `archive/todos/cqrs-rewrite-validation-history-rp063-through-rp074.md`,当前入口只保留最近权威验证与恢复点
- 当前相对 `origin/main` 的累计 branch diff 为 `24 files / 1754 changed lines`,仍低于本轮 `$gframework-batch-boot 50` 的主要 stop condition,可继续推进下一批低风险切片
- 已将 mixed fallback 场景进一步收敛:当 runtime 允许同一程序集声明多个 `CqrsReflectionFallbackAttribute` 实例时,generator 现会把可直接引用的 fallback handlers 与仅能按名称恢复的 fallback handlers 拆分发射
- `CqrsReflectionFallbackAttribute` 现允许多实例,以承载 `Type[]` 与字符串 fallback 元数据的组合输出
@@ -236,6 +242,7 @@ CQRS 迁移与收敛。
- 历史跟踪归档:[cqrs-rewrite-history-through-rp043.md](../archive/todos/cqrs-rewrite-history-through-rp043.md)
- 验证历史归档:[cqrs-rewrite-validation-history-through-rp062.md](../archive/todos/cqrs-rewrite-validation-history-through-rp062.md)
+- `RP-063` 至 `RP-074` 验证归档:[cqrs-rewrite-validation-history-rp063-through-rp074.md](../archive/todos/cqrs-rewrite-validation-history-rp063-through-rp074.md)
- CQRS 与 Mediator 评估归档:[cqrs-vs-mediator-assessment-rp063.md](../archive/todos/cqrs-vs-mediator-assessment-rp063.md)
- 历史 trace 归档:[cqrs-rewrite-history-through-rp043.md](../archive/traces/cqrs-rewrite-history-through-rp043.md)
- `RP-046` 至 `RP-061` trace 归档:[cqrs-rewrite-history-rp046-through-rp061.md](../archive/traces/cqrs-rewrite-history-rp046-through-rp061.md)
@@ -244,111 +251,19 @@ CQRS 迁移与收敛。
- `RP-043` 之前的详细阶段记录、定向验证命令和阶段性决策均已移入主题内归档
- `RP-046` 至 `RP-062` 的历史验证命令与阶段性结果已移入验证归档,active tracking 只保留当前恢复入口需要的最新验证
-- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
+- `RP-063` 至 `RP-074` 的详细验证命令与阶段性结果已移入验证归档,active tracking 只保留当前 PR 复核批次的权威结果
+- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
- 结果:通过
- - 备注:确认当前分支对应 `PR #305`,并定位到仍需本地复核的 CodeRabbit / Greptile open thread
-- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;本轮确认 XML 文档补齐、`NonParallelizable`、`_syncRoot` 命名与 `ai-plan` 收敛未引入新增编译问题
-- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests|FullyQualifiedName~CqrsArchitectureContextIntegrationTests.Handler_Can_Access_Architecture_Context|FullyQualifiedName~CqrsArchitectureContextAdvancedFeaturesTests.Request_With_Retry_Behavior_Should_Succeed_On_First_Attempt|FullyQualifiedName~CqrsArchitectureContextAdvancedFeaturesTests.Transient_Error_Request_Should_Succeed_Without_Simulated_Errors"`
- - 结果:通过
- - 备注:`5/5` passed;覆盖 generated invoker provider、真实上下文注入与两条重命名高级行为测试
-- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently|FullyQualifiedName~PublishAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently|FullyQualifiedName~CreateStream_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently"`
- - 结果:通过
- - 备注:`3/3` passed;确认并发首次解析测试在失败路径释放调整后保持通过
-- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~Emits_Direct_Type_Fallback_Metadata_When_All_Fallback_Handlers_Are_Referenceable_And_Runtime_Type_Contract_Is_Available|FullyQualifiedName~Emits_Mixed_Direct_Type_And_String_Fallback_Metadata_When_Runtime_Allows_Multiple_Fallback_Attributes"`
- - 结果:通过
- - 备注:`3/3` passed;确认 provider 生成分支注释与断言顺序修正未改变生成语义
+ - 备注:确认当前分支对应 `PR #307`,状态为 `OPEN`;当前仍有 `9` 条 CodeRabbit open thread,本轮只接受其中经本地复核后仍成立的合同防御、文档和 tracking 建议
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
- 结果:通过
- - 备注:构建成功;并行验证期间出现过 `MSB3026` 拷贝重试噪音,属于同时运行多个 `dotnet` 命令时的输出文件竞争,不是持久性编译 warning
-- `bash scripts/validate-csharp-naming.sh`
- - 结果:通过
- - 备注:使用显式 `GIT_DIR` / `GIT_WORK_TREE` 绑定重跑后,`1045` 个 tracked C# 文件的命名校验全部通过;本轮 `_syncRoot` 改名未引入命名规则回归
-- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;本轮确认 notification publisher seam、README 与文档更新未引入 `GFramework.Cqrs` 构建告警
-- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认 stream invoker provider 生成与显式枚举接口实现未引入生成器编译问题
-- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认 stream invoker provider fixture 与回归断言可以编译通过
+ - 备注:`0 warning / 0 error`;确认描述符前置防御、XML 文档与文档修正未引入 `GFramework.Cqrs` 模块告警
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
- 结果:通过
- - 备注:`4/4` passed;覆盖 generated request / stream invoker provider 的 registrar 接线与 dispatcher 消费语义
-- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- - 结果:通过
- - 备注:`2/2` passed;确认 generated registry 会同时发射 request / stream invoker provider 描述符与静态 invoker 方法
-- `GIT_DIR=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/.git/worktrees/GFramework-cqrs GIT_WORK_TREE=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework-WorkTree/GFramework-cqrs bash scripts/validate-csharp-naming.sh`
- - 结果:通过
- - 备注:`1059` 个 tracked C# 文件命名校验全部通过;本轮新增 stream invoker 类型与测试命名未引入回归
-- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- - 结果:通过
- - 备注:`4/4` passed;确认 hidden implementation + visible interface 场景也会继续发射 request / stream invoker provider 元数据
-- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
- - 结果:通过
- - 备注:`8/8` passed;补齐 hidden implementation + visible interface 场景后,确认 generated request / stream invoker 在 runtime 侧也会优先命中 provider descriptor
-- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认本轮 precise reflected invoker provider 合同回归未引入 generator 编译告警
-- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;并行验证时曾出现过 `MSB3026` 输出文件竞争噪音,随后已串行重跑并得到干净构建结果
-- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_For_Precise_Reflected_Request_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_For_Precise_Reflected_Stream_Registrations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface"`
- - 结果:通过
- - 备注:`4/4` passed;串行确认 visible-interface hidden-implementation 仍发射 provider 元数据,而 precise reflected 注册继续保持“不发射 provider descriptor”的当前合同
-- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
- - 结果:通过
- - 备注:并行执行 build/test 时出现 `MSB3026` 输出文件竞争噪音;无真实编译错误,后续以串行 test 结果作为本轮 authoritative 行为验证
-- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Enumerator|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Enumerator|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- - 结果:通过
- - 备注:`6/6` passed;锁定 request / stream provider gate 依赖“provider 接口 + descriptor 枚举接口”同时存在,且原有 happy-path 发射仍保持通过
-- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
- - 结果:通过
- - 备注:并行执行 build/test 时出现 `MSB3026` 输出文件竞争噪音;当前已确认没有新增 analyzer warning,`GFramework.Cqrs.Tests` 仍能完成 Release 构建
-- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Throw_When_Generated_Request_Invoker_Is_Not_Static|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Throw_When_Generated_Request_Invoker_Is_Incompatible|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Throw_When_Generated_Stream_Invoker_Is_Not_Static|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Throw_When_Generated_Stream_Invoker_Is_Incompatible|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.SendAsync_Should_Use_Generated_Request_Invoker_When_Provider_Is_Registered|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests.CreateStream_Should_Use_Generated_Stream_Invoker_When_Provider_Is_Registered"`
- - 结果:通过
- - 备注:`6/6` passed;确认 request / stream 的非法 generated invoker 现统一抛出 `InvalidOperationException`,且原有 happy-path 未回归
-- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认新增 non-enumerating provider 回归未引入构建告警
-- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
- - 结果:通过
- - 备注:`14/14` passed;确认 request / stream 的 generated happy-path、异常路径与 non-enumerating provider 反射回退语义均保持通过
-- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认 `CqrsRuntimeModule` 接线变更未引入 `GFramework.Core` 模块构建问题
-- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsNotificationPublisherTests"`
- - 结果:通过
- - 备注:`5/5` 通过;覆盖自定义 publisher 顺序、上下文注入、零处理器、首错即停与默认接线复用
-- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
- - 结果:通过
- - 备注:`41/41` 通过;确认 CQRS 基础设施默认接线与容器行为未回归
-- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
- - 结果:通过
- - 备注:`42/42` 通过;本轮新增 legacy alias 回填回归后,确认正式 seam 与旧命名空间 alias 仍指向同一实例
-- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认 legacy alias helper 收敛与文档更新未引入 `GFramework.Core` 模块构建告警
-- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests|FullyQualifiedName~CqrsHandlerRegistrarTests|FullyQualifiedName~CqrsDispatcherCacheTests"`
- - 结果:通过
- - 备注:`22/22` 通过;确认 generated request invoker provider 的 registrar 接线、dispatcher 消费与现有 request/notification/stream cache 语义未回归
-- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- - 结果:通过
- - 备注:`1/1` 通过;锁定 generator 会在 runtime 合同可用时发射 request invoker provider 成员
-- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认 request invoker provider seam 与 dispatcher/registrar 接线未引入新增构建告警
-- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
- - 结果:通过
- - 备注:`0 warning / 0 error`;确认三份 `Mediator` 命名收口后的 CQRS 测试项目构建仍然干净
-- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureContextTests"`
- - 结果:通过
- - 备注:`22/22` 通过;新增 `PublishAsync` / `CreateStream` 并发首次访问只解析一次 `ICqrsRuntime` 的回归
+ - 备注:`16/16` passed;确认非静态 invoker 回退语义、空 descriptor 枚举回退,以及既有 generated / incompatible 分支均保持通过
## 下一步
-1. 在保持 branch diff 明显低于 `50 files` 的前提下,继续挑选下一批低风险 `dispatch/invoker` 收敛切片,并优先考虑 request / stream provider 的剩余 runtime 失败边界、缓存预热边界或 generator gate 合同补强
+1. 在保持 branch diff 明显低于 `50 files` 的前提下,继续挑选下一批低风险 `dispatch/invoker` 收敛切片,并优先考虑 request / stream provider 的剩余缓存预热边界或 generator gate 合同补强
2. 基于已落地的 notification publisher seam,评估是否需要第二阶段公开配置面、并行 publisher 或 telemetry decorator
3. 单独规划旧 `Command` / `Query` API 的收口顺序;`LegacyICqrsRuntime` compatibility slice 已收口到显式 helper 与专门测试,可暂时移出最高优先级
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 4140cff1..8a3d6cde 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -2,6 +2,35 @@
## 2026-04-30
+### 阶段:PR #307 review follow-up 收敛(CQRS-REWRITE-RP-075)
+
+- 在 `RP-074` 后继续沿用 `gframework-batch-boot 50` 的低风险切片策略,本轮只处理 `$gframework-pr-review` 对当前 `PR #307` 仍然成立的本地问题
+- 主线程先用 `fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` 抓取 PR #307 的 latest-head open threads,确认真正仍需处理的项集中在:
+ - stream/request invoker 描述符入口缺少更早的合同防御
+ - request/stream provider 测试缺少“实现枚举契约但返回空 descriptor 集合”的回退覆盖
+ - `docs/zh-CN/core/cqrs.md` 把 generated metadata 不兼容时的行为误写成“回退到反射”
+ - active tracking 累积了 `RP-063` 至 `RP-074` 的长验证历史,不再适合作为默认恢复入口
+- 本轮实现收敛:
+ - `GFramework.Cqrs/CqrsRequestInvokerDescriptor.cs` 与 `GFramework.Cqrs/CqrsStreamInvokerDescriptor.cs` 现会在构造阶段拒绝实例方法,把非法 generated metadata 失败点前移到 registrar 激活/预热阶段
+ - `GFramework.Cqrs/CqrsRequestInvokerDescriptorEntry.cs` 与 `GFramework.Cqrs/CqrsStreamInvokerDescriptorEntry.cs` 现补齐公开构造入口的空值防御,并保持 request / stream 形状对称
+ - `GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` 现补齐 request / stream 的空 descriptor 枚举回退回归,并把“非静态 invoker”断言从首次分发抛错收敛为 registrar 放弃 generated registry 后回退到反射路径
+ - `GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs` 现补齐 `` XML 注释与 `TryGetDescriptor(...)` 参数空值防御
+ - `docs/zh-CN/core/cqrs.md` 现明确区分“未命中 generated descriptor 时回退到反射绑定”和“已命中的不兼容 generated metadata 会直接抛错”,并把 reader-facing 表格里的 `Internal/` 路径标签改成语义文案
+ - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` 现把恢复点推进到 `RP-075`,同时把 `RP-063` 至 `RP-074` 的命令级验证历史迁移到新的归档文件,active 入口只保留最近 PR 锚点与权威验证
+
+### 验证(RP-075)
+
+- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
+ - 结果:通过,`0 warning / 0 error`
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"`
+ - 结果:通过,`16/16` passed
+
+### 当前下一步(RP-075)
+
+1. 提交本轮 PR #307 review follow-up 收敛,保持恢复点、trace 与已验证代码状态一致
+2. 若继续下一批,优先挑选 request / stream provider 的缓存预热边界或 generator gate 合同补强,而不是扩散到新的模块
+3. 保持只暂存本轮相关文件,避免把工作区里无关的 `.gitignore` 本地改动混入提交
+
### 阶段:non-enumerating provider reflection fallback 回归(CQRS-REWRITE-RP-074)
- 在 `RP-073` 提交后继续按 `gframework-batch-boot 50` 执行;当前 branch diff 相对 `origin/main` 仍远低于 `50 files` 阈值,因此继续追加一轮单文件 runtime contract 回归
diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md
index e9b502ed..c705bfd3 100644
--- a/docs/zh-CN/core/cqrs.md
+++ b/docs/zh-CN/core/cqrs.md
@@ -167,7 +167,7 @@ protected override void OnInitialize()
2. 存在生成注册器时优先使用 `ICqrsHandlerRegistry`
3. 当生成注册器同时暴露 generated request invoker provider 时,runtime 会把 request/response 类型对对应的 descriptor 预先接线到 dispatcher 缓存,后续请求分发优先消费这些 generated request invoker 元数据
4. 当生成注册器同时暴露 generated stream invoker provider 时,runtime 会以同样方式优先消费 stream request 对应的 generated stream invoker descriptor;只有当前类型对未命中时,才回退到既有反射 stream binding
-5. 生成注册器不可用或元数据异常时记录告警并回退到反射路径
+5. 生成注册器不可用时记录告警并回退到反射路径;只有“未命中 generated descriptor”才会走反射绑定,已命中的不兼容元数据会直接抛出异常
6. 当生成注册器携带 `CqrsReflectionFallbackAttribute` 元数据时,运行时会先完成生成注册器注册,再补剩余 handler
7. `CqrsReflectionFallbackAttribute` 可以同时携带 `Type[]` 和 `string[]` 两类清单;运行时会优先复用直接 `Type` 条目,只对名称条目做定向 `Assembly.GetType(...)` 查找
8. 只有旧版空 marker 或生成注册器不可用时,才会回到整程序集反射扫描
@@ -236,7 +236,7 @@ RegisterCqrsPipelineBehavior>();
| `GFramework.Cqrs.Abstractions/Cqrs/` | `ICqrsRuntime`、`ICqrsHandlerRegistrar`、`IPipelineBehavior<,>`、`IRequestHandler<,>`、`Unit` | 请求、处理器和 runtime seam 的最小契约 |
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase`、`QueryBase`、`NotificationBase`、`RequestBase`、`ContextAwareCqrsExtensions` | 业务侧常用基类和上下文发送入口 |
| `GFramework.Cqrs/Cqrs/` | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>`、`AbstractRequestHandler<,>`、`AbstractStreamCommandHandler<,>`、`AbstractStreamQueryHandler<,>`、`LoggingBehavior<,>` | 默认处理器基类、上下文注入、流式处理与行为管道 |
-| `GFramework.Cqrs` 根入口与 `Internal/` | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry`、`CqrsHandlerRegistryAttribute`、`CqrsReflectionFallbackAttribute`、`ICqrsRequestInvokerProvider`、`ICqrsStreamInvokerProvider` | runtime 创建入口、generated-registry 优先级、request / stream invoker provider 协作点、targeted fallback 语义和程序集去重规则 |
+| 运行时入口与内部协作层 | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry`、`CqrsHandlerRegistryAttribute`、`CqrsReflectionFallbackAttribute`、`ICqrsRequestInvokerProvider`、`ICqrsStreamInvokerProvider` | runtime 创建入口、generated-registry 优先级、request / stream invoker provider 协作点、targeted fallback 语义和程序集去重规则 |
| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `CqrsHandlerRegistryGenerator`、`RuntimeTypeReferenceSpec`、`OrderedRegistrationKind` | 生成注册器、可直接引用类型判定、mixed fallback 发射与诊断边界 |
## 继续阅读