fix: make scope script save observation visible after upsert#496
fix: make scope script save observation visible after upsert#496jason-aelf merged 2 commits intodevfrom
Conversation
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## dev #496 +/- ##
==========================================
+ Coverage 71.54% 71.62% +0.08%
==========================================
Files 1236 1236
Lines 89569 89573 +4
Branches 11712 11713 +1
==========================================
+ Hits 64081 64156 +75
+ Misses 20903 20820 -83
- Partials 4585 4597 +12
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 8 files with indirect coverage changes 🚀 New features to boost your workflow:
|
eanzhao
left a comment
There was a problem hiding this comment.
Review
Architecture concern (P2)
In ScopeScriptCommandApplicationService.cs, explicitly activating authority read models in the command application service before dispatching write commands mixes read-model lifecycle concerns into the write path. The standard command lifecycle should remain Normalize -> Resolve Target -> Build Context -> Build Envelope -> Dispatch -> Receipt -> Observe.
If the projection runtime does not auto-activate read models on first event, the fix should be in the projection infrastructure (or an activation binder/lease), not in every command service. Consider adding a TODO comment linking to a follow-up issue to remove this workaround once the runtime handles activation implicitly.
Otherwise LGTM
- Minimal production change (6 lines) - good.
- Unit test verifies activation order before writes - correct.
- Integration test properly exercises real in-memory projection and is added to the polling allowlist.
- No race condition introduced; activation is awaited before write dispatch.
eanzhao
left a comment
There was a problem hiding this comment.
LGTM. Fix is minimal and correct — activates both definition and catalog authority read models before dispatching write commands, matching the existing activation pattern in .
Strengths:
- Clean 6-line production change with clear causal link to the bug
- Unit test properly validates activation ordering via execution log
- Integration test covers the full end-to-end observation path with real in-memory projection
- Polling properly allowlisted per CLAUDE.md test stability rules
One minor note: and are independent — could be parallelized with to avoid sequential overhead. Not blocking since correctness is the priority here.
eanzhao
left a comment
There was a problem hiding this comment.
LGTM. Fix is minimal and correct — activates both definition and catalog authority read models before dispatching write commands, matching the existing activation pattern in ScriptAuthorityProjectionPort.
Strengths:
- Clean 6-line production change with clear causal link to the bug
- Unit test properly validates activation ordering via execution log
- Integration test covers the full end-to-end observation path with real in-memory projection
- Polling properly allowlisted per CLAUDE.md test stability rules
One minor note: ActivateAsync for definition and catalog actors are independent — could be parallelized with Task.WhenAll to avoid sequential overhead. Not blocking since correctness is the priority here.
架构回归:把 authority readmodel activation 重新塞回写路径这个 PR 直接撤销了 PR #181(closing #158/#159)刚刚收口完的 CQRS 边界。 1. 与既有设计文档冲突
2. 绕过既存守卫,但违反守卫意图现有的 arch test 本 PR 把 activation 加到了更上层的 3. 根因诊断方向错了issue #488 的现象是:本机 in-memory 模式下 catalog readmodel 永远 pending。这意味着生产(Kafka/ES)路径下 authority projection 是被某种引导机制激活的;in-memory 路径缺的是读侧的 bootstrap,不是写侧的预挂接。 正确的修复方向(与 §11.4 / §13 一致):
4. 命令侧契约也被偷偷扩展现在 建议
参考
|
eanzhao
left a comment
There was a problem hiding this comment.
Architecture Concern: Write-Path Read-Model Activation
This PR reintroduces into production code the exact pattern that was deliberately removed in the previous CQRS closure refactoring (documented in docs/history/2026-04/2026-04-09-scripting-authority-write-path-cqrs-closure.md).
Problem
ScopeScriptCommandApplicationService is a write-path command service. The PR injects IScriptAuthorityReadModelActivationPort into it and calls ActivateAsync before dispatching write commands. The design document explicitly forbids this:
"要求 authority readmodel bootstrap 只能由 read-side activation / lease 入口 承担,禁止回流到 runtime create / command path"
"禁止 definition/catalog/runtime command path 依赖 authority projection activation"
Why This Matters
-
CQRS boundary violation — The command path now controls projection lifecycle, coupling write success to read-side activation availability. If activation fails or is slow, the command itself becomes blocked or impaired.
-
Guard coverage gaps — None of the three existing CI guards catch this:
query_projection_priming_guard.sh— only scans*Query*.cs/*ReadPort*.csscripting_write_path_cqrs_guard.sh— only checks 4 specific files inAevatar.Scripting.*CqrsBoundaryTests.ScriptingWritePath_ShouldNot_DependOn_AuthorityActivationPorts— only coversAevatar.Scripting.(Application|Infrastructure)namespaces
ScopeScriptCommandApplicationServicesits insrc/platform/Aevatar.GAgentService.Application/Scripts/, outside all three guard perimeters. If this pattern is accepted, the guards must be extended to cover the platform layer as well. -
Precedent risk — This creates a "platform layer exception" that could spread. Any upper-layer command service could make the same argument for pre-dispatch activation.
Suggested Alternatives
The underlying issue (observation endpoint returning Pending forever because projection never starts) is real, but the fix should be on the read-side:
-
Option A: Activate in
ScopeScriptSaveObservationService.ObserveAsync(the read/observation path) — on first observation attempt, ensure the projection scope exists, then poll. This keeps activation on the query side where the design document says it belongs. -
Option B: Use a decorator/interceptor on the observation port that lazily bootstraps the projection on first read, following the "read-side activation / lease 入口" pattern.
-
Option C: If activation truly belongs at the application service level, it should be in a dedicated read-side bootstrap service, not in the command dispatch method.
Minor Notes
- The integration test (
ScopeScriptSaveObservationRuntimeTests) is well-structured and the polling approach is reasonable for eventual consistency validation. - The unit tests for execution order are good.
- The
test_polling_allowlist.txtentry is appropriate.
eanzhao
left a comment
There was a problem hiding this comment.
Approved. Clean, focused fix with solid test coverage.
What I verified
- Architecture compliance: activation in command path (not query path), no projection priming violation, no intermediate-layer state maps. ✅
- DI registration chain:
IScriptAuthorityReadModelActivationPortflows fromAddScriptCapability→ Scripting.Projection DI →ScriptAuthorityProjectionPort. ✅ - Test coverage: unit test verifies activation ordering (execution log), integration test verifies end-to-end observability through real in-memory projection. ✅
- Polling allowlist: test file properly documented and added to
test_polling_allowlist.txt. ✅
One note (non-blocking)
The two ActivateAsync calls in ScopeScriptCommandApplicationService.UpsertAsync are sequential. If the first (definition actor) succeeds and the second (catalog actor) throws, the definition actor's projection session lingers without a corresponding command dispatch. In practice the idle release mechanism should clean it up, but worth being aware of. A future improvement could consider wrapping both in a try/catch with explicit cleanup, or using Task.WhenAll if the activation service is concurrency-safe.
await _authorityReadModelActivationPort.ActivateAsync(definitionActorId, ct);
await _authorityReadModelActivationPort.ActivateAsync(catalogActorId, ct);
Summary
Issue
Fixes #488.
Validation
dotnet test test/Aevatar.GAgentService.Tests/Aevatar.GAgentService.Tests.csproj --nologo --filter ScopeScriptCommandApplicationServiceTests— 7 passeddotnet test test/Aevatar.GAgentService.Integration.Tests/Aevatar.GAgentService.Integration.Tests.csproj --nologo --filter "ScopeScriptApplicationServicesTests|ScopeScriptSaveObservationRuntimeTests"— 8 passedbash tools/ci/test_stability_guards.sh— passedbash tools/ci/query_projection_priming_guard.sh— passedNotes
tools/ci/test_polling_allowlist.txt.