fix(cqrs): 收敛生成调用描述符与PR评审回归

- 修复 request 与 stream generated invoker 描述符的静态方法与空值防御,提前拒绝非法元数据

- 补充 provider 空描述符枚举与非静态 invoker 回退回归,更新相关 XML 注释与中文文档语义

- 更新 cqrs-rewrite 活跃跟踪、执行 trace 与验证归档,记录 PR #307 的当前验证结论
This commit is contained in:
gewuyou 2026-04-30 16:25:59 +08:00
parent 8b36626266
commit 83528742bb
10 changed files with 457 additions and 153 deletions

View File

@ -226,11 +226,11 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
}
/// <summary>
/// 验证当 generated request invoker provider 返回实例方法时,
/// dispatcher 会显式拒绝该描述符,而不是在后续绑定阶段静默接受非法合同
/// 验证当 generated request invoker provider 暴露实例方法时,
/// registrar 会放弃该 generated registry 并回退到运行时反射路径
/// </summary>
[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<InvalidOperationException>(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"));
}
/// <summary>
@ -270,11 +268,11 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
}
/// <summary>
/// 验证当 generated stream invoker provider 返回实例方法时,
/// dispatcher 会在首次建流时显式拒绝该描述符
/// 验证当 generated stream invoker provider 暴露实例方法时,
/// registrar 会放弃该 generated registry 并回退到运行时反射路径
/// </summary>
[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<InvalidOperationException>(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]));
}
/// <summary>
@ -313,6 +309,46 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Assert.That(exception!.Message, Does.Contain("incompatible invoker"));
}
/// <summary>
/// 验证当 generated request invoker provider 实现枚举契约、但返回空描述符集合时,
/// dispatcher 仍会回退到既有反射路径。
/// </summary>
[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"));
}
/// <summary>
/// 验证当 generated stream invoker provider 实现枚举契约、但返回空描述符集合时,
/// dispatcher 仍会回退到既有流式反射路径。
/// </summary>
[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]));
}
/// <summary>
/// 模拟返回实例 request invoker 方法的 generated registry。
/// </summary>
@ -321,15 +357,6 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
ICqrsRequestInvokerProvider,
IEnumeratesCqrsRequestInvokerDescriptors
{
private static readonly CqrsRequestInvokerDescriptorEntry DescriptorEntry = new(
typeof(GeneratedRequestInvokerRequest),
typeof(string),
new CqrsRequestInvokerDescriptor(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(NonStaticRequestInvokerProviderRegistry).GetMethod(
nameof(InvokeGenerated),
BindingFlags.NonPublic | BindingFlags.Instance)!));
/// <inheritdoc />
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<GeneratedRequestInvokerRequest, string>),
typeof(NonStaticRequestInvokerProviderRegistry).GetMethod(
nameof(InvokeGenerated),
BindingFlags.NonPublic | BindingFlags.Instance)!);
return true;
}
@ -360,7 +394,17 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
/// <inheritdoc />
public IReadOnlyList<CqrsRequestInvokerDescriptorEntry> GetDescriptors()
{
return [DescriptorEntry];
return
[
new CqrsRequestInvokerDescriptorEntry(
typeof(GeneratedRequestInvokerRequest),
typeof(string),
new CqrsRequestInvokerDescriptor(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(NonStaticRequestInvokerProviderRegistry).GetMethod(
nameof(InvokeGenerated),
BindingFlags.NonPublic | BindingFlags.Instance)!))
];
}
private ValueTask<string> 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<GeneratedStreamInvokerRequest, int>),
typeof(NonStaticStreamInvokerProviderRegistry).GetMethod(
nameof(InvokeGenerated),
BindingFlags.NonPublic | BindingFlags.Instance)!));
/// <inheritdoc />
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<GeneratedStreamInvokerRequest, int>),
typeof(NonStaticStreamInvokerProviderRegistry).GetMethod(
nameof(InvokeGenerated),
BindingFlags.NonPublic | BindingFlags.Instance)!);
return true;
}
@ -472,7 +517,17 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
/// <inheritdoc />
public IReadOnlyList<CqrsStreamInvokerDescriptorEntry> GetDescriptors()
{
return [DescriptorEntry];
return
[
new CqrsStreamInvokerDescriptorEntry(
typeof(GeneratedStreamInvokerRequest),
typeof(int),
new CqrsStreamInvokerDescriptor(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
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
}
}
/// <summary>
/// 模拟实现 request descriptor 枚举契约、但当前不暴露任何 descriptor 的 generated registry。
/// </summary>
private sealed class EmptyEnumeratingRequestInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsRequestInvokerProvider,
IEnumeratesCqrsRequestInvokerDescriptors
{
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(GeneratedRequestInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsRequestInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsRequestInvokerDescriptorEntry> GetDescriptors()
{
return Array.Empty<CqrsRequestInvokerDescriptorEntry>();
}
}
/// <summary>
/// 模拟实现 stream descriptor 枚举契约、但当前不暴露任何 descriptor 的 generated registry。
/// </summary>
private sealed class EmptyEnumeratingStreamInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsStreamInvokerProvider,
IEnumeratesCqrsStreamInvokerDescriptors
{
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(GeneratedStreamInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsStreamInvokerDescriptorEntry> GetDescriptors()
{
return Array.Empty<CqrsStreamInvokerDescriptorEntry>();
}
}
/// <summary>
/// 创建带有 generated request invoker registry 元数据的程序集替身。
/// </summary>

View File

@ -32,6 +32,9 @@ internal sealed class HiddenImplementationGeneratedStreamInvokerProviderRegistry
/// </summary>
/// <param name="services">承载处理器映射的服务集合。</param>
/// <param name="logger">用于记录注册诊断的日志器。</param>
/// <exception cref="ArgumentNullException">
/// 当 <paramref name="services" /> 或 <paramref name="logger" /> 为 <see langword="null" /> 时抛出。
/// </exception>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
@ -50,11 +53,17 @@ internal sealed class HiddenImplementationGeneratedStreamInvokerProviderRegistry
/// <param name="responseType">流式响应元素类型。</param>
/// <param name="descriptor">命中时返回的描述符。</param>
/// <returns>若类型对匹配当前测试流式请求则返回 <see langword="true" />。</returns>
/// <exception cref="ArgumentNullException">
/// 当 <paramref name="requestType" /> 或 <paramref name="responseType" /> 为 <see langword="null" /> 时抛出。
/// </exception>
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
if (requestType == typeof(HiddenImplementationStreamInvokerContainer.VisibleStreamRequest)
&& responseType == typeof(int))
{

View File

@ -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.";
/// <summary>
/// 获取请求处理器在容器中的服务类型。
/// </summary>
@ -27,5 +30,22 @@ public sealed class CqrsRequestInvokerDescriptor(
/// <summary>
/// 获取执行请求处理器的开放静态方法。
/// </summary>
public MethodInfo InvokerMethod { get; } = invokerMethod ?? throw new ArgumentNullException(nameof(invokerMethod));
public MethodInfo InvokerMethod { get; } = ValidateInvokerMethod(invokerMethod);
/// <summary>
/// 在描述符构造阶段拒绝实例方法,避免非法 generated metadata 延迟到首次分发时才暴露。
/// </summary>
/// <param name="invokerMethod">待验证的 generated invoker 方法。</param>
/// <returns>通过校验的静态方法。</returns>
/// <exception cref="ArgumentNullException">当 <paramref name="invokerMethod" /> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="ArgumentException">当 <paramref name="invokerMethod" /> 不是静态方法时抛出。</exception>
private static MethodInfo ValidateInvokerMethod(MethodInfo invokerMethod)
{
ArgumentNullException.ThrowIfNull(invokerMethod);
if (!invokerMethod.IsStatic)
throw new ArgumentException(NonStaticInvokerMessage, nameof(invokerMethod));
return invokerMethod;
}
}

View File

@ -3,10 +3,39 @@ namespace GFramework.Cqrs;
/// <summary>
/// 描述单个 request/response 类型对与其 generated invoker 元数据之间的映射条目。
/// </summary>
/// <param name="RequestType">请求运行时类型。</param>
/// <param name="ResponseType">响应运行时类型。</param>
/// <param name="Descriptor">对应的 generated request invoker 描述符。</param>
public sealed record CqrsRequestInvokerDescriptorEntry(
Type RequestType,
Type ResponseType,
CqrsRequestInvokerDescriptor Descriptor);
public sealed record CqrsRequestInvokerDescriptorEntry
{
/// <summary>
/// 初始化 request invoker 描述符映射条目。
/// </summary>
/// <param name="requestType">请求运行时类型。</param>
/// <param name="responseType">响应运行时类型。</param>
/// <param name="descriptor">对应的 generated request invoker 描述符。</param>
/// <exception cref="ArgumentNullException">
/// 当 <paramref name="requestType" />、<paramref name="responseType" /> 或 <paramref name="descriptor" /> 为 <see langword="null" /> 时抛出。
/// </exception>
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));
}
/// <summary>
/// 获取请求运行时类型。
/// </summary>
public Type RequestType { get; }
/// <summary>
/// 获取响应运行时类型。
/// </summary>
public Type ResponseType { get; }
/// <summary>
/// 获取对应的 generated request invoker 描述符。
/// </summary>
public CqrsRequestInvokerDescriptor Descriptor { get; }
}

View File

@ -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.";
/// <summary>
/// 获取流式请求处理器在容器中的服务类型。
/// </summary>
@ -27,5 +30,22 @@ public sealed class CqrsStreamInvokerDescriptor(
/// <summary>
/// 获取执行流式请求处理器的开放静态方法。
/// </summary>
public MethodInfo InvokerMethod { get; } = invokerMethod ?? throw new ArgumentNullException(nameof(invokerMethod));
public MethodInfo InvokerMethod { get; } = ValidateInvokerMethod(invokerMethod);
/// <summary>
/// 在描述符构造阶段拒绝实例方法,避免非法 generated metadata 延迟到首次建流时才暴露。
/// </summary>
/// <param name="invokerMethod">待验证的 generated invoker 方法。</param>
/// <returns>通过校验的静态方法。</returns>
/// <exception cref="ArgumentNullException">当 <paramref name="invokerMethod" /> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="ArgumentException">当 <paramref name="invokerMethod" /> 不是静态方法时抛出。</exception>
private static MethodInfo ValidateInvokerMethod(MethodInfo invokerMethod)
{
ArgumentNullException.ThrowIfNull(invokerMethod);
if (!invokerMethod.IsStatic)
throw new ArgumentException(NonStaticInvokerMessage, nameof(invokerMethod));
return invokerMethod;
}
}

View File

@ -3,10 +3,39 @@ namespace GFramework.Cqrs;
/// <summary>
/// 描述单个 stream request/response 类型对与其 generated invoker 元数据之间的映射条目。
/// </summary>
/// <param name="RequestType">流式请求运行时类型。</param>
/// <param name="ResponseType">流式响应元素类型。</param>
/// <param name="Descriptor">对应的 generated stream invoker 描述符。</param>
public sealed record CqrsStreamInvokerDescriptorEntry(
Type RequestType,
Type ResponseType,
CqrsStreamInvokerDescriptor Descriptor);
public sealed record CqrsStreamInvokerDescriptorEntry
{
/// <summary>
/// 初始化 stream invoker 描述符映射条目。
/// </summary>
/// <param name="requestType">流式请求运行时类型。</param>
/// <param name="responseType">流式响应元素类型。</param>
/// <param name="descriptor">对应的 generated stream invoker 描述符。</param>
/// <exception cref="ArgumentNullException">
/// 当 <paramref name="requestType" />、<paramref name="responseType" /> 或 <paramref name="descriptor" /> 为 <see langword="null" /> 时抛出。
/// </exception>
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));
}
/// <summary>
/// 获取流式请求运行时类型。
/// </summary>
public Type RequestType { get; }
/// <summary>
/// 获取流式响应元素类型。
/// </summary>
public Type ResponseType { get; }
/// <summary>
/// 获取对应的 generated stream invoker 描述符。
/// </summary>
public CqrsStreamInvokerDescriptor Descriptor { get; }
}

View File

@ -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` 的回归

View File

@ -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 与专门测试,可暂时移出最高优先级

View File

@ -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` 现补齐 `<exception>` 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 回归

View File

@ -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<LoggingBehavior<,>>();
| `GFramework.Cqrs.Abstractions/Cqrs/` | `ICqrsRuntime``ICqrsHandlerRegistrar``IPipelineBehavior<,>``IRequestHandler<,>``Unit` | 请求、处理器和 runtime seam 的最小契约 |
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase<TInput, TResponse>``QueryBase<TInput, TResponse>``NotificationBase<TInput>``RequestBase<TInput, TResponse>``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 发射与诊断边界 |
## 继续阅读