Evidence:
- `src/Aevatar.Studio.Application/Studio/Abstractions/IWorkflowDraftStore.cs:5-10`: interface comment says drafts are authoritative editor state.
- `src/Aevatar.Studio.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs:59`: authoritative draft port is bound to `ChronoStorageWorkflowDraftStore`.
- `src/Aevatar.Studio.Infrastructure/Storage/ChronoStorageWorkflowDraftStore.cs:20-40`: save writes `ScopedWorkflowDraftFact` directly to blob storage.
- `src/Aevatar.Studio.Infrastructure/Storage/ChronoStorageWorkflowDraftStore.cs:71-91`: get reads protobuf bytes directly from blob storage and returns the draft.
- `src/Aevatar.Studio.Application/AppScopedWorkflowService.cs:136-150`: application service reads existing draft and saves through the store in the write path.
- `src/Aevatar.Studio.Application/AppScopedWorkflowService.cs:399-406`: list path groups direct store results into query response.
- `src/Aevatar.Studio.Application/AppScopedWorkflowService.cs:432`: get path reads direct store result.
- Contrast path: `src/Aevatar.Studio.Application/Studio/Services/WorkspaceService.cs` and `src/Aevatar.Studio.Infrastructure/ActorBacked/ActorDispatchStudioWorkspaceCommandPort.cs` show actor-backed workspace draft command flow already exists for the non-scoped path.
Fix boundary:
- Do not modify ChronoStorage or require a new external endpoint; `../chrono-storage` was not present and AGENTS forbids relying on external product changes.
- Keep protobuf payloads if storage remains an implementation detail; serialization format is not the violation.
- Move authoritative scoped draft writes behind an actor-owned workspace/scope command contract, then expose query results via readmodel/projection.
- Preserve current Studio editor UX and draft DTOs; cluster is about authority and read/write boundary, not adding editor features.
human_brief:
problem_title: "Studio scoped draft 把对象存储当成了业务真相"
problem_statement: |
Scoped workflow draft 被接口注释定义为“编辑器里的权威草稿状态”,但真正的保存和读取直接落到 ChronoStorage blob。这样业务事实没有 actor 拥有,也没有通过 command/event/readmodel 链路进入系统的统一状态模型。以后要做版本、并发、回滚、权限审计或多节点一致性时,这条路径会绕过已有的 workspace actor 规则。
problem_example_file_path: "src/Aevatar.Studio.Infrastructure/Storage/ChronoStorageWorkflowDraftStore.cs:20-41"
problem_example_code: |
public async Task SaveDraftAsync(
string scopeId,
string workflowId,
string workflowName,
string yaml,
WorkflowLayoutDocument? layout,
CancellationToken ct)
{
var normalizedWorkflowId = NormalizeRequired(workflowId, nameof(workflowId));
var context = ResolveWorkflowContext(scopeId, $"{WorkflowDirectory}/{normalizedWorkflowId}.yaml");
if (context == null)
throw new InvalidOperationException("Scoped workflow draft storage is not enabled.");
var fact = new ScopedWorkflowDraftFact // ← problem: 业务事实在应用侧直接组装为存储 payload
{
WorkflowId = normalizedWorkflowId,
WorkflowName = workflowName,
Yaml = yaml,
Layout = layout is null ? null : ToProtoLayout(layout),
};
await _blobClient.UploadAsync(context, fact.ToByteArray(), "application/x-protobuf", ct); // ← problem: 保存直接写 blob,而不是 actor command/event
}
why_needs_design: |
修复需要决定 scoped draft 是否复用现有 Studio workspace actor,还是新建 scope-workspace actor。还要决定草稿 readmodel 的版本、水位和与 publish flow 的关系;这些都是公共业务契约,不适合用机械替换完成。
design_question: "Scoped workflow draft 应该归现有 workspace actor 管,还是为每个 scope 建一个独立 draft/workspace actor?"
original_authors:
- "@loning"
- "@AbigailDeng"
- "@eanzhao"
- "@potter-sun"
```yaml
## 待 maintainer 决策
请评论或加 `auto-loop-resume` label。不评论则 auto-loop 在 phase9-auto-solve label 下走 3 solver consensus。
📢 cc 原作者: @loning
⟦AI:AUTO-LOOP⟧
Cluster
id: cluster-038-studio-scoped-workflow-drafts-storage-authority
rule_ids:
severity: high
requires_design: true
files_touched_estimate: 5-10
old_pattern: "Scoped workflow drafts are declared authoritative editor state but saved and queried directly through a ChronoStorage blob adapter."
new_pattern: "Scoped workflow drafts are actor-owned workspace facts; storage is a provider behind actor commit/projection, and queries read the materialized draft readmodel."