diff --git a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
index 9652f63b..5895e42f 100644
--- a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
+++ b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
@@ -306,8 +306,6 @@ public class ArchitectureContextTests
public async Task SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
{
const int workerCount = 8;
- var workerStartupTimeout = TimeSpan.FromSeconds(5);
- var firstResolutionTimeout = TimeSpan.FromSeconds(5);
using var startGate = new ManualResetEventSlim(false);
using var allowResolutionToComplete = new ManualResetEventSlim(false);
using var workersReady = new CountdownEvent(workerCount);
@@ -339,18 +337,11 @@ public class ArchitectureContextTests
}))
.ToArray();
- Assert.That(
- workersReady.Wait(workerStartupTimeout),
- Is.True,
- "Expected all workers to be ready before releasing start gate.");
- startGate.Set();
-
- Assert.That(
- SpinWait.SpinUntil(() => Volatile.Read(ref resolutionCallCount) > 0, firstResolutionTimeout),
- Is.True,
- "Expected at least one CQRS runtime resolution attempt.");
-
- allowResolutionToComplete.Set();
+ ReleaseWorkersAfterFirstResolutionAttempt(
+ workersReady,
+ startGate,
+ allowResolutionToComplete,
+ () => Volatile.Read(ref resolutionCallCount) > 0);
var responses = await Task.WhenAll(requests);
@@ -372,8 +363,6 @@ public class ArchitectureContextTests
public async Task PublishAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
{
const int workerCount = 8;
- var workerStartupTimeout = TimeSpan.FromSeconds(5);
- var firstResolutionTimeout = TimeSpan.FromSeconds(5);
using var startGate = new ManualResetEventSlim(false);
using var allowResolutionToComplete = new ManualResetEventSlim(false);
using var workersReady = new CountdownEvent(workerCount);
@@ -405,18 +394,11 @@ public class ArchitectureContextTests
}))
.ToArray();
- Assert.That(
- workersReady.Wait(workerStartupTimeout),
- Is.True,
- "Expected all workers to be ready before releasing start gate.");
- startGate.Set();
-
- Assert.That(
- SpinWait.SpinUntil(() => Volatile.Read(ref resolutionCallCount) > 0, firstResolutionTimeout),
- Is.True,
- "Expected at least one CQRS runtime resolution attempt.");
-
- allowResolutionToComplete.Set();
+ ReleaseWorkersAfterFirstResolutionAttempt(
+ workersReady,
+ startGate,
+ allowResolutionToComplete,
+ () => Volatile.Read(ref resolutionCallCount) > 0);
await Task.WhenAll(notifications).ConfigureAwait(false);
@@ -437,8 +419,6 @@ public class ArchitectureContextTests
public async Task CreateStream_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
{
const int workerCount = 8;
- var workerStartupTimeout = TimeSpan.FromSeconds(5);
- var firstResolutionTimeout = TimeSpan.FromSeconds(5);
using var startGate = new ManualResetEventSlim(false);
using var allowResolutionToComplete = new ManualResetEventSlim(false);
using var workersReady = new CountdownEvent(workerCount);
@@ -470,18 +450,11 @@ public class ArchitectureContextTests
}))
.ToArray();
- Assert.That(
- workersReady.Wait(workerStartupTimeout),
- Is.True,
- "Expected all workers to be ready before releasing start gate.");
- startGate.Set();
-
- Assert.That(
- SpinWait.SpinUntil(() => Volatile.Read(ref resolutionCallCount) > 0, firstResolutionTimeout),
- Is.True,
- "Expected at least one CQRS runtime resolution attempt.");
-
- allowResolutionToComplete.Set();
+ ReleaseWorkersAfterFirstResolutionAttempt(
+ workersReady,
+ startGate,
+ allowResolutionToComplete,
+ () => Volatile.Read(ref resolutionCallCount) > 0);
await Task.WhenAll(streamTasks).ConfigureAwait(false);
@@ -509,6 +482,46 @@ public class ArchitectureContextTests
}
}
+ ///
+ /// 释放并发 worker,并确保在断言失败时也能放行首次 runtime 解析。
+ ///
+ /// 用于确认 worker 已就绪的倒计时器。
+ /// 用于同时放行 worker 的门闩。
+ /// 用于解除首次 runtime 解析阻塞的门闩。
+ /// 用于判断当前是否已观察到首次 runtime 解析尝试。
+ private static void ReleaseWorkersAfterFirstResolutionAttempt(
+ CountdownEvent workersReady,
+ ManualResetEventSlim startGate,
+ ManualResetEventSlim allowResolutionToComplete,
+ Func hasObservedResolutionAttempt)
+ {
+ ArgumentNullException.ThrowIfNull(workersReady);
+ ArgumentNullException.ThrowIfNull(startGate);
+ ArgumentNullException.ThrowIfNull(allowResolutionToComplete);
+ ArgumentNullException.ThrowIfNull(hasObservedResolutionAttempt);
+
+ var workerStartupTimeout = TimeSpan.FromSeconds(5);
+ var firstResolutionTimeout = TimeSpan.FromSeconds(5);
+
+ Assert.That(
+ workersReady.Wait(workerStartupTimeout),
+ Is.True,
+ "Expected all workers to be ready before releasing start gate.");
+ startGate.Set();
+
+ try
+ {
+ Assert.That(
+ SpinWait.SpinUntil(hasObservedResolutionAttempt, firstResolutionTimeout),
+ Is.True,
+ "Expected at least one CQRS runtime resolution attempt.");
+ }
+ finally
+ {
+ allowResolutionToComplete.Set();
+ }
+ }
+
///
/// 为 `CreateStream` 并发解析测试提供最小异步流。
///
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
index a408d508..858c00d9 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
@@ -67,6 +67,23 @@ public sealed partial class CqrsHandlerRegistryGenerator
requestInvokerEmissions);
}
+ ///
+ /// 从 direct handler 注册描述中提取 request invoker 发射计划。
+ ///
+ ///
+ /// 指示当前 runtime 是否同时暴露 ICqrsRequestInvokerProvider 与
+ /// IEnumeratesCqrsRequestInvokerDescriptors 契约;若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
+ ///
+ /// 已按稳定顺序整理完成的 handler 注册描述。
+ ///
+ /// 由 directRegistration.RequestInvokerRegistration 派生出的 集合。
+ /// methodIndex 按 与其 direct registration 的遍历顺序单调递增,
+ /// 因而只要上游排序稳定,生成的 invoker 方法名与描述符顺序就跨运行保持稳定。
+ ///
+ ///
+ /// 缺少 RequestInvokerRegistration 的 direct registration 会被显式跳过,而不会生成半成品 provider 成员;
+ /// 调用方应把“为什么没有 request invoker registration”对应的诊断留在更早的建模阶段,而不是在源码发射阶段兜底。
+ ///
private static ImmutableArray CreateRequestInvokerEmissions(
bool supportsRequestInvokerProvider,
IReadOnlyList registrations)
@@ -273,6 +290,17 @@ public sealed partial class CqrsHandlerRegistryGenerator
builder.AppendLine(" }");
}
+ ///
+ /// 发射 generated registry 的 request invoker provider 成员。
+ ///
+ /// 生成源码构造器。
+ ///
+ /// 来自 的稳定发射计划。
+ ///
+ ///
+ /// 该输出包含三部分:描述符数组、provider 查询方法,以及与描述符逐项对应的静态 invoker 方法。
+ /// 若发射计划为空,调用方应直接跳过整个 provider 分支,而不是输出空的 registry seam。
+ ///
private static void AppendRequestInvokerProviderMembers(
StringBuilder builder,
ImmutableArray requestInvokerEmissions)
@@ -288,6 +316,15 @@ public sealed partial class CqrsHandlerRegistryGenerator
}
}
+ ///
+ /// 发射 generated registry 的 request invoker 描述符数组。
+ ///
+ /// 生成源码构造器。
+ /// 当前要输出的 request invoker 发射计划。
+ ///
+ /// 每个条目都会把请求类型、响应类型和对应的静态 invoker 方法打包成
+ /// CqrsRequestInvokerDescriptorEntry,供 registrar 在注册阶段写入 dispatcher 的弱缓存。
+ ///
private static void AppendRequestInvokerDescriptorArray(
StringBuilder builder,
ImmutableArray requestInvokerEmissions)
@@ -319,6 +356,14 @@ public sealed partial class CqrsHandlerRegistryGenerator
builder.AppendLine(" ];");
}
+ ///
+ /// 发射 generated registry 对 request invoker provider 契约的实现方法。
+ ///
+ /// 生成源码构造器。
+ ///
+ /// 默认 runtime 真正消费的是 GetDescriptors() 暴露的完整描述符集合,并在注册阶段一次性写入缓存;
+ /// TryGetDescriptor(...) 保留为显式查询接口,因此这里使用线性扫描即可保持生成代码简单且无额外字典分配。
+ ///
private static void AppendRequestInvokerProviderMethods(StringBuilder builder)
{
builder.Append(" public global::System.Collections.Generic.IReadOnlyList
+ /// 为单个 request invoker 描述符发射对应的静态强类型桥接方法。
+ ///
+ /// 生成源码构造器。
+ /// 当前要输出的 invoker 发射计划。
+ ///
+ /// 这些方法的编号与 一一对应,
+ /// dispatcher 通过描述符里的 把 object 形参桥接回强类型 handler 与 request。
+ ///
private static void AppendRequestInvokerMethod(StringBuilder builder, RequestInvokerEmissionSpec emission)
{
builder.Append(" private static global::System.Threading.Tasks.ValueTask<");
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextAdvancedFeaturesTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextAdvancedFeaturesTests.cs
index 05674a7b..63fb487b 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextAdvancedFeaturesTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextAdvancedFeaturesTests.cs
@@ -62,7 +62,7 @@ internal sealed class CqrsArchitectureContextAdvancedFeaturesTests
}
[Test]
- public async Task Request_With_Retry_Behavior_Should_Retry_On_Failure()
+ public async Task Request_With_Retry_Behavior_Should_Succeed_On_First_Attempt()
{
// 由于我们没有实现实际的重试行为,简化测试逻辑
TestRetryBehavior.AttemptCount = 0;
@@ -132,7 +132,7 @@ internal sealed class CqrsArchitectureContextAdvancedFeaturesTests
}
[Test]
- public async Task Transient_Error_Should_Be_Handled_By_Retry_Mechanism()
+ public async Task Transient_Error_Request_Should_Succeed_Without_Simulated_Errors()
{
// 由于我们没有实现实际的瞬态错误处理,简化测试逻辑
TestTransientErrorHandler.ErrorCount = 0;
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextIntegrationTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextIntegrationTests.cs
index c1fa7b0a..d9ff2602 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextIntegrationTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsArchitectureContextIntegrationTests.cs
@@ -67,8 +67,7 @@ public class CqrsArchitectureContextIntegrationTests
[Test]
public async Task Handler_Can_Access_Architecture_Context()
{
- // 当前测试通过直接注入上下文来聚焦验证架构上下文集成结果。
- TestContextAwareHandler.LastContext = _context;
+ TestContextAwareHandler.LastContext = null;
var request = new TestContextAwareRequest();
await _context!.SendRequestAsync(request).ConfigureAwait(false);
@@ -359,17 +358,17 @@ public class CqrsArchitectureContextIntegrationTests
///
/// 为上下文感知请求提供静态响应的测试处理器。
///
- public sealed class TestContextAwareRequestHandler : IRequestHandler
+ public sealed class TestContextAwareRequestHandler : ContextAwareBase, IRequestHandler
{
///
- /// 处理请求并返回固定结果。
+ /// 记录当前处理器观察到的架构上下文,并返回固定结果。
///
/// 当前测试请求。
/// 取消令牌。
/// 固定的测试结果。
public ValueTask Handle(TestContextAwareRequest request, CancellationToken cancellationToken)
{
- // 保持测试中设置的上下文,不要重置为空。
+ TestContextAwareHandler.LastContext = Context;
return new ValueTask("Context accessed");
}
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
index 0919bc79..e5e5171f 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
@@ -14,12 +14,15 @@ namespace GFramework.Cqrs.Tests.Cqrs;
[NonParallelizable]
internal sealed class CqrsGeneratedRequestInvokerProviderTests
{
+ private ILoggerFactoryProvider? _previousLoggerFactoryProvider;
+
///
/// 在每个用例前重置 registrar / dispatcher 的静态缓存,避免跨用例共享状态影响断言。
///
[SetUp]
public void SetUp()
{
+ _previousLoggerFactoryProvider = LoggerFactoryResolver.Provider;
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
ClearRegistrarCaches();
ClearDispatcherCaches();
@@ -31,6 +34,7 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
[TearDown]
public void TearDown()
{
+ LoggerFactoryResolver.Provider = _previousLoggerFactoryProvider ?? new ConsoleLoggerFactoryProvider();
ClearRegistrarCaches();
ClearDispatcherCaches();
}
@@ -67,18 +71,7 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
var context = new ArchitectureContext(container);
var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload"));
-
- var requestBindings = GetDispatcherCacheField("RequestDispatchBindings");
- var binding = GetRequestDispatchBindingValue(
- requestBindings,
- typeof(GeneratedRequestInvokerRequest),
- typeof(string));
-
- Assert.Multiple(() =>
- {
- Assert.That(response, Is.EqualTo("generated:payload"));
- Assert.That(binding, Is.Not.Null);
- });
+ Assert.That(response, Is.EqualTo("generated:payload"));
}
///
@@ -156,22 +149,4 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
.Invoke(cache, Array.Empty
/// 请求运行时类型。
/// 响应运行时类型。
/// 命中时返回的 request invoker 元数据。
/// 若当前 provider 可处理该请求/响应类型对则返回 ;否则返回 。
+ ///
+ /// 若 provider 希望被默认 runtime 自动接线到 dispatcher 的 generated invoker 缓存中,
+ /// 还必须同时实现 ,以便 registrar 在注册阶段枚举全部描述符。
+ ///
bool TryGetDescriptor(
Type requestType,
Type responseType,
diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
index 000e9ed2..f6e88c1d 100644
--- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
+++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
@@ -263,8 +263,8 @@ internal static class CqrsHandlerRegistrar
if (registry is not ICqrsRequestInvokerProvider provider)
return;
- services.AddSingleton(typeof(ICqrsRequestInvokerProvider), provider);
RegisterGeneratedRequestInvokerDescriptors(provider, assemblyName, logger);
+ services.AddSingleton(typeof(ICqrsRequestInvokerProvider), provider);
logger.Debug(
$"Registered CQRS request invoker provider {provider.GetType().FullName} for assembly {assemblyName}.");
}
diff --git a/GFramework.Cqrs/Notification/INotificationPublisher.cs b/GFramework.Cqrs/Notification/INotificationPublisher.cs
index 85f4238b..67000367 100644
--- a/GFramework.Cqrs/Notification/INotificationPublisher.cs
+++ b/GFramework.Cqrs/Notification/INotificationPublisher.cs
@@ -16,9 +16,10 @@ public interface INotificationPublisher
/// 执行一次通知发布。
///
/// 通知类型。
- /// 当前发布调用的处理器集合与执行入口。
+ /// 当前发布调用的处理器集合与执行入口,不能为空。
/// 取消令牌。
/// 表示通知发布完成的值任务。
+ /// 为 。
ValueTask PublishAsync(
NotificationPublishContext context,
CancellationToken cancellationToken = default)
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index 1dc35bad..9961a139 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -2251,8 +2251,6 @@ public class CqrsHandlerRegistryGeneratorTests
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
- var generatedSource = execution.GeneratedSources[0].content;
-
Assert.Multiple(() =>
{
Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306"));
@@ -2260,6 +2258,7 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(generatorErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
+ var generatedSource = execution.GeneratedSources[0].content;
Assert.That(
generatedSource,
Does.Contain(
@@ -2302,8 +2301,6 @@ public class CqrsHandlerRegistryGeneratorTests
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
- var generatedSource = execution.GeneratedSources[0].content;
-
Assert.Multiple(() =>
{
Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306"));
@@ -2311,6 +2308,7 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(generatorErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
+ var generatedSource = execution.GeneratedSources[0].content;
Assert.That(
generatedSource,
Does.Contain(
@@ -2358,8 +2356,6 @@ public class CqrsHandlerRegistryGeneratorTests
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
- var generatedSource = execution.GeneratedSources[0].content;
-
Assert.Multiple(() =>
{
Assert.That(inputCompilationErrors, Is.Empty);
@@ -2367,6 +2363,7 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(generatorErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
+ var generatedSource = execution.GeneratedSources[0].content;
Assert.That(
generatedSource,
Does.Contain(
diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs
index 8122f2ca..adcb83ea 100644
--- a/GFramework.Tests.Common/CqrsTestRuntime.cs
+++ b/GFramework.Tests.Common/CqrsTestRuntime.cs
@@ -98,7 +98,13 @@ public static class CqrsTestRuntime
///
private static void RegisterLegacyRuntimeAlias(MicrosoftDiContainer container, ICqrsRuntime runtime)
{
- container.Register((LegacyICqrsRuntime)runtime);
+ if (runtime is not LegacyICqrsRuntime legacyRuntime)
+ {
+ throw new InvalidOperationException(
+ $"The registered {nameof(ICqrsRuntime)} must also implement {typeof(LegacyICqrsRuntime).FullName}.");
+ }
+
+ container.Register(legacyRuntime);
}
///
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 20945043..64a394db 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
@@ -123,10 +123,11 @@ CQRS 迁移与收敛。
- 本地核对后确认 `dotnet-format` 仍只有 `Restore operation failed` 噪音,没有附带当前仍成立的文件级格式诊断
- 已按 review triage 修正 generator source preamble 的多实例 fallback 特性排版、移除死参数,并补强 mixed/direct fallback 发射回归断言与 XML 文档
- `2026-04-30` 已重新执行 `$gframework-pr-review`:
- - 当前分支对应 `PR #304`,状态为 `OPEN`
- - latest reviewed commit 当前剩余 `7` 条 CodeRabbit nitpick 与 `2` 条 Greptile open threads,集中在测试脆弱断言、共享测试状态并发保护,以及 `CqrsDispatcher` 的缓存线程模型文档
- - 本地核对后,已确认这些评论仍对应当前代码;MegaLinter 继续只暴露 `dotnet-format` 的 `Restore operation failed` 环境噪音,CTRF 汇总为 `2203/2203` passed
- - 已在本地完成 follow-up:request pipeline invoker 改为 binding 级复用、共享测试状态切换到 `System.Threading.Lock` 保护、顺序测试改为受控记录接口、`CqrsDispatcherCacheTests` 标记为 `NonParallelizable`,并补齐相关 XML / 线程模型注释
+ - 当前分支对应 `PR #305`,状态为 `OPEN`
+ - 当前抓取到 `9` 条 CodeRabbit open threads、`2` 条 Greptile open threads;远端 CTRF 汇总为 `2214/2214` passed,MegaLinter 仍只暴露 `dotnet-format` 的 `Restore operation failed` 环境噪音
+ - 本地核对后,已确认以下评论仍然成立并已完成修正:`ArchitectureContextTests` 并发测试失败路径释放、`CqrsGeneratedRequestInvokerProviderTests` 的全局 logger provider 恢复与私有缓存断言解耦、`CqrsArchitectureContextIntegrationTests` 的真实上下文注入断言、`GeneratedRequestInvokerRequest` / `INotificationPublisher` XML 文档、`CqrsHandlerRegistrar` 的 provider 注册顺序、`CqrsTestRuntime` 的 legacy alias 显式失败模式,以及 `cqrs-rewrite` trace 重复标题
+ - 对于 `ICqrsRequestInvokerProvider` / generated `TryGetDescriptor(...)` 相关 Greptile 评论,本地评估后未改 dispatcher 热路径语义;改为补齐公开注释与生成器方法级注释,明确默认 runtime 只在注册阶段经 `IEnumeratesCqrsRequestInvokerDescriptors` 预热缓存,`TryGetDescriptor(...)` 保留为显式查询 seam
+ - 本轮额外修正了 `GFramework.SourceGenerators.Tests` 中先读取 `GeneratedSources[0]` 再断言长度的脆弱顺序,并将 `ArchitectureContextTests` 的并发 orchestration 收敛到公共 helper,消除本轮引入的 `MA0051` warning
- `2026-04-29` 已完成一轮 precise runtime type lookup 的数组回归补强:
- `GFramework.SourceGenerators.Tests` 已新增多维数组、交错数组、外部程序集隐藏元素类型三类回归
- 当前生成器在 precise runtime type lookup 下已稳定保留数组秩信息,并递归发射交错数组的 `MakeArrayType()` 链
@@ -212,10 +213,22 @@ CQRS 迁移与收敛。
- `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`
- 结果:通过
- - 备注:确认当前分支对应 `PR #304`,并定位到仍需本地复核的 CodeRabbit / Greptile open thread
+ - 备注:确认当前分支对应 `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` 改名未引入命名规则回归
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 e3db6837..68ce00ac 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
@@ -21,7 +21,7 @@
- `CqrsGeneratedRequestInvokerProviderTests` 锁定 registrar 会注册 generated request invoker provider,且 dispatcher 走 generated invoker 后会返回 `generated:` 前缀结果
- `CqrsHandlerRegistryGeneratorTests` 锁定 generated source 会包含 request invoker provider 接口、descriptor 条目与 `InvokeRequestHandler0(...)` 方法
-### 验证
+### 验证(RP-067)
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsGeneratedRequestInvokerProviderTests|FullyQualifiedName~CqrsHandlerRegistrarTests|FullyQualifiedName~CqrsDispatcherCacheTests"`
- 结果:通过,`22/22` passed
@@ -30,7 +30,7 @@
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
-### 当前下一步
+### 当前下一步(RP-067)
1. 评估 notification / stream invoker 是否值得沿同一 provider 模式继续前移,或先补 request provider 的公开说明与诊断语义
2. 继续在保持 branch diff 低于阈值的前提下推进下一批;当前相对 `origin/main` 的 branch diff 为 `22 files`
@@ -53,14 +53,14 @@
- `docs/zh-CN/core/cqrs.md`
- 三处文档都已明确:`GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 只是旧命名空间下保留的 compatibility alias,新代码应依赖 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime`
-### 验证
+### 验证(RP-066)
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
- 结果:通过,`42/42` passed
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
-### 当前下一步
+### 当前下一步(RP-066)
1. 在保持 branch diff 低于阈值的前提下,回到 `dispatch/invoker` 生成前移主线
2. 优先尝试只覆盖 request 路径的 generated invoker/provider 最小切片,避免一次卷入 notification / stream / pipeline executor
@@ -81,14 +81,14 @@
- `CreateStream(...)` 在并发首次访问时只解析一次 `ICqrsRuntime`
- 集成后已确认三份测试文件中不再残留 `GFramework.Cqrs.Tests.Mediator` 命名空间或 `Mediator` 语义命名
-### 验证
+### 验证(RP-065)
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureContextTests"`
- 结果:通过,`22/22` passed
-### 当前下一步
+### 当前下一步(RP-065)
1. 继续 `Phase 8` 主线,回到 `dispatch/invoker` 生成前移或 `LegacyICqrsRuntime` 收口的下一个低风险切片
2. 在下一次 batch 结束后复算 branch diff,确认距 `50 files` stop condition 的剩余 headroom
@@ -110,7 +110,7 @@
- `GFramework.Cqrs/README.md` 与 `docs/zh-CN/core/cqrs.md` 已同步说明默认通知语义与可替换 seam
- 中途验证曾因并行 .NET 构建产生输出文件锁噪音;已改为串行重跑并获取干净结果
-### 验证
+### 验证(RP-064)
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
@@ -123,7 +123,7 @@
- `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`
- 结果:通过
-### 当前下一步
+### 当前下一步(RP-064)
1. 评估 notification publisher seam 的第二阶段是否需要公开配置面、并行 publisher 或 telemetry decorator
2. 把 `dispatch/invoker` 生成前移重新拉回 `Phase 8` 主线,作为下一个实现切片
@@ -140,7 +140,7 @@
- 当前仍未完整吸收 publisher 策略抽象、细粒度 pipeline、telemetry / diagnostics / benchmark 体系与 runtime 主体生成
- 本轮把默认下一步从“继续盯 PR thread”调整为“围绕 publisher seam 与 dispatch/invoker 生成前移做下一轮设计收敛”
-### 验证
+### 验证(RP-063)
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
- 结果:通过,`0 warning / 0 error`