From e5b173c29abb4ad2faf211bf8f20fd2075c1945c Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 13 May 2026 09:04:13 +0800 Subject: [PATCH] =?UTF-8?q?fix(cqrs):=20=E4=BF=AE=E6=AD=A3request=E7=AE=A1?= =?UTF-8?q?=E9=81=93=E4=B8=AD=E7=9A=84generated=20invoker=E5=9B=9E?= =?UTF-8?q?=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 request pipeline 末端继续复用缓存 RequestInvoker 的运行时语义 - 补充 generated request invoker 与 stream 缺失 handler 的回归测试覆盖 - 更新 benchmark reader-facing 注释与 cqrs-rewrite 恢复入口记录 --- .../Messaging/NotificationFanOutBenchmarks.cs | 4 +- .../Messaging/RequestBenchmarks.cs | 2 +- .../Messaging/RequestLifetimeBenchmarks.cs | 4 +- .../Messaging/StreamingBenchmarks.cs | 2 +- .../CqrsDispatcherContextValidationTests.cs | 23 +++++++ ...qrsGeneratedRequestInvokerProviderTests.cs | 61 ++++++++++++++++++ GFramework.Cqrs/Internal/CqrsDispatcher.cs | 25 +++++--- .../todos/cqrs-rewrite-migration-tracking.md | 62 ++++++++++++++----- .../traces/cqrs-rewrite-migration-trace.md | 45 ++++++++++++++ 9 files changed, 198 insertions(+), 30 deletions(-) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs index 0e3efb1a..29ee33ea 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs @@ -22,8 +22,8 @@ using GeneratedMediator = Mediator.Mediator; namespace GFramework.Cqrs.Benchmarks.Messaging; /// -/// 对比固定 4 个处理器的 notification fan-out publish 在 baseline、GFramework.CQRS、NuGet `Mediator` -/// 与 MediatR 之间的开销。 +/// 对比固定 4 个处理器的 notification fan-out publish 在 baseline、GFramework.CQRS 默认顺序发布器、 +/// GFramework.CQRS 内置 TaskWhenAllNotificationPublisher、NuGet `Mediator` 与 MediatR 之间的开销。 /// [Config(typeof(Config))] public class NotificationFanOutBenchmarks diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs index ff5ca2c4..f0dbf4b9 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs @@ -132,7 +132,7 @@ public class RequestBenchmarks } /// - /// 通过 `ai-libs/Mediator` 的 source-generated concrete mediator 发送 request,作为高性能对照组。 + /// 通过 NuGet `Mediator` 的 source-generated concrete mediator 发送 request,作为高性能对照组。 /// /// 代表当前 `Mediator` request dispatch 完成的值任务。 [Benchmark] diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs index f12f83e6..6869b0ab 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs @@ -201,7 +201,7 @@ public class RequestLifetimeBenchmarks } /// - /// 按生命周期把 benchmark request handler 注册到 GFramework 容器。 + /// 按生命周期把 benchmark request handler 注册到 GFramework 容器。 /// /// 当前 benchmark 拥有并负责释放的容器。 /// 待比较的 handler 生命周期。 @@ -248,7 +248,7 @@ public class RequestLifetimeBenchmarks } /// - /// Benchmark request。 + /// Benchmark request。 /// /// 请求标识。 public sealed record BenchmarkRequest(Guid Id) : diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs index 449c1bba..a4dff680 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs @@ -168,7 +168,7 @@ public class StreamingBenchmarks } /// - /// 通过 `ai-libs/Mediator` 的 source-generated concrete mediator 创建 stream,并按当前观测模式消费。 + /// 通过 NuGet `Mediator` 的 source-generated concrete mediator 创建 stream,并按当前观测模式消费。 /// /// 按当前观测模式完成 stream 消费后的等待句柄。 [Benchmark] diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs index e3e0d337..c189eb53 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs @@ -147,6 +147,29 @@ internal sealed class CqrsDispatcherContextValidationTests Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext")); } + /// + /// 验证 stream handler 缺失时,dispatcher 会在建流调用点同步抛出异常, + /// 而不是返回一个延迟到枚举阶段才失败的异步流。 + /// + [Test] + public void CreateStream_Should_Throw_When_Handler_Is_Missing() + { + var runtime = CreateRuntime( + container => + { + container + .Setup(currentContainer => currentContainer.Get(typeof(IStreamRequestHandler))) + .Returns((object?)null); + container + .Setup(currentContainer => currentContainer.HasRegistration(typeof(IStreamPipelineBehavior))) + .Returns(false); + }); + + Assert.That( + () => runtime.CreateStream(new FakeCqrsContext(), new ContextAwareStreamRequest()), + Throws.InvalidOperationException.With.Message.Contains("No CQRS stream handler registered")); + } + /// /// 验证当 stream pipeline behavior 需要上下文注入、但当前 CQRS 上下文不实现 /// 时, diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs index 227f101d..6c16b20e 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Reflection; +using System.Threading; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Ioc; @@ -28,6 +29,7 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests { _previousLoggerFactoryProvider = LoggerFactoryResolver.Provider; LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); + GeneratedRequestPipelineTrackingBehavior.InvocationCount = 0; GeneratedStreamPipelineTrackingBehavior.InvocationCount = 0; ClearRegistrarCaches(); ClearDispatcherCaches(); @@ -40,6 +42,7 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests public void TearDown() { LoggerFactoryResolver.Provider = _previousLoggerFactoryProvider ?? new ConsoleLoggerFactoryProvider(); + GeneratedRequestPipelineTrackingBehavior.InvocationCount = 0; GeneratedStreamPipelineTrackingBehavior.InvocationCount = 0; ClearRegistrarCaches(); ClearDispatcherCaches(); @@ -181,6 +184,30 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests Assert.That(response, Is.EqualTo("generated-hidden:payload")); } + /// + /// 验证 generated request invoker 与 request pipeline 行为同时存在时, + /// dispatcher 仍会保持 generated invoker 优先,并正确复用现有 request 执行链。 + /// + [Test] + public async Task SendAsync_Should_Use_Generated_Request_Invoker_Inside_Request_Pipeline() + { + var generatedAssembly = CreateGeneratedRequestInvokerAssembly(); + var container = new MicrosoftDiContainer(); + container.RegisterCqrsPipelineBehavior(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var context = new ArchitectureContext(container); + var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false); + + Assert.Multiple(() => + { + Assert.That(response, Is.EqualTo("generated:payload")); + Assert.That(GeneratedRequestPipelineTrackingBehavior.InvocationCount, Is.EqualTo(1)); + }); + } + /// /// 验证 dispatcher 在首次创建 stream binding 时,会优先消费 generated stream invoker provider。 /// @@ -608,6 +635,40 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests Assert.That(results, Is.EqualTo([30, 31])); } + /// + /// 记录 generated request invoker 与 request pipeline 行为组合时的命中次数。 + /// + private sealed class GeneratedRequestPipelineTrackingBehavior + : IPipelineBehavior + { + private static int _invocationCount; + + /// + /// 获取或重置当前测试进程中的行为触发次数。 + /// + public static int InvocationCount + { + get => Volatile.Read(ref _invocationCount); + set => Volatile.Write(ref _invocationCount, value); + } + + /// + /// 记录一次行为执行,然后继续执行 generated request invoker。 + /// + /// 当前请求消息。 + /// 下一个处理阶段。 + /// 取消令牌。 + /// 下游处理阶段返回的响应。 + public ValueTask Handle( + GeneratedRequestInvokerRequest message, + MessageHandlerDelegate next, + CancellationToken cancellationToken) + { + Interlocked.Increment(ref _invocationCount); + return next(message, cancellationToken); + } + } + /// /// 模拟返回实例 request invoker 方法的 generated registry。 /// diff --git a/GFramework.Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Cqrs/Internal/CqrsDispatcher.cs index b9bab1db..3e039e3e 100644 --- a/GFramework.Cqrs/Internal/CqrsDispatcher.cs +++ b/GFramework.Cqrs/Internal/CqrsDispatcher.cs @@ -150,7 +150,7 @@ internal sealed class CqrsDispatcher( } return dispatchBinding.GetPipelineExecutor(behaviors.Count) - .Invoke(handler, behaviors, request, cancellationToken); + .Invoke(handler, behaviors, dispatchBinding.RequestInvoker, request, cancellationToken); } catch (Exception exception) { @@ -604,12 +604,14 @@ internal sealed class CqrsDispatcher( private static ValueTask InvokeRequestPipelineExecutorAsync( object handler, IReadOnlyList behaviors, + RequestInvoker requestInvoker, object request, CancellationToken cancellationToken) where TRequest : IRequest { var invocation = new RequestPipelineInvocation( - (IRequestHandler)handler, + handler, + requestInvoker, behaviors); return invocation.InvokeAsync((TRequest)request, cancellationToken); } @@ -669,6 +671,7 @@ internal sealed class CqrsDispatcher( private delegate ValueTask RequestPipelineInvoker( object handler, IReadOnlyList behaviors, + RequestInvoker requestInvoker, object request, CancellationToken cancellationToken); @@ -978,6 +981,7 @@ internal sealed class CqrsDispatcher( public ValueTask Invoke( object handler, IReadOnlyList behaviors, + RequestInvoker requestInvoker, object request, CancellationToken cancellationToken) { @@ -987,7 +991,7 @@ internal sealed class CqrsDispatcher( $"Cached request pipeline executor expected {BehaviorCount} behaviors, but received {behaviors.Count}."); } - return invoker(handler, behaviors, request, cancellationToken); + return invoker(handler, behaviors, requestInvoker, request, cancellationToken); } } @@ -1147,11 +1151,13 @@ internal sealed class CqrsDispatcher( /// 该对象只存在于本次分发,不会跨请求保留容器解析出的实例。 /// private sealed class RequestPipelineInvocation( - IRequestHandler handler, + object handler, + RequestInvoker requestInvoker, IReadOnlyList behaviors) where TRequest : IRequest { - private readonly IRequestHandler _handler = handler; + private readonly object _handler = handler; + private readonly RequestInvoker _requestInvoker = requestInvoker; private readonly IReadOnlyList _behaviors = behaviors; private readonly MessageHandlerDelegate?[] _continuations = new MessageHandlerDelegate?[behaviors.Count + 1]; @@ -1198,11 +1204,16 @@ internal sealed class CqrsDispatcher( } /// - /// 调用最终请求处理器。 + /// 调用最终请求处理入口。 /// + /// + /// request pipeline 末端必须继续复用当前 binding 上缓存的 , + /// 这样 generated request invoker provider 才能在接入 pipeline 后保持与无 pipeline 路径一致的调用语义, + /// 而不是退回到接口虚调用路径。 + /// private ValueTask InvokeHandlerAsync(TRequest request, CancellationToken cancellationToken) { - return _handler.Handle(request, cancellationToken); + return _requestInvoker(_handler, request, cancellationToken); } /// 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 05e37c78..39c347f9 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 @@ -12,21 +12,32 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-142` +- 恢复点编号:`CQRS-REWRITE-RP-143` - 当前阶段:`Phase 8` - 当前 PR 锚点:`PR #350(MERGED,2026-05-13)` - 当前结论: - - 本轮按 `$gframework-batch-boot 50` 恢复后,先核对本地仓库真值,确认 `feat/cqrs-optimization` 已与 - `origin/main` 指向同一合并提交 `4837aa2a`,此前 tracking 中的 `PR #350(OPEN)`、`14 files` 等事实已过期。 - - 当前 topic 的 benchmark runtime 修正、XML 文档补齐与 `README` 边界收口已经随 `PR #350` 合并进入 - `origin/main`,不再存在可继续扩批的活动写面。 - - 这轮收口不再继续新增 benchmark 或测试切片,而是把 public recovery 入口刷新为“已合并、无 branch diff、等待下一轮新任务”的状态, - 避免后续 `boot` 落回已完成的 PR 上下文。 - - recovery 刷新提交落地后,当前 branch-wide 停止原因仍不是 `50 files` 阈值,而是语义边界已经完成: - - 刷新开始前,`origin/main...HEAD` 的评估结果为 `0 files / 0 lines` - - 当前工作树干净 - - 当前唯一新增 diff 只来自 `cqrs-rewrite` 的 public recovery 文档刷新 - - 继续在同一 topic 上机械扩批不会产生新的低风险、可验证切片 + - 在用户允许 subagent 后,本轮按 `context-budget 优先、reviewability 次之、50 files 仅作粗阈值` 重开了一波小型 multi-agent 批处理, + 只接受单文件或窄文件组的 docs/test 切片。 + - 已接受的低风险切片: + - `GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs` + - 修正两处 XML 文档缩进异常 + - `GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs` + - 类级摘要明确当前 `GFramework.CQRS` fan-out 对照包含默认顺序发布器与内置 `TaskWhenAllNotificationPublisher` + - `GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs` + - 将 `Mediator` reader-facing 注释统一为 “NuGet `Mediator` 的 source-generated concrete mediator” + - `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs` + - 补一条 stream 缺失 handler 的失败语义回归,固定当前分支在调用点同步抛 `InvalidOperationException` + - worker 在 `CqrsGeneratedRequestInvokerProviderTests.cs` 补 request/generated + pipeline 对称测试后,主线程确认这不是环境噪音,而是命中了真实运行时缺口: + - request 路径在接入 `IPipelineBehavior<,>` 后,会退回 `_handler.Handle(...)` + - 因此 generated request invoker 无法像 stream 路径那样在 pipeline 末端继续保持优先 + - 主线程已在 `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 收口该缺口: + - request pipeline executor 现在显式复用当前 binding 缓存的 `RequestInvoker` + - generated request invoker provider 在接入 pipeline 后保持与无 pipeline 路径一致的调用语义 + - 当前 stop decision: + - 不再继续下一波 + - 原因不是 `50 files` 阈值耗尽;当前 accepted scope 仍然很小 + - 停止原因是当前上下文已接近本轮安全预算,而剩余候选只剩 descriptor / factory / publisher 类小测试,继续扩批收益明显下降 ## 当前活跃事实 @@ -34,12 +45,19 @@ CQRS 迁移与收敛。 - 当前 HEAD / 基线:`origin/main @ 4837aa2a (2026-05-12 20:37:56 +0800)` - 当前 PR:`PR #350(已合并到 origin/main)` - 当前写面: + - `GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs` + - `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs` + - `GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` + - `GFramework.Cqrs/Internal/CqrsDispatcher.cs` - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` - 当前基线: - `origin/main @ 4837aa2a (2026-05-12 20:37:56 +0800)` -- 当前已提交 branch diff 只涉及 `2` 个 `ai-plan/public/cqrs-rewrite/**` recovery 文档 -- 当前工作面已收口为 public recovery 文档刷新;当前工作树干净,CQRS benchmark 代码不再处于活跃修改状态 +- 当前 batch working-tree diff:`7` 个源码 / 测试文件 +- 当前工作面已收口为 docs/test 小切片 + 1 处 request pipeline runtime 修正;没有重新打开 benchmark 工程设计级改造 - 最近已合并提交: - `2dd9435c` `fix(cqrs-benchmarks): 修正Mediator基准运行时配置` - `e3532fc2` `feat(cqrs-benchmarks): 补齐request生命周期的Mediator对照` @@ -51,6 +69,7 @@ CQRS 迁移与收敛。 或独立 benchmark 工程,而不是在同一份 source-generated 产物上切换 runtime `ServiceLifetime`。 - 如果 `feat/cqrs-optimization` 继续承载新的 CQRS 任务而不先分支,public recovery 入口会把“已合并的 PR #350”与下一轮新工作混在一起。 - 若未来再开 benchmark XML / docs 波次,仍需要主线程先抽样核对代表文件,避免重复接受误报 inventory。 +- 剩余低风险候选主要是 `NotificationPublisher` / invoker descriptor / `CqrsRuntimeFactory` 的单文件测试;它们不是当前上下文预算下的高收益下一波。 ## 最近权威验证 @@ -62,13 +81,22 @@ CQRS 迁移与收敛。 - 备注:确认当前 branch diff 始终只覆盖两份 `ai-plan` recovery 文档,没有重新打开 CQRS benchmark 代码写面 - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` - 结果:通过,`0 warning / 0 error` - - 备注:作为当前 recovery 刷新任务的最小 build validation,继续确认 benchmark 工程在合并后保持可编译 + - 备注:确认 benchmark reader-facing 文档收口后工程仍保持 `Release` 可编译 +- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release` + - 结果:通过,`0 warning / 0 error` + - 备注:确认 request pipeline 复用 generated invoker 的 runtime 修正未引入编译回归 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"` + - 结果:通过,`Passed: 28, Failed: 0` + - 备注:确认 generated request invoker 在接入 request pipeline 后仍保持优先,并通过新增回归测试锁定 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsDispatcherContextValidationTests"` + - 结果:通过,`Passed: 7, Failed: 0` + - 备注:确认 stream 缺失 handler 的失败语义回归与既有上下文校验测试仍全部通过 ## 下一推荐步骤 -1. 若要继续 CQRS 主题的新一轮实现,先把当前 recovery 刷新提交合并或重放到新的 topic branch,再从最新 `origin/main` 继续,而不是复用已完成的 `PR #350` 上下文。 +1. 若继续 CQRS 主题的低风险硬化,优先从 `NotificationPublisher`、invoker descriptor、`CqrsRuntimeFactory` 三类单文件测试候选中再挑一条,不要重新打开 benchmark 设计级议题。 2. 若后续重新打开 `Mediator` 生命周期 parity 工作,优先设计独立 compile-time config / 独立 benchmark 工程,并把该设计单独记录到新的 tracking phase。 -3. 若只是恢复本 worktree 继续其他 topic,可把 `cqrs-rewrite` 视为“当前已在自然停点完成”的历史入口,不再默认把 benchmark README / XML 清扫当作活跃批处理目标。 +3. 若只是恢复其他 topic,可把当前 `cqrs-rewrite` active 入口视为“本轮已在上下文预算前的自然停点完成”。 ## 活跃文档 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 d0f9070a..83756595 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 @@ -7,6 +7,51 @@ SPDX-License-Identifier: Apache-2.0 ## 2026-05-13 +### 阶段:允许 subagent 后的低风险多 Agent 收口(CQRS-REWRITE-RP-143) + +- 在用户允许 subagent 后,主线程切到小型 multi-agent 协调模式,但仍把 critical path 保留在本地: + - 主线程负责候选筛选、runtime 缺口判断、最终验证与 `ai-plan` + - explorer 只做只读盘点 + - worker 只接单文件或窄文件组 ownership +- explorer 结论汇总: + - 没有值得继续扩成“实现型 benchmark 波次”的大切片 + - 低风险候选主要是 reader-facing 术语对齐与单文件回归测试 + - `Mediator` 生命周期 parity、notification fan-out 生命周期矩阵、benchmark 工程级拆分仍判定为高风险 +- accepted worker / 主线程 scope: + - `RequestLifetimeBenchmarks.cs` + - 主线程修正 2 处 XML 文档缩进异常 + - `NotificationFanOutBenchmarks.cs` + - `RequestBenchmarks.cs` + - `StreamingBenchmarks.cs` + - worker 收口 benchmark reader-facing 术语,build 通过 + - `CqrsDispatcherContextValidationTests.cs` + - worker 补 stream 缺失 handler 的同步抛错回归,targeted test 通过 + - `CqrsGeneratedRequestInvokerProviderTests.cs` + - worker 补 generated request + pipeline 对称测试 + - 首次运行失败,暴露 request pipeline 路径在接入 behavior 后退回 `_handler.Handle(...)` + - `CqrsDispatcher.cs` + - 主线程将 request pipeline 末端改为继续复用当前 binding 的 `RequestInvoker` + - 使 generated request invoker provider 在 pipeline 存在时保持与无 pipeline 路径一致 +- 本轮权威验证: + - `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"` + - 结果:通过,`Passed: 28, Failed: 0` + - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsDispatcherContextValidationTests"` + - 结果:通过,`Passed: 7, Failed: 0` + - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` + - 结果:通过,`0 warning / 0 error` +- 当前 stop decision: + - 不再继续下一波 + - 原因不是 branch-size;当前变更仍远低于 `$gframework-batch-boot 50`` + - 停止原因是主线程上下文已接近本轮安全预算,而剩余候选只剩收益较低的单文件测试硬化 +- 当前下一步: + - 更新 `ai-plan/public/cqrs-rewrite/**` + - 运行 license-header / `git diff --check` + - 提交本轮多 Agent 小波次收口 + +## 2026-05-13 + ### 阶段:PR #350 合并后的 recovery 入口刷新(CQRS-REWRITE-RP-142) - 继续按 `$gframework-batch-boot 50` 恢复当前 topic,但启动时先核对分支真值,而不是沿用 active tracking 中的旧 PR 状态。