id: cluster-006-command-path-projection-lifecycle
rule_ids: [R-WRITE-OBSERVE-001, R-PROJECTION-ACTOR-001]
severity: medium
requires_design: true
files_touched_estimate: 8-20
old_pattern: "Command/application services call EnsureProjection/ActivateProjection inline before or around writes."
new_pattern: "Write path dispatches command/event only; projection activation is explicit setup, actor-owned binding, or background materializer outside command request stack."
Evidence:
src/workflow/Aevatar.Workflow.Infrastructure/Runs/WorkflowRunActorPort.cs:75-76: activates binding and materialization during run creation.
src/platform/Aevatar.GAgentService.Application/Services/ServiceCommandApplicationService.cs:47 and later: command methods ensure read projections.
src/platform/Aevatar.GAgentService.Governance.Application/Services/ServiceGovernanceCommandApplicationService.cs:34: governance command primes projection.
src/platform/Aevatar.GAgentService.Infrastructure/Adapters/LlmSessionRegistrationAdapter.cs:49: registration adapter ensures projection after creating actor.
Fix boundary:
- Separate command/write lifecycle from projection activation.
- Keep projection startup services or explicit activation APIs if they are outside query/write request paths.
- Add code comment in command services:
New pattern: command path emits facts; readmodel visibility is observed asynchronously.
human_brief:
problem_title: "写路径顺手启动读模型投影"
problem_statement: |
多个 command/application service 在创建或修改 actor 时,会同步调用 EnsureProjection 或 ActivateAsync。这让一次写请求同时承担写侧命令和读侧物化准备,调用方很容易误以为返回后 readmodel 已经可见。开发者会关心这个问题,因为这会模糊 ACK 语义,也让 query/readmodel 的生命周期散落在各个业务入口。
problem_example_file_path: "src/workflow/Aevatar.Workflow.Infrastructure/Runs/WorkflowRunActorPort.cs:67-82"
problem_example_code: |
runActor = await _runtime.CreateAsync(
BuildRunActorId(definitionResolution.ActorId),
ct: ct);
createdActorIds.Add(runActor.Id);
if (!string.IsNullOrWhiteSpace(definitionResolution.ActorId))
await _runtime.LinkAsync(definitionResolution.ActorId, runActor.Id, ct);
await EnsureBindingProjectionAsync(runActor.Id, ct); // ← problem: 写路径同步启动 binding projection
await EnsureExecutionMaterializationAsync(runActor.Id, ct); // ← problem: 写路径同步启动 materialization
await _dispatchPort.DispatchAsync(
runActor.Id,
CreateWorkflowRunBindEnvelope(
definitionResolution.ActorId,
runActor.Id,
definition.WorkflowYaml,
why_needs_design: |
需要决定 projection activation 是资源创建的一部分、后台 materializer 的职责,还是由显式 lease/binder API 管理。不同能力现在各自调用 ensure,统一迁移会影响 ACK 文案、readmodel freshness、启动服务和测试契约。
design_question: "哪些 projection 必须在 actor 创建前预绑定,哪些可以完全交给后台 materializer?请给出统一生命周期规则。"
original_authors:
- "@eanzhao"
- "@loning"
- "@auric"
📢 cc 原作者
决策需要(进 Phase 9 design 共识)
本 issue 由 iter18 audit 标 requires_design=true 自动开。Auto-loop 已加 phase9-auto-solve label,3 solver(minimal/structural/delete)+ meta-judge 自动启动,等 3/3 unanimous 即派 implement。
Maintainer 可任意评论参与;每条评论触发 fresh solver round。
🤖 Auto-loop iter18 / codex-refactor-loop
Evidence:
src/workflow/Aevatar.Workflow.Infrastructure/Runs/WorkflowRunActorPort.cs:75-76: activates binding and materialization during run creation.src/platform/Aevatar.GAgentService.Application/Services/ServiceCommandApplicationService.cs:47and later: command methods ensure read projections.src/platform/Aevatar.GAgentService.Governance.Application/Services/ServiceGovernanceCommandApplicationService.cs:34: governance command primes projection.src/platform/Aevatar.GAgentService.Infrastructure/Adapters/LlmSessionRegistrationAdapter.cs:49: registration adapter ensures projection after creating actor.Fix boundary:
New pattern: command path emits facts; readmodel visibility is observed asynchronously.human_brief:
problem_title: "写路径顺手启动读模型投影"
problem_statement: |
多个 command/application service 在创建或修改 actor 时,会同步调用
EnsureProjection或ActivateAsync。这让一次写请求同时承担写侧命令和读侧物化准备,调用方很容易误以为返回后 readmodel 已经可见。开发者会关心这个问题,因为这会模糊 ACK 语义,也让 query/readmodel 的生命周期散落在各个业务入口。problem_example_file_path: "src/workflow/Aevatar.Workflow.Infrastructure/Runs/WorkflowRunActorPort.cs:67-82"
problem_example_code: |
runActor = await _runtime.CreateAsync(
BuildRunActorId(definitionResolution.ActorId),
ct: ct);
createdActorIds.Add(runActor.Id);
if (!string.IsNullOrWhiteSpace(definitionResolution.ActorId))
await _runtime.LinkAsync(definitionResolution.ActorId, runActor.Id, ct);
await EnsureBindingProjectionAsync(runActor.Id, ct); // ← problem: 写路径同步启动 binding projection
await EnsureExecutionMaterializationAsync(runActor.Id, ct); // ← problem: 写路径同步启动 materialization
why_needs_design: |
需要决定 projection activation 是资源创建的一部分、后台 materializer 的职责,还是由显式 lease/binder API 管理。不同能力现在各自调用 ensure,统一迁移会影响 ACK 文案、readmodel freshness、启动服务和测试契约。
design_question: "哪些 projection 必须在 actor 创建前预绑定,哪些可以完全交给后台 materializer?请给出统一生命周期规则。"
original_authors:
- "@eanzhao"
- "@loning"
- "@auric"
📢 cc 原作者
决策需要(进 Phase 9 design 共识)
本 issue 由 iter18 audit 标 requires_design=true 自动开。Auto-loop 已加 phase9-auto-solve label,3 solver(minimal/structural/delete)+ meta-judge 自动启动,等 3/3 unanimous 即派 implement。
Maintainer 可任意评论参与;每条评论触发 fresh solver round。
🤖 Auto-loop iter18 / codex-refactor-loop