diff --git a/.agents/skills/gframework-batch-boot/SKILL.md b/.agents/skills/gframework-batch-boot/SKILL.md index 857d8964..e10173f7 100644 --- a/.agents/skills/gframework-batch-boot/SKILL.md +++ b/.agents/skills/gframework-batch-boot/SKILL.md @@ -147,7 +147,7 @@ When shorthand is used: 6. After each completed batch: - integrate or verify the result - rerun the required validation - - recompute the primary stop-condition metric + - recompute the primary stop-condition metric - reassess whether one more batch would likely push the agent near or beyond roughly 80% context usage - decide immediately whether to continue or stop 7. Do not require the user to manually trigger every round unless: diff --git a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs index a872b5a8..fb93cc32 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs @@ -6,6 +6,7 @@ using System.Linq; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Ioc; using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Internal; using MediatR; using Microsoft.Extensions.DependencyInjection; using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime; @@ -58,11 +59,11 @@ internal static class BenchmarkHostFactory var notificationPublisher = container.Get(); var runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger, notificationPublisher); container.Register(runtime); - container.Register((LegacyICqrsRuntime)runtime); + RegisterLegacyRuntimeAlias(container, runtime); } else if (container.Get() is null) { - container.Register((LegacyICqrsRuntime)container.GetRequired()); + RegisterLegacyRuntimeAlias(container, container.GetRequired()); } if (container.Get() is null) @@ -81,6 +82,43 @@ internal static class BenchmarkHostFactory } } + /// + /// 只激活当前 benchmark 场景明确拥有的 generated registry,避免同一程序集里的其他 benchmark registry + /// 扩大冻结后服务索引与 dispatcher descriptor 基线。 + /// + /// 当前 benchmark 需要接入的 generated registry 类型。 + /// 承载 generated registry 注册结果的 GFramework benchmark 容器。 + internal static void RegisterGeneratedBenchmarkRegistry(MicrosoftDiContainer container) + where TRegistry : class, GFramework.Cqrs.ICqrsHandlerRegistry + { + ArgumentNullException.ThrowIfNull(container); + + var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar"); + CqrsHandlerRegistrar.RegisterGeneratedRegistry(container, typeof(TRegistry), registrarLogger); + } + + /// + /// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。 + /// + /// 承载 runtime 别名的 benchmark 容器。 + /// 当前正式 CQRS runtime 实例。 + /// + /// 未同时实现 legacy CQRS runtime 契约。 + /// + private static void RegisterLegacyRuntimeAlias(MicrosoftDiContainer container, ICqrsRuntime runtime) + { + ArgumentNullException.ThrowIfNull(container); + ArgumentNullException.ThrowIfNull(runtime); + + if (runtime is not LegacyICqrsRuntime legacyRuntime) + { + throw new InvalidOperationException( + $"The registered {typeof(ICqrsRuntime).FullName} must also implement {typeof(LegacyICqrsRuntime).FullName}. Actual runtime type: {runtime.GetType().FullName}."); + } + + container.Register(legacyRuntime); + } + /// /// 创建只承载当前 benchmark handler 集合的最小 MediatR 宿主。 /// diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs index a84c6d1a..9d946e72 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs @@ -66,7 +66,7 @@ public class RequestBenchmarks _baselineHandler = new BenchmarkRequestHandler(); _container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => { - container.RegisterCqrsHandlersFromAssembly(typeof(RequestBenchmarks).Assembly); + BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container); }); _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime( _container, diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs index 87e326e5..4b8589ed 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs @@ -83,7 +83,7 @@ public class RequestInvokerBenchmarks _generatedContainer = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => { - container.RegisterCqrsHandlersFromAssembly(typeof(RequestInvokerBenchmarks).Assembly); + BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container); }); _generatedRuntime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime( _generatedContainer, diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs index 23d703ec..a2883955 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs @@ -69,7 +69,7 @@ public class RequestPipelineBenchmarks _baselineHandler = new BenchmarkRequestHandler(); _container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => { - container.RegisterCqrsHandlersFromAssembly(typeof(RequestPipelineBenchmarks).Assembly); + BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container); RegisterGFrameworkPipelineBehaviors(container, PipelineCount); }); _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime( diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs index a552a233..deacac21 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs @@ -83,7 +83,7 @@ public class StreamInvokerBenchmarks _generatedContainer = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => { - container.RegisterCqrsHandlersFromAssembly(typeof(StreamInvokerBenchmarks).Assembly); + BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container); }); _generatedRuntime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime( _generatedContainer, diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs index c121dfd8..7d761d03 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs @@ -93,9 +93,11 @@ public class StreamLifetimeBenchmarks _container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => { - container.RegisterCqrsHandlersFromAssembly(typeof(StreamLifetimeBenchmarks).Assembly); + BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container); RegisterGFrameworkHandler(container, Lifetime); }); + // 容器内已提前保留默认 runtime 以支撑 generated registry 接线; + // 这里额外创建带生命周期后缀的 runtime,只是为了区分不同 benchmark 矩阵的 dispatcher 日志。 _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime( _container, LoggerFactoryResolver.Provider.CreateLogger(nameof(StreamLifetimeBenchmarks) + "." + Lifetime)); diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs index cd6e4a83..8b886d29 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs @@ -67,7 +67,7 @@ public class StreamingBenchmarks _baselineHandler = new BenchmarkStreamHandler(); _container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => { - container.RegisterCqrsHandlersFromAssembly(typeof(StreamingBenchmarks).Assembly); + BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container); }); _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime( _container, diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs index 5eec00eb..9e1d0678 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs @@ -43,6 +43,57 @@ internal sealed class CqrsDispatcherContextValidationTests Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext")); } + /// + /// 验证 request 上下文校验失败时, + /// 不会在调用点同步抛出,而是返回一个 faulted 保持既有异步失败语义。 + /// + [Test] + public void SendAsync_Should_Return_Faulted_ValueTask_When_Context_Preparation_Fails() + { + var runtime = CreateRuntime( + container => + { + container + .Setup(currentContainer => currentContainer.Get(typeof(IRequestHandler))) + .Returns(new ContextAwareRequestHandler()); + container + .Setup(currentContainer => currentContainer.HasRegistration(typeof(IPipelineBehavior))) + .Returns(false); + }); + + ValueTask dispatch = default; + Assert.That( + () => { dispatch = runtime.SendAsync(new FakeCqrsContext(), new ContextAwareRequest()); }, + Throws.Nothing); + Assert.That( + async () => await dispatch.ConfigureAwait(false), + Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext")); + } + + /// + /// 验证 request handler 缺失时,dispatcher 仍返回 faulted , + /// 而不是在调用点同步抛出异常。 + /// + [Test] + public void SendAsync_Should_Return_Faulted_ValueTask_When_Handler_Is_Missing() + { + var runtime = CreateRuntime( + container => + { + container + .Setup(currentContainer => currentContainer.Get(typeof(IRequestHandler))) + .Returns((object?)null); + }); + + ValueTask dispatch = default; + Assert.That( + () => { dispatch = runtime.SendAsync(new FakeCqrsContext(), new ContextAwareRequest()); }, + Throws.Nothing); + Assert.That( + async () => await dispatch.ConfigureAwait(false), + Throws.InvalidOperationException.With.Message.Contains("No CQRS request handler registered")); + } + /// /// 验证当 notification handler 需要上下文注入、但当前 CQRS 上下文不实现 时, /// dispatcher 会在发布前显式失败。 diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs index 07204bbf..15273d96 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs @@ -7,6 +7,7 @@ using GFramework.Core.Architectures; using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Internal; namespace GFramework.Cqrs.Tests.Cqrs; @@ -99,6 +100,32 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests Is.EqualTo([typeof(GeneratedStreamInvokerProviderRegistry)])); } + /// + /// 验证 direct generated-registry 激活入口只会接入指定 registry,而不会顺手把同一测试程序集里的其他 registry 一并注册。 + /// + [Test] + public void RegisterGeneratedRegistry_Should_Register_Only_The_Selected_Provider() + { + var container = new MicrosoftDiContainer(); + var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsGeneratedRequestInvokerProviderTests)); + + CqrsHandlerRegistrar.RegisterGeneratedRegistry( + container, + typeof(GeneratedRequestInvokerProviderRegistry), + logger); + + var requestProviders = container.GetAll(); + var streamProviders = container.GetAll(); + + Assert.Multiple(() => + { + Assert.That( + requestProviders.Select(static provider => provider.GetType()), + Is.EqualTo([typeof(GeneratedRequestInvokerProviderRegistry)])); + Assert.That(streamProviders, Is.Empty); + }); + } + /// /// 验证当实现类型隐藏、但 stream handler interface 仍可直接表达时, /// registrar 仍会把 generated stream invoker provider 注册到容器中。 diff --git a/GFramework.Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Cqrs/Internal/CqrsDispatcher.cs index 86caab3d..aa1e6d24 100644 --- a/GFramework.Cqrs/Internal/CqrsDispatcher.cs +++ b/GFramework.Cqrs/Internal/CqrsDispatcher.cs @@ -110,30 +110,38 @@ internal sealed class CqrsDispatcher( IRequest request, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(context); - ArgumentNullException.ThrowIfNull(request); - - var requestType = request.GetType(); - var dispatchBinding = GetRequestDispatchBinding(requestType); - var handler = container.Get(dispatchBinding.HandlerType) - ?? throw new InvalidOperationException( - $"No CQRS request handler registered for {requestType.FullName}."); - - PrepareHandler(handler, context); - if (!container.HasRegistration(dispatchBinding.BehaviorType)) + try { - return dispatchBinding.RequestInvoker(handler, request, cancellationToken); + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(request); + + var requestType = request.GetType(); + var dispatchBinding = GetRequestDispatchBinding(requestType); + var handler = container.Get(dispatchBinding.HandlerType) + ?? throw new InvalidOperationException( + $"No CQRS request handler registered for {requestType.FullName}."); + + PrepareHandler(handler, context); + if (!container.HasRegistration(dispatchBinding.BehaviorType)) + { + return dispatchBinding.RequestInvoker(handler, request, cancellationToken); + } + + var behaviors = container.GetAll(dispatchBinding.BehaviorType); + + foreach (var behavior in behaviors) + { + PrepareHandler(behavior, context); + } + + return dispatchBinding.GetPipelineExecutor(behaviors.Count) + .Invoke(handler, behaviors, request, cancellationToken); } - - var behaviors = container.GetAll(dispatchBinding.BehaviorType); - - foreach (var behavior in behaviors) + catch (Exception exception) { - PrepareHandler(behavior, context); + // 保留旧 async 实现的 faulted-ValueTask 失败语义,同时继续复用 direct-return 的热路径。 + return ValueTask.FromException(exception); } - - return dispatchBinding.GetPipelineExecutor(behaviors.Count) - .Invoke(handler, behaviors, request, cancellationToken); } /// diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs index 3607771c..a332927c 100644 --- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -68,6 +68,36 @@ internal static class CqrsHandlerRegistrar } } + /// + /// 直接激活并注册单个 generated registry,避免调用方为了只接入一个 benchmark registry + /// 而额外扫描同一程序集里的其他 registry / handler。 + /// + /// 承载 generated registry 注册结果的目标容器。 + /// 要直接激活的 generated registry 类型。 + /// 当前注册过程使用的日志记录器。 + /// + /// 。 + /// + /// 指定 registry 类型不满足 generated registry 运行时契约。 + internal static void RegisterGeneratedRegistry( + IIocContainer container, + Type registryType, + ILogger logger) + { + ArgumentNullException.ThrowIfNull(container); + ArgumentNullException.ThrowIfNull(registryType); + ArgumentNullException.ThrowIfNull(logger); + + var assemblyName = GetAssemblySortKey(registryType.Assembly); + if (!TryCreateGeneratedRegistry(registryType, assemblyName, logger, out var registry)) + { + throw new InvalidOperationException( + $"Unable to activate generated CQRS handler registry {registryType.FullName} in assembly {assemblyName}."); + } + + RegisterGeneratedRegistries(container.GetServicesUnsafe, [registry], assemblyName, logger); + } + /// /// 优先使用程序集级源码生成注册器完成 CQRS 映射注册。 /// diff --git a/GFramework.Cqrs/Properties/AssemblyInfo.cs b/GFramework.Cqrs/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..0572e1c6 --- /dev/null +++ b/GFramework.Cqrs/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) 2025-2026 GeWuYou +// SPDX-License-Identifier: Apache-2.0 + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("GFramework.Cqrs.Tests")] +[assembly: InternalsVisibleTo("GFramework.Cqrs.Benchmarks")] 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 d5bdfec3..5f2766c4 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,10 +7,11 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-108` +- 恢复点编号:`CQRS-REWRITE-RP-109` - 当前阶段:`Phase 8` -- 当前 PR 锚点:`PR #340` +- 当前 PR 锚点:`PR #341` - 当前结论: + - 当前 `RP-109` 已使用 `$gframework-pr-review` 复核 `PR #341` latest-head review:benchmark 宿主改为定向激活当前场景的 generated registry,避免同一 benchmark 程序集里的其他 registry 扩大冻结服务索引与 `HasRegistration` 基线;`BenchmarkHostFactory` 为 legacy runtime alias 注册补齐防守式类型检查与 stream lifetime 运行时注释;`CqrsDispatcher.SendAsync(...)` 在保留 direct-return 热路径的同时恢复 faulted `ValueTask` 失败语义,并补齐 generated registry 定向接线与 request fault 语义回归测试;`.agents/skills/gframework-batch-boot/SKILL.md` 的 MD005 缩进也已顺手修正 - `GFramework.Cqrs` 已完成对外部 `Mediator` 的生产级替代,当前主线已从“是否可替代”转向“仓库内部收口与能力深化顺序” - `dispatch/invoker` 生成前移已扩展到 request / stream 路径,`RP-077` 已补齐 request invoker provider gate 与 stream gate 对称的 descriptor / descriptor entry runtime 合同回归 - `RP-078` 已补齐 mixed fallback metadata 在 runtime 不允许多个 fallback attribute 实例时的单字符串 attribute 回退回归 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 103b92b0..a0ef3c32 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-05-08 +### 阶段:PR #341 latest-head review 收口(CQRS-REWRITE-RP-109) + +- 使用 `$gframework-pr-review` 抓取 `feat/cqrs-optimization` 当前公开 PR,并确认当前锚点已从 `PR #340` 更新为 `PR #341` +- 本轮 latest-head review 结论: + - `CodeRabbit` 仍有 `BenchmarkHostFactory.cs` 的 legacy runtime 硬转型、`StreamLifetimeBenchmarks.cs` 的注释缺口,以及 `.agents/skills/gframework-batch-boot/SKILL.md` 的 `MD005` 缩进问题 + - `Greptile` 指出的两条仍然成立:benchmark 项目里通过 `RegisterCqrsHandlersFromAssembly(typeof(...).Assembly)` 会把同程序集的其他 generated registry 一并激活,扩大 benchmark 宿主的服务索引基线;`CqrsDispatcher.SendAsync(...)` 直接去掉 `async/await` 后也把原本的 faulted-`ValueTask` 失败语义改成了同步抛出 +- 本轮主线程决策: + - 在 `GFramework.Cqrs.Internal.CqrsHandlerRegistrar` 新增 direct generated-registry 激活入口,并通过 `InternalsVisibleTo` 暴露给 `GFramework.Cqrs.Benchmarks`,让 benchmark 宿主只激活当前场景的 generated registry + - 把 `RequestBenchmarks`、`RequestPipelineBenchmarks`、`StreamingBenchmarks`、`StreamLifetimeBenchmarks` 以及 request/stream invoker benchmark 的 generated 宿主全部切到定向 registry 接线,避免同程序集其他 registry 扩大冻结索引和 descriptor 预热基线 + - 在 `BenchmarkHostFactory` 里用防守式类型检查注册 legacy runtime alias,并补充 stream lifetime runtime 二次创建的注释 + - 让 `CqrsDispatcher.SendAsync(...)` 通过 `ValueTask.FromException(...)` 恢复旧的 faulted-`ValueTask` 失败语义,同时保留成功路径的 direct-return 热路径 + - 补齐 `CqrsGeneratedRequestInvokerProviderTests` 与 `CqrsDispatcherContextValidationTests` 的 targeted 回归,并顺手修正 batch boot skill 的 markdown 缩进 +- 本轮权威验证: + - `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release` + - 结果:通过,`1 warning / 0 error` + - 备注:仅出现 `MSB3026` 单次复制重试告警,随后成功产出 `net10.0` 目标;未出现编译失败或新增代码警告 + - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` + - 结果:通过,`0 warning / 0 error` + - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsDispatcherContextValidationTests|FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests"` + - 结果:通过,`24/24` passed + - 备注:首轮并行验证时因与 build 同时运行触发 MSBuild 输出文件锁竞争;改为串行重跑同一命令后稳定通过 + - `python3 scripts/license-header.py --check --paths GFramework.Cqrs/Properties/AssemblyInfo.cs GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs GFramework.Cqrs/Internal/CqrsDispatcher.cs GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs` + - 结果:通过 + - 备注:仓库脚本默认内部调用未绑定 worktree 的 `git ls-files`,因此本轮按修改文件列表显式 `--paths` 校验 + - `git diff --check` + - 结果:通过 +- 本轮下一步: + - 运行 `GFramework.Cqrs` / `GFramework.Cqrs.Benchmarks` 的 Release build、相关 targeted tests、license header check 与 `git diff --check` + ### 阶段:stream handler 生命周期矩阵 benchmark(CQRS-REWRITE-RP-108) - 延续 `$gframework-batch-boot 50`,本轮继续使用 `origin/main` 作为 branch diff 基线,并先复核: