diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
index 80926f1b..0bc762fb 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs
@@ -79,8 +79,8 @@ public sealed partial class CqrsHandlerRegistryGenerator
/// 从可直接表达 handler 接口的注册描述中提取 request invoker 发射计划。
///
///
- /// 指示当前 runtime 是否同时暴露 ICqrsRequestInvokerProvider 与
- /// IEnumeratesCqrsRequestInvokerDescriptors 契约;若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
+ /// 指示当前 runtime 是否完整暴露 request invoker provider、descriptor 与 descriptor entry 契约;
+ /// 若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
///
/// 已按稳定顺序整理完成的 handler 注册描述。
///
@@ -136,8 +136,8 @@ public sealed partial class CqrsHandlerRegistryGenerator
/// 从可直接表达 handler 接口的注册描述中提取 stream invoker 发射计划。
///
///
- /// 指示当前 runtime 是否同时暴露 ICqrsStreamInvokerProvider 与
- /// IEnumeratesCqrsStreamInvokerDescriptors 契约;若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
+ /// 指示当前 runtime 是否完整暴露 stream invoker provider、descriptor 与 descriptor entry 契约;
+ /// 若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
///
/// 已按稳定顺序整理完成的 handler 注册描述。
///
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index 77185834..545577f2 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -2332,6 +2332,66 @@ public class CqrsHandlerRegistryGeneratorTests
("CqrsHandlerRegistry.g.cs", AssemblyLevelCqrsHandlerRegistryExpected));
}
+ ///
+ /// 验证当 runtime 缺少 generated registry 需要依赖的基础合同时,
+ /// 生成器会整体跳过发射,避免产出无法承载运行时注册合同的半成品源码。
+ ///
+ /// 待移除 runtime 合同块的起始标记。
+ /// 待移除 runtime 合同块之后的下一个稳定标记。
+ [TestCase(
+ "public interface ICqrsHandlerRegistry",
+ "[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]")]
+ [TestCase(
+ "public interface INotificationHandler",
+ "public interface IStreamRequestHandler")]
+ [TestCase(
+ "public interface IRequestHandler",
+ "rename:MissingIRequestHandler")]
+ [TestCase(
+ "public interface IStreamRequestHandler",
+ "rename:MissingIStreamRequestHandler")]
+ [TestCase(
+ "[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]",
+ "[AttributeUsage(AttributeTargets.Assembly)]")]
+ [TestCase(
+ "public interface ILogger",
+ "rename:MissingILogger")]
+ [TestCase(
+ "public interface IServiceCollection",
+ "rename:MissingServiceCollection")]
+ public void Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract(
+ string startMarker,
+ string endMarker)
+ {
+ var source = endMarker.StartsWith("rename:", StringComparison.Ordinal)
+ ? RenameTypeIdentifier(
+ HiddenNestedHandlerSelfRegistrationSource,
+ startMarker.Replace("public interface ", string.Empty, StringComparison.Ordinal),
+ endMarker["rename:".Length..])
+ : RemoveBlock(
+ HiddenNestedHandlerSelfRegistrationSource,
+ startMarker,
+ endMarker);
+ var execution = ExecuteGenerator(source);
+ var inputCompilationErrors = execution.InputCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ var generatorErrors = execution.GeneratorDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(inputCompilationErrors, Is.Empty);
+ Assert.That(generatedCompilationErrors, Is.Empty);
+ Assert.That(generatorErrors, Is.Empty);
+ Assert.That(execution.GeneratedSources, Is.Empty);
+ });
+ }
+
///
/// 验证当程序集包含生成代码无法合法引用的私有嵌套处理器时,生成器会在生成注册器内部执行定向反射注册,
/// 不再依赖程序集级 fallback marker。
@@ -2870,6 +2930,50 @@ public class CqrsHandlerRegistryGeneratorTests
});
}
+ ///
+ /// 验证当 runtime 同时支持直接 与字符串 fallback 元数据、但不允许多个 fallback 特性实例时,
+ /// mixed 场景会整体回退到单个字符串特性,避免生成会违反 runtime attribute usage 的多实例元数据。
+ ///
+ [Test]
+ public void
+ Emits_String_Fallback_Metadata_For_Mixed_Fallback_When_Runtime_Disallows_Multiple_Fallback_Attributes()
+ {
+ var source = ReplaceAttributeUsageForType(
+ AssemblyLevelMixedFallbackMetadataSource,
+ "CqrsReflectionFallbackAttribute",
+ "[AttributeUsage(AttributeTargets.Assembly)]");
+ var execution = ExecuteGenerator(
+ source,
+ allowUnsafe: true);
+ var inputCompilationErrors = execution.InputCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ var generatorErrors = execution.GeneratorDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ Assert.Multiple(() =>
+ {
+ Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306"));
+ Assert.That(generatedCompilationErrors, Is.Empty);
+ Assert.That(generatorErrors, Is.Empty);
+ Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
+ var generatedSource = execution.GeneratedSources[0].content;
+ Assert.That(
+ generatedSource,
+ Does.Contain(
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+AlphaHandler\", \"TestApp.Container+BetaHandler\")]"));
+ Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute(typeof("));
+ Assert.That(
+ CountOccurrences(
+ generatedSource,
+ "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"),
+ Is.EqualTo(1));
+ });
+ }
+
///
/// 验证当 runtime 暴露 request invoker provider 契约时,生成器会让 generated registry 同时发射
/// request invoker 描述符与对应的开放静态 invoker 方法。
@@ -2998,6 +3102,58 @@ public class CqrsHandlerRegistryGeneratorTests
});
}
+ ///
+ /// 验证当 runtime 缺少 CqrsRequestInvokerDescriptor 时,
+ /// 生成器不会继续发射依赖描述符类型的 request provider 元数据。
+ ///
+ [Test]
+ public void Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Type()
+ {
+ var source = RenameTypeIdentifier(
+ RequestInvokerProviderSource,
+ "CqrsRequestInvokerDescriptor",
+ "MissingCqrsRequestInvokerDescriptor");
+ var generatedSource = RunGenerator(source);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(
+ generatedSource,
+ Does.Contain(
+ "internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry"));
+ Assert.That(generatedSource, Does.Not.Contain("ICqrsRequestInvokerProvider"));
+ Assert.That(generatedSource, Does.Not.Contain("IEnumeratesCqrsRequestInvokerDescriptors"));
+ Assert.That(generatedSource, Does.Not.Contain("CqrsRequestInvokerDescriptorEntry("));
+ Assert.That(generatedSource, Does.Not.Contain("InvokeRequestHandler0"));
+ });
+ }
+
+ ///
+ /// 验证当 runtime 缺少 CqrsRequestInvokerDescriptorEntry 时,
+ /// 生成器不会继续保留 request provider 的枚举接口或静态 invoker 元数据。
+ ///
+ [Test]
+ public void Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type()
+ {
+ var source = RenameTypeIdentifier(
+ RequestInvokerProviderSource,
+ "CqrsRequestInvokerDescriptorEntry",
+ "MissingCqrsRequestInvokerDescriptorEntry");
+ var generatedSource = RunGenerator(source);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(
+ generatedSource,
+ Does.Contain(
+ "internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry"));
+ Assert.That(generatedSource, Does.Not.Contain("ICqrsRequestInvokerProvider"));
+ Assert.That(generatedSource, Does.Not.Contain("IEnumeratesCqrsRequestInvokerDescriptors"));
+ Assert.That(generatedSource, Does.Not.Contain("CqrsRequestInvokerDescriptorEntry("));
+ Assert.That(generatedSource, Does.Not.Contain("InvokeRequestHandler0"));
+ });
+ }
+
///
/// 验证当 request handler 仍需走 precise reflected 注册时,
/// 生成器即使检测到 request invoker provider runtime 合同,也不会错误发射无法稳定表达隐藏请求/响应类型的 provider 元数据。
@@ -3373,6 +3529,47 @@ public class CqrsHandlerRegistryGeneratorTests
return !char.IsLetterOrDigit(character) && character != '_';
}
+ ///
+ /// 替换指定测试类型紧邻的 AttributeUsage 声明,用于构造 runtime contract 的 attribute usage 变体。
+ ///
+ /// 原始测试源码。
+ /// 需要定位的类型名。
+ /// 替换后的完整 AttributeUsage 声明。
+ /// 完成 attribute usage 替换后的源码。
+ private static string ReplaceAttributeUsageForType(
+ string source,
+ string typeName,
+ string replacementAttributeUsage)
+ {
+ ArgumentNullException.ThrowIfNull(source);
+ ArgumentNullException.ThrowIfNull(typeName);
+ ArgumentNullException.ThrowIfNull(replacementAttributeUsage);
+
+ var typeIndex = source.IndexOf($"public sealed class {typeName}", StringComparison.Ordinal);
+ if (typeIndex < 0)
+ {
+ throw new InvalidOperationException("The requested type declaration was not found in the generator test input.");
+ }
+
+ const string attributeUsagePrefix = "[AttributeUsage(";
+ var attributeUsageStartIndex = source.LastIndexOf(attributeUsagePrefix, typeIndex, StringComparison.Ordinal);
+ if (attributeUsageStartIndex < 0)
+ {
+ throw new InvalidOperationException("The requested AttributeUsage declaration was not found in the generator test input.");
+ }
+
+ var attributeUsageEndIndex = source.IndexOf(']', attributeUsageStartIndex);
+ if (attributeUsageEndIndex < 0 || attributeUsageEndIndex > typeIndex)
+ {
+ throw new InvalidOperationException("The requested AttributeUsage declaration is malformed.");
+ }
+
+ return source.Remove(
+ attributeUsageStartIndex,
+ attributeUsageEndIndex - attributeUsageStartIndex + 1)
+ .Insert(attributeUsageStartIndex, replacementAttributeUsage);
+ }
+
///
/// 统计生成源码中某个固定片段的出现次数,用于锁定程序集级 fallback 特性的发射个数。
///
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 fff5461f..efc871e8 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,19 +7,24 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-076`
+- 恢复点编号:`CQRS-REWRITE-RP-082`
- 当前阶段:`Phase 8`
-- 当前 PR 锚点:`PR #307`
+- 当前 PR 锚点:`PR #323`
- 当前结论:
- `GFramework.Cqrs` 已完成对外部 `Mediator` 的生产级替代,当前主线已从“是否可替代”转向“仓库内部收口与能力深化顺序”
- - `dispatch/invoker` 生成前移已扩展到 request / stream 路径,当前 `RP-076` 已补齐 stream invoker provider gate 的四项 runtime 合同分支
- - `ai-plan` active 入口现以 `PR #307` 和 `RP-076` 为唯一权威恢复锚点;更早 PR 与阶段细节均以下方归档为准
+ - `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 回退回归
+ - `RP-079` 已补齐 runtime 缺少 generated handler registry interface 时的 generator 静默跳过回归
+ - `RP-080` 已将基础 generation gate 回归扩展到 notification handler interface、stream handler interface 与 registry attribute 缺失分支
+ - `RP-081` 已继续补齐基础 generation gate 的 logging 与 DI runtime contract 缺失分支
+ - 当前 `RP-082` 已补齐基础 generation gate 的 request handler runtime contract 缺失分支
+ - `ai-plan` active 入口现以 `PR #323` 和 `RP-082` 为唯一权威恢复锚点;`PR #307`、其他更早 PR 与阶段细节均以下方归档或说明为准
## 当前活跃事实
-- 当前分支对应 `PR #307`,状态为 `OPEN`
+- 当前分支对应 `PR #323`,状态为 `OPEN`
- latest-head review 仍以 `ai-plan` 恢复文档收敛为主要待闭环项;代码与测试侧的本地有效问题已收敛
-- 远端 `CTRF` 最新汇总为 `2247/2247` passed
+- 远端 `CTRF` 最新汇总为 `2274/2274` passed
- `MegaLinter` 当前只暴露 `dotnet-format` 的 `Restore operation failed` 环境噪音,尚未提供本地仍成立的文件级格式诊断
## 当前风险
@@ -30,18 +35,65 @@ CQRS 迁移与收敛。
## 最近权威验证
-- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
+- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output `
- 结果:通过
- - 备注:确认当前分支对应 `PR #307`,本轮剩余 open AI feedback 主要集中在 `ai-plan` 收敛
+ - 备注:确认当前分支对应 `PR #323`,本轮剩余 open AI feedback 主要集中在 `ai-plan` PR 锚点收敛
+- `git diff --check`
+ - 结果:通过
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "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.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Entry_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"`
- 结果:通过,`5/5` passed
+- `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_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type|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"`
+ - 结果:通过,`4/4` passed
+- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
+ - 结果:通过,`0 warning / 0 error`
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行,避免脚本内部 plain `git ls-files` 误判仓库上下文
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_String_Fallback_Metadata_For_Mixed_Fallback_When_Runtime_Disallows_Multiple_Fallback_Attributes"`
+ - 结果:通过,`1/1` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Handler_Registry_Interface"`
+ - 结果:通过,`1/1` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"`
+ - 结果:通过,`4/4` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"`
+ - 结果:通过,`6/6` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"`
+ - 结果:通过,`7/7` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
## 下一推荐步骤
-1. 继续处理 `PR #307` 的剩余 review 收尾,优先保持 `ai-plan` active 入口与 trace 的单一锚点一致
-2. 若继续推进代码切片,优先复核 request 侧是否存在与 stream gate 对称的生成合同遗漏,再决定是否补同批 generator 回归
+1. 继续处理 `PR #323` 的剩余 review 收尾,优先保持 `ai-plan` active 入口与 trace 的单一锚点一致
+2. 若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支;基础 gate 的可安全构造缺失分支已覆盖
3. 在进入下一批 runtime / generator 收敛前,保持最小 Release build 或 targeted test 作为权威验证
## 活跃文档
@@ -56,5 +108,5 @@ CQRS 迁移与收敛。
## 说明
-- `PR #261`、`PR #302`、`PR #305` 及更早阶段的详细过程已不再作为 active 恢复入口;如需追溯,以对应归档文件为准
+- `PR #261`、`PR #302`、`PR #305`、`PR #307` 及更早阶段的详细过程已不再作为 active 恢复入口;如需追溯,以对应归档文件或历史 trace 段落为准
- active tracking 仅保留当前恢复点、当前风险、最近权威验证与下一推荐步骤,避免 `boot` 落到历史阶段细节
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 b46a73ea..0dec487b 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,11 +2,11 @@
## 2026-04-30
-### 阶段:PR #307 active 入口收敛(CQRS-REWRITE-RP-076)
+### 阶段:历史 PR #307 active 入口收敛(CQRS-REWRITE-RP-076)
- 继续沿用 `$gframework-pr-review` 对 `PR #307` 做 latest-head triage,本轮只处理仍成立的 `ai-plan` 恢复入口问题
- 主线程确认当前远端权威信号:
- - 当前分支对应 `PR #307`,状态为 `OPEN`
+ - 当时分支对应 `PR #307`,状态为 `OPEN`
- 远端 `CTRF` 最新汇总为 `2247/2247` passed
- `MegaLinter` 仅剩 `dotnet-format` 的 `Restore operation failed` 环境噪音
- 仍未闭环的 review 重点集中在 `cqrs-rewrite` active tracking / trace 仍保留过多历史锚点,而非新的运行时代码缺陷
@@ -17,7 +17,7 @@
### 验证(RP-076)
-- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
+- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output `
- 结果:通过
- 备注:确认 `PR #307` 的当前 review 重点已收敛到 `ai-plan` 文档收尾
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
@@ -27,6 +27,188 @@
### 当前下一步(RP-076)
-1. 继续按 `PR #307` 的 latest-head review 收尾,优先保持 active tracking 与 active trace 的单一锚点一致
+1. 当时继续按 `PR #307` 的 latest-head review 收尾,优先保持 active tracking 与 active trace 的单一锚点一致
2. 若继续推进代码切片,先复核 request 侧是否仍存在与 stream invoker gate 对称的生成合同遗漏
3. 进入下一批前继续使用最小 Release build 或 targeted test 作为权威验证,避免把环境噪音误判为代码问题
+
+## 2026-05-04
+
+### 阶段:request invoker provider gate 对称回归(CQRS-REWRITE-RP-077)
+
+- 使用 `$gframework-batch-boot 25` 继续 `feat/cqrs-optimization` 的 CQRS 收口批次
+- 批次目标:在 branch diff 相对 `origin/main` 接近 `25` 个文件前,补齐低风险的 generator 合同回归切片
+- 本轮先确认当前 worktree 已无 `local-plan` 遗留恢复入口,随后转入 `cqrs-rewrite` 的 request / stream invoker provider gate 对称性复核
+- 结论:
+ - 生产代码已经同时检查 request provider、enumerator、descriptor 与 descriptor entry 四项 runtime 合同
+ - request 侧测试只覆盖缺少 provider / enumerator,缺少 descriptor / descriptor entry 的回归覆盖落后于 stream 侧
+- 已补齐:
+ - `Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Type`
+ - `Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type`
+ - source emission XML 文档同步说明 provider gate 依赖完整 descriptor / descriptor entry 合同
+
+### 验证(RP-077)
+
+- `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_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type|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"`
+ - 结果:通过,`4/4` passed
+- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
+ - 结果:通过,`0 warning / 0 error`
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行,避免脚本内部 plain `git ls-files` 误判仓库上下文
+- `git diff --check`
+ - 结果:通过
+
+### 当前下一步(RP-077)
+
+1. 继续使用 `origin/main` 作为 `$gframework-batch-boot 25` 的基线,复算 branch diff 后决定是否还能接下一批
+2. 若继续推进代码切片,优先查找 request / stream invoker provider runtime 合同之外的同类对称测试缺口
+
+### 阶段:mixed fallback attribute usage 回归(CQRS-REWRITE-RP-078)
+
+- 继续沿用 `$gframework-batch-boot 25`,当前 branch diff 仍低于阈值
+- 复核 fallback metadata runtime contract 后确认:
+ - mixed fallback 在 runtime 允许多个 fallback attribute 实例时已有直接 `Type` + 字符串拆分回归
+ - runtime 同时支持 `params Type[]` / `params string[]` 但不允许多个 fallback attribute 实例时,缺少锁定“整体回退到单个字符串 attribute”的回归
+- 已补齐:
+ - `Emits_String_Fallback_Metadata_For_Mixed_Fallback_When_Runtime_Disallows_Multiple_Fallback_Attributes`
+ - `ReplaceAttributeUsageForType` 测试辅助方法,用于构造 runtime attribute usage 变体而不复制大型 source fixture
+
+### 验证(RP-078)
+
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_String_Fallback_Metadata_For_Mixed_Fallback_When_Runtime_Disallows_Multiple_Fallback_Attributes"`
+ - 结果:通过,`1/1` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+
+### 当前下一步(RP-078)
+
+1. 继续复算 branch diff vs `origin/main`,若仍低于 `25` 个文件可继续下一批
+2. 下一批优先查看 fallback metadata 与 generated invoker provider 之外是否还有同类 runtime contract gate 回归缺口
+
+### 阶段:基础 generated registry contract gate 回归(CQRS-REWRITE-RP-079)
+
+- 继续沿用 `$gframework-batch-boot 25`,当前 branch diff 仍低于阈值
+- 复核 generator 基础启用条件后确认:缺少 `ICqrsHandlerRegistry` 时,runtime 不具备承载 generated registry 的基础接口合同,应整体跳过发射
+- 已补齐:
+ - `Does_Not_Generate_Registry_When_Runtime_Lacks_Handler_Registry_Interface`
+
+### 验证(RP-079)
+
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Handler_Registry_Interface"`
+ - 结果:通过,`1/1` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+
+### 当前下一步(RP-079)
+
+1. 继续复算 branch diff vs `origin/main`,若仍低于 `25` 个文件可继续下一批
+2. 下一批优先复核基础 generation gate 中其他必需 runtime contracts 是否也需要同类回归覆盖
+
+### 阶段:基础 generated registry contract gate 扩展回归(CQRS-REWRITE-RP-080)
+
+- 将 `RP-079` 的单一 handler registry interface 缺失回归扩展为基础 generation gate 参数化测试
+- 已补齐缺失分支:
+ - `ICqrsHandlerRegistry`
+ - `INotificationHandler`
+ - `IStreamRequestHandler`
+ - `CqrsHandlerRegistryAttribute`
+- stream handler interface 变体采用类型重命名构造 runtime metadata miss,避免删除命名空间尾部单行接口时引入输入编译错误
+
+### 验证(RP-080)
+
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"`
+ - 结果:通过,`4/4` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+
+### 当前下一步(RP-080)
+
+1. 继续复算 branch diff vs `origin/main`,若仍低于 `25` 个文件可继续下一批
+2. 下一批优先复核基础 generation gate 中 logging / DI 依赖是否已有合适的输入编译安全回归覆盖方式
+
+### 阶段:基础 generated registry external contract gate 回归(CQRS-REWRITE-RP-081)
+
+- 延续 `RP-080` 的参数化基础 generation gate 测试,将外部 logging / DI 依赖也纳入同一组静默跳过回归
+- 已补齐缺失分支:
+ - `GFramework.Core.Abstractions.Logging.ILogger`
+ - `Microsoft.Extensions.DependencyInjection.IServiceCollection`
+- 两个变体均通过类型重命名构造 runtime metadata miss,保持输入源码可编译,避免把依赖缺失测试误写成编译失败测试
+
+### 验证(RP-081)
+
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"`
+ - 结果:通过,`6/6` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+
+### 当前下一步(RP-081)
+
+1. 继续复算 branch diff vs `origin/main`,若仍低于 `25` 个文件可继续下一批
+2. 下一批优先复核基础 generation gate 中 request handler contract 与 handler registry attribute 以外是否还有可安全构造的缺失分支
+
+### 阶段:基础 generated registry request handler gate 回归(CQRS-REWRITE-RP-082)
+
+- 延续 `RP-081` 的基础 generation gate 参数化测试,补齐 `IRequestHandler` 缺失分支
+- 该变体同样通过类型重命名构造 runtime metadata miss,保持输入源码可编译
+- 至此基础 generation gate 中可安全构造的缺失分支已覆盖:
+ - request handler interface
+ - notification handler interface
+ - stream handler interface
+ - handler registry interface
+ - handler registry attribute
+ - logging interface
+ - DI service collection interface
+
+### 验证(RP-082)
+
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"`
+ - 结果:通过,`7/7` passed
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `git diff --check`
+ - 结果:通过
+
+### 当前下一步(RP-082)
+
+1. 继续复算 branch diff vs `origin/main`,若仍低于 `25` 个文件可继续下一批
+2. 下一批优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支;基础 gate 的可安全构造缺失分支已覆盖
+
+### 阶段:PR #323 review 锚点收敛(CQRS-REWRITE-RP-082)
+
+- 使用 `$gframework-pr-review` 重新拉取当前分支 PR review payload,确认当前分支对应 `PR #323`,状态为 `OPEN`
+- 本轮 latest-head open AI thread 仅指出 active tracking 中仍保留 `PR #307` 作为当前 PR 锚点;本地复核后确认该反馈仍成立
+- 已将 active tracking 的当前 PR 锚点、活跃事实、最近 PR review 备注和下一推荐步骤统一到 `PR #323`
+- `PR #307` 仅保留为历史 PR 说明和较早 trace 段落,不再作为 active 恢复入口
+
+### 验证(PR #323 review)
+
+- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output `
+ - 结果:通过
+ - 备注:确认 `PR #323` 只有 1 个 CodeRabbit open thread,指向 active tracking 的 PR 锚点漂移
+- 远端 `CTRF` 最新汇总为 `2274/2274` passed
+- `MegaLinter` 当前仅报告 `dotnet-format` 的 `Restore operation failed` 环境噪音,未提供本地仍成立的文件级格式诊断
+- `git diff --check`
+ - 结果:通过
+- `python3 scripts/license-header.py --check`
+ - 结果:通过
+ - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
+- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release`
+ - 结果:通过,`0 warning / 0 error`
+
+### 当前下一步(PR #323 review)
+
+1. 若 review 重新触发后仍有 latest-head open thread,继续以 `PR #323` 为当前唯一 PR 恢复锚点复核
+2. 后续若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支