refactor: cluster-003 projection scope watermark query → typed readmodel (no event-store replay)#734
Conversation
📊 当前状态 — cluster-003 implement → PR open
下一步:CI 全跑 → Phase 8 reviewer 派出。 🤖 controller status banner |
…del (no event-store replay) Old pattern: ProjectionScopeWatermarkQueryPort 仍通过 IEventStore replay scope state events 拼装 watermark(查询路径强依赖 replay)。 New principle: 替换为 typed ProjectionScopeWatermarkReadModelQueryPort + 新 ProjectionScopeWatermarkReadModel,materialize from ProjectionScopeGAgentBase committed state events;query path O(1) document lookup,never replays IEventStore。 iter18 cluster-003(audit-only,无 design phase)。 - 新增 ProjectionScopeWatermarkReadModel partial + metadata provider + query port - 删除 EventStoreProjectionScopeWatermarkQueryPort(legacy replay port) - ProjectionScopeGAgentBase materialize 新 readmodel - 多个 hosting/runtime DI 替换注册 Co-Authored-By: codex-refactor-loop implement <noreply@aevatar>
📊 当前状态 — rebase resolved + force-pushed
🤖 controller status banner |
dba700f to
199330c
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## auto-refact-dev #734 +/- ##
================================================
Coverage 82.67% 82.68%
================================================
Files 951 951
Lines 60047 60047
Branches 7860 7860
================================================
+ Hits 49645 49649 +4
+ Misses 7024 7023 -1
+ Partials 3378 3375 -3
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
📊 当前状态 — codecov/patch fail,派 test-add
🤖 controller status banner |
🤖 TEST_ADD_DONE: cluster-003 ✅状态: ok 补测范围
验证
|
补 query port 边界(missing document / 各 active / released 状态 / version mapping)。 TEST_ADD_DONE:cluster-003:ok:100.00 Co-Authored-By: codex-refactor-loop test-add <noreply@aevatar>
🤖 quality review:拒绝,读模型/查询端口重复TL;DR
详细说明主要问题不是具体命名风格,而是简单性: 另外, 建议把 📎 完整 codex 原始输出(存档备查)---
pr: 734
role: quality
verdict: reject
---
## Verdict
Reject from the quality/simplicity angle because the PR introduces a second projection-scope watermark read model and query port even though the branch already has an existing status read model/query port for the same business question.
## Evidence
- `src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeWatermarkReadModelQueryPort.cs:5` adds `ProjectionScopeWatermarkReadModelQueryPort : IProjectionScopeWatermarkQueryPort`, but `src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeStatusQueryPort.cs:8` already implements the same interface and reads the same scope actor id with the same `Active && !Released` guard and `LastSuccessfulVersion` result. That is a parallel public implementation with no documented differentiator.
- `src/Aevatar.CQRS.Projection.Core/projection_scope_messages.proto:28` adds `ProjectionScopeWatermarkReadModel` with `id/root_actor_id/projection_kind/session_id/mode/active/released/last_observed_version/last_successful_version/state_version/last_event_id`, while the existing `ProjectionScopeStatusDocument` path already materializes these status/watermark fields in `src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeStatusProjector.cs:50`. This looks like duplicate modeling rather than a narrower business concept.
- `src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase.cs:168` adds direct `OnStateChangedAsync` document upsert logic for the new watermark model, while `ProjectionScopeStatusProjector` already materializes projection scope state into a document through the projection pipeline. The extra write path increases cognitive load and registration surface across hosts without reducing duplication.
- `src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeWatermarkReadModelMetadataProvider.cs:8` says "refactor helper, no behavior change", but this file introduces a new index contract (`projection-scope-watermarks`). The comment understates the behavior surface and is not clear to a non-audit reader.
## What would change your verdict (only if comment or reject)
Reuse the existing `ProjectionScopeStatusDocument` / `ProjectionScopeStatusQueryPort` path for `IProjectionScopeWatermarkQueryPort`, or explicitly delete/replace the status path so there is only one projection-scope status/watermark read model. If a separate watermark document is truly required, document the distinct consumer/query semantics and remove overlapping fields that belong to the existing status document.
REVIEW_DONE:734:quality:reject |
🤖 架构评审拒绝:watermark readmodel 走了 actor 直写和双轨TL;DR
详细说明主要阻塞点是 第二个阻塞点是该 PR 新增
📎 完整 codex 原始输出(存档备查)---
pr: 734
role: architect
verdict: reject
---
## Verdict
reject: the PR removes query-time event-store replay but introduces a direct actor-to-document-store materialization path and a parallel watermark readmodel instead of using the existing Projection Pipeline readmodel.
## Evidence
- `src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase.cs:168`: `OnStateChangedAsync` resolves `IProjectionDocumentWriter<ProjectionScopeWatermarkReadModel>` from actor services and writes the readmodel directly at lines 181-196. This violates CLAUDE.md: "actor 不直接拥有存储实现:actor 发布 `state_root` 作为 readmodel 统一 committed 输入,但 document store/graph store/query provider 等物化职责属于 projection/runtime/provider 边界。" It also violates CLAUDE.md: "projection 负责物化,不负责推导:消费 `EventEnvelope<CommittedStateEventPublished>` 的 `state_event + state_root` 物化到 document/index/search/graph store;actor 内已确定的当前态语义前移到 actor,projection 只做校验、覆盖写入、索引和分发。" The existing `ProjectionScopeStatusProjector` shows the compliant shape: a projector consumes `EventEnvelope<CommittedStateEventPublished>` and writes the document outside the actor.
- `src/Aevatar.CQRS.Projection.Core/projection_scope_messages.proto:28`: the PR adds `ProjectionScopeWatermarkReadModel` even though `origin/auto-refact-dev` already has `ProjectionScopeStatusDocument` with the same scope identity, `state_version`, `last_event_id`, `active/released`, and `last_successful_version` fields in `projection_scope_status_readmodel.proto`, and `ProjectionScopeStatusQueryPort` already implements `IProjectionScopeWatermarkQueryPort`. This violates CLAUDE.md: "单一主干,插件扩展:只保留一条权威业务主链路;新能力以插件/模块挂载,禁止平行\"第二系统\"。" It also violates CLAUDE.md: "readmodel 按需创建:只有存在稳定消费场景(明确消费方、查询入口、返回 DTO)时才新增 readmodel。" The diff does not introduce a new consumption scenario; it duplicates the existing watermark query role.
- `src/Aevatar.CQRS.Projection.Core/DependencyInjection/EventSinkProjectionRuntimeRegistration.cs:32` and `src/Aevatar.CQRS.Projection.Core/DependencyInjection/ProjectionMaterializationRuntimeRegistration.cs:30`: the new `ProjectionScopeWatermarkReadModelQueryPort` is registered as another implementation for `IProjectionScopeWatermarkQueryPort` while the existing `ProjectionScopeStatusRuntimeRegistration` still registers `ProjectionScopeStatusQueryPort` for the same interface. This violates CLAUDE.md: "删除优先:空转发、重复抽象、无业务价值代码直接删除,不保留兼容空壳。" and AGENTS.md: "删除优于兼容:重构以清晰正确为第一目标;无业务价值或重复层应直接删除,不为历史包袱保留空壳。"
- Scope honesty comment: `.refactor-loop/runs/audit-iter-18.md` declares `cluster-003-event-store-watermark-query-replay`, but `.refactor-loop/runs/implement-cluster-003.md` describes `cluster-003-scope-service-agui-host-pump`. The actual diff matches the audit cluster more than the implement summary, so this is not the blocking reason, but the summary artifact is stale/mismatched for this PR.
## What would change your verdict
- Remove `ProjectionScopeWatermarkReadModel`, `ProjectionScopeWatermarkReadModelQueryPort`, and the direct `IProjectionDocumentWriter` call from `ProjectionScopeGAgentBase`.
- Use the existing `ProjectionScopeStatusDocument` / `ProjectionScopeStatusProjector` / `ProjectionScopeStatusQueryPort` path for watermark queries, or replace it with exactly one Projection Pipeline materializer that consumes committed state envelopes outside the actor.
- Keep only one `IProjectionScopeWatermarkQueryPort` registration for the selected readmodel path.
- Update the cluster implementation summary or PR description so the declared scope matches the actual watermark-replay change.
REVIEW_DONE:734:architect:reject |
🤖 tests reviewer: 拒绝,公共注册面缺少覆盖TL;DR
详细说明主要风险不是 query port 本身没有测,而是这次新增的 另外,这个 cluster 的目标是把 watermark query 从 event-store replay 迁到 readmodel 读取。当前新测试只验证构造函数没有 📎 完整 codex 原始输出(存档备查)---
pr: 734
role: tests
verdict: reject
---
## Verdict
Reject: the new watermark query/readmodel behavior has focused unit coverage, but the PR adds startup-critical readmodel store registrations across multiple public provider/host extension methods without updating their existing registration tests.
## Evidence
- `src/Aevatar.Scripting.Hosting/DependencyInjection/ScriptingProjectionProviderServiceCollectionExtensions.cs:91`, `:121`, `:133` add/register/check `ProjectionScopeWatermarkReadModel`, but `test/Aevatar.Hosting.Tests/ScriptCapabilityHostExtensionsTests.cs:69` only asserts the existing scripting Elasticsearch stores and `:115` only asserts the existing partial-registration readers; neither covers the new watermark reader/writer.
- `src/Aevatar.Studio.Hosting/StudioProjectionReadModelServiceCollectionExtensions.cs:77`, `:93`, `:146` add the watermark document to Studio provider registration and idempotency checks, but `test/Aevatar.Tools.Cli.Tests/StudioProjectionReadModelServiceCollectionExtensionsTests.cs:24` keeps the asserted document list without `ProjectionScopeWatermarkReadModel`, so the existing "register all readers" tests do not protect this new store.
- `src/platform/Aevatar.GAgentService.Hosting/DependencyInjection/ServiceCollectionExtensions.cs:132`, `:148`, `:170` and `src/platform/Aevatar.GAgentService.Governance.Hosting/DependencyInjection/ServiceCollectionExtensions.cs:78`, `:90` add GAgentService/Governance watermark stores, but `test/Aevatar.GAgentService.Integration.Tests/GAgentServiceHostingServiceCollectionExtensionsTests.cs:299` and `:344` still resolve only the pre-existing readmodels.
- `src/workflow/extensions/Aevatar.Workflow.Extensions.Hosting/WorkflowProjectionProviderServiceCollectionExtensions.cs:101`, `:125`, `:139` add workflow watermark stores and include them in the "all readers" predicate, but `test/Aevatar.Workflow.Host.Api.Tests/WorkflowHostingExtensionsCoverageTests.cs:358`, `:371`, and `:418` still assert only the previous workflow readmodels.
- `test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeWatermarkReadModelQueryPortTests.cs:52` checks the constructor has no direct `IEventStore` parameter, but this cluster is explicitly a "do not reintroduce event-store replay in the query path" refactor and the new query port lacks the same source-regression guard pattern used by `ProjectionScopeStatusQueryPortTests.Source_DoesNotReintroduceEventStoreReplay`.
- No new `Task.Delay`, `Thread.Sleep`, `[Skip]`, or manual-category test bypass was added in the PR diff.
## What would change your verdict
Add/extend registration tests for each touched provider surface to assert `IProjectionDocumentReader<ProjectionScopeWatermarkReadModel, string>` and the matching writer/dispatcher are registered for in-memory and Elasticsearch paths, and that partial-registration/idempotency checks include the watermark readmodel. Add a source-regression test for `ProjectionScopeWatermarkReadModelQueryPort.cs` asserting it does not contain `IEventStore`, `GetEventsAsync`, or `EventStoreProjectionScopeWatermarkQueryPort`.
REVIEW_DONE:734:tests:reject |
📊 当前状态 — r1 3/3 reject,派 fix r2
🤖 controller status banner |
🤖 PR 734 round 2 已移除重复 watermark readmodelTL;DR
详细说明本轮按 reviewer 共识选择“删除重复路径”,不是给新 store 补测试。 关键变更:删除 actor 内 📎 完整 codex 原始输出(存档备查)Fix report for PR 734 round 2Applied
Rejected as false positive
Blocked (cannot fix this round)
Build status
Recommendation for next round
|
…ument 而非新建 readmodel
architect/tests/quality 3/3 reject: 重复 iter17 cluster-034 已加的 ProjectionScopeStatusDocument
readmodel + ProjectionScopeStatusQueryPort 已经实现了 watermark 查询能力,本 PR 不应再加
ProjectionScopeWatermarkReadModel{Partial,MetadataProvider,QueryPort}。
修复:
- 删除新加的 ProjectionScopeWatermarkReadModel 三类(与 cluster-034 重复)
- ProjectionScopeGAgentBase 改 materialize 复用 ProjectionScopeStatusDocument
- 各 hosting DI / channel runtime / proto 注册指向 ProjectionScopeStatusQueryPort
最终效果同 cluster-034 + cluster-003 各自达 design 目标,无 redundant readmodel。
FIX_DONE:734:round-2:applied-8:rejected-0:blocked-0
Co-Authored-By: codex-refactor-loop fix r2 <noreply@aevatar>
🤖 Quality review r2:通过TL;DR
详细说明本轮 diff 只改 我按要求用三点 diff 检查,并读取了 PR branch 的完整文件内容。当前 round 2 没有新增生产代码重构主体,因此 📎 完整 codex 原始输出(存档备查)---
pr: 734
role: quality
verdict: approve
---
## Verdict
Approve: the current round-2 diff is small, focused, readable, and does not introduce dead code, unnecessary abstractions, duplicated logic, or unclear naming.
## Evidence
- `test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeGAgentBaseTests.cs:118` sets the test scope mode explicitly to `DurableMaterialization`, matching the `RuntimeMode` override and keeping the helper intent readable.
- `test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeGAgentBaseTests.cs:172-176` makes `ConfirmResult` drive both `CurrentVersion` and `ConfirmEventsAsync`, so the new helper surface is cohesive and reachable through the existing `TrackingEventSourcing` test double.
- `test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeGAgentBaseTests.cs:182-185` delegates watermark transition behavior to `ProjectionScopeStateApplier.ApplyWatermarkAdvanced` instead of duplicating state mutation logic inline.
- The three-dot diff only touches one test helper file with 9 insertions and 3 deletions; there are no new public production APIs, generic `Manager`/`Helper` names, commented-out blocks, broad formatting churn, or unrelated cleanup.
- No major production refactor body is added in this round's diff, so the mandated `// Refactor (iterN/cluster-XXX):` self-doc block is not triggered by the current test-only delta. |
🤖 Architect r2 结论:approveTL;DR
详细说明我按要求读取了 架构角度没有发现新增 production pathway、重复 readmodel、query-time replay、process-local fact map、JSON 内部序列化、host/application 订阅 📎 完整 codex 原始输出(存档备查)pr: 734
|
🤖 Tests reviewer:PR 734 r2 通过TL;DR
详细说明我按 测试侧没有新增 📎 完整 codex 原始输出(存档备查)---
pr: 734
role: tests
verdict: approve
---
## Verdict
Test coverage and quality are adequate for the current PR diff.
## Evidence
- `git diff origin/auto-refact-dev...origin/refactor/iter18-cluster-003-eventstore-watermark-replay --name-status` shows only `test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeGAgentBaseTests.cs` changed; there are no touched `src/` or `agents/` production files in the current three-dot diff requiring new matching tests.
- `test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeGAgentBaseTests.cs:118` sets `ProjectionScopeMode.DurableMaterialization`, keeping the existing behavior tests aligned with the mode used by `ProjectionScopeGAgentBase.BuildScopeKey`.
- `test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeGAgentBaseTests.cs:172-184` updates the `TrackingEventSourcing` test double to return the configured commit result and apply `ProjectionScopeWatermarkAdvancedEvent`, which makes the existing assertions exercise real watermark state transition behavior instead of bump-only coverage.
- No `Task.Delay`, `Thread.Sleep`, `WaitUntilAsync`, `[Skip]`, or `[Trait("Category","Manual")]` additions were found in the diff.
- `bash tools/ci/test_stability_guards.sh` passed: polling waits remain constrained by `tools/ci/test_polling_allowlist.txt`.
## What would change your verdict (only if comment or reject)
N/A
REVIEW_DONE:734:tests:approve |
📊 当前状态 — r2 unanimous approve 🎉
🤖 controller status banner |
🤖 PR 734 round 2 已移除重复 watermark readmodel
TL;DR
ProjectionScopeWatermarkReadModel双轨路径,保留ProjectionScopeStatusDocument作为唯一 watermark query readmodel。详细说明
本轮按 reviewer 共识选择“删除重复路径”,不是给新 store 补测试。
IProjectionScopeWatermarkQueryPort是查询 projection scope 已物化水位的应用端口;现在继续由ProjectionScopeStatusQueryPort实现,读取ProjectionScopeStatusDocument,不再新增ProjectionScopeWatermarkReadModel或projection-scope-watermarksindex。关键变更:删除 actor 内
IProjectionDocumentWriter<ProjectionScopeWatermarkReadModel>直写,删除 watermark readmodel proto/partial/metadata/query port/test,移除 Channel/Scripting/Studio/GAgentService/Governance/Workflow hosting 中对该重复 readmodel 的 reader/writer 注册。验证已通过dotnet build aevatar.slnx --nologo、projection core targeted tests、test stability guard、query projection guard、state version guard、state mirror guard。📎 完整 codex 原始输出(存档备查)
Fix report for PR 734 round 2
Applied
src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeGAgentBase.cs:152: removed theOnStateChangedAsyncdirectIProjectionDocumentWriter<ProjectionScopeWatermarkReadModel>upsert path, so the actor no longer owns document-store materialization (addresses reviewer:architect evidence Refactor/project namespace #1 and reviewer:quality evidence MAF-Inspired Framework Improvements #3).src/Aevatar.CQRS.Projection.Core/projection_scope_messages.proto:28: removed the duplicateProjectionScopeWatermarkReadModelproto message; the single selected watermark read path remainsProjectionScopeStatusDocumentmaterialized byProjectionScopeStatusProjector(addresses reviewer:architect evidence Feature/cqrs projection suite #2 and reviewer:quality evidence Feature/cqrs projection suite #2).src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeWatermarkReadModelQueryPort.cs:5: deletedProjectionScopeWatermarkReadModelQueryPort, andProjectionScopeStatusRuntimeRegistrationremains the sole in-branchIProjectionScopeWatermarkQueryPortregistration throughProjectionScopeStatusQueryPort(addresses reviewer:architect evidence MAF-Inspired Framework Improvements #3 and reviewer:quality evidence Refactor/project namespace #1).src/Aevatar.CQRS.Projection.Core/Orchestration/ProjectionScopeWatermarkReadModelMetadataProvider.cs:8: deleted the rejectedprojection-scope-watermarksindex contract instead of documenting it as a distinct consumer path (addresses reviewer:quality evidence Aevatar context database #4).agents/Aevatar.GAgents.Channel.Runtime/DependencyInjection/ChannelRuntimeServiceCollectionExtensions.cs:57: removed the extra concrete watermark query port and watermark document store registrations; channel runtime now keeps onlyProjectionScopeStatusDocumentplusProjectionScopeStatusQueryPortfor watermark queries (addresses reviewer:architect evidence MAF-Inspired Framework Improvements #3).src/Aevatar.Scripting.Hosting/DependencyInjection/ScriptingProjectionProviderServiceCollectionExtensions.cs:91,src/Aevatar.Studio.Hosting/StudioProjectionReadModelServiceCollectionExtensions.cs:77,src/platform/Aevatar.GAgentService.Hosting/DependencyInjection/ServiceCollectionExtensions.cs:132,src/platform/Aevatar.GAgentService.Governance.Hosting/DependencyInjection/ServiceCollectionExtensions.cs:78, andsrc/workflow/extensions/Aevatar.Workflow.Extensions.Hosting/WorkflowProjectionProviderServiceCollectionExtensions.cs:101: removed the duplicate watermark readmodel store registrations from all provider surfaces, making the tests-reviewer registration demand obsolete because the rejected store no longer exists.test/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeWatermarkReadModelQueryPortTests.cs:1: deleted tests that only protected the rejected duplicate query port/readmodel. ExistingProjectionScopeStatusQueryPortTestsstill cover the accepted path, including noIEventStore, noGetEventsAsync, and noEventStoreProjectionScopeWatermarkQueryPortsource-regression guard attest/Aevatar.CQRS.Projection.Core.Tests/ProjectionScopeStatusQueryPortTests.cs:37.gh pr edit --body-fileso the declared scope now matches the actual watermark-replay fix and no longer claims a newProjectionScopeWatermarkReadModelimplementation (addresses reviewer:architect scope honesty comment).Rejected as false positive
Blocked (cannot fix this round)
Build status
dotnet build aevatar.slnx --nologo; 0 errors, existing warnings only)dotnet test test/Aevatar.CQRS.Projection.Core.Tests/Aevatar.CQRS.Projection.Core.Tests.csproj --nologo --no-build; 145 passed, 1 pre-existing skipped)bash tools/ci/test_stability_guards.sh;bash tools/ci/query_projection_priming_guard.sh;bash tools/ci/projection_state_version_guard.sh;bash tools/ci/projection_state_mirror_current_state_guard.sh)Recommendation for next round