310 lines
18 KiB
Markdown
310 lines
18 KiB
Markdown
# P1R7 Source Owner Propagation 全局审阅版
|
||
|
||
## 结论
|
||
|
||
推荐把 P1R-7 后续主线从“AI 单 owner 先做”提升为“全局 source owner propagation 分层方案”,但实现仍必须按 owner 分阶段切片推进。
|
||
|
||
全局推荐方案:
|
||
|
||
1. Events owner 只负责统一事件投影、幂等、payload 校验、脱敏、可见性过滤和 SSE,不反向读取任何 source owner 业务表。
|
||
2. 每个 source owner 只负责本域权威事实和本域 publish outbox;worker 通过 `muse-module-events-api` 调用 `EventsPublishApi`。
|
||
3. 不建立一个跨 owner 的中心 source 模块,也不让 Events server 直接拥有 source propagation 业务事实。
|
||
4. 当前 AI 第一切片实现草稿不能直接作为全局方案完成证据;它只能作为后续 P1R-7b 的候选实现素材,必须先按本全局方案复审和修订。
|
||
|
||
P1R-7 后续拆分建议:
|
||
|
||
| 阶段 | 目标 | 状态口径 |
|
||
|---|---|---|
|
||
| P1R-7b | 全局 propagation 规则冻结 + AI terminal event 第一 owner 切片 | 只推进 `needs_verification` 证据 |
|
||
| P1R-7c | Knowledge source status / projection event propagation | 只推进 `needs_verification` 证据 |
|
||
| P1R-7d | Market lifecycle / account projection / governance event propagation | 只推进 `needs_verification` 证据 |
|
||
| P1R-7e | Account(Member) security / entitlement / usage notification propagation | 只推进 `needs_verification` 证据 |
|
||
| P1R-7f | Content canonical change / block saved / export task event propagation | 只推进 `needs_verification` 证据 |
|
||
| P1R-7 completed approval | 对 P1R-7 做单独完成审批 | 必须用户单独批准 |
|
||
|
||
本审阅版不实现代码、不修改 OpenAPI、不修改 scanner、不修改 coverage JSON/Markdown,不把 Events、P1R-7、Market 或任何 owner 标为 `completed`。
|
||
|
||
## 已验证事实
|
||
|
||
### 工作区与状态
|
||
|
||
- 正确工作区为 `/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0`。
|
||
- 当前分支为 `dev/1.0.0`,`git pull --ff-only origin dev/1.0.0` 输出 `Already up to date.`。
|
||
- 当前 HEAD 为 `fa29753 test(p1r): 收口 Events SSE 门禁`。
|
||
- 当前 worktree 有未提交的 P1R-7b AI outbox 草稿、V17 migration 草稿和 `docs/agent-specs/` 文档草稿;本审阅版不清理、不回退这些文件。
|
||
- 受保护文件当前定向 `git status --short` 无输出:
|
||
- `docs/api-contracts/market/openapi.yaml`
|
||
- `docs/api-contracts/ai/openapi.yaml`
|
||
- `docs/api-contracts/knowledge/openapi.yaml`
|
||
- `docs/api-contracts/events/openapi.yaml`
|
||
- `muse-cloud/scripts/p1r-audit-api-coverage.py`
|
||
- `docs/superpowers/reports/p1r-api-coverage.json`
|
||
- `docs/superpowers/reports/p1r-api-coverage.md`
|
||
|
||
### Coverage 与阶段边界
|
||
|
||
- 当前 coverage summary:
|
||
- `totalOperations = 233`
|
||
- `completedOperations = 100`
|
||
- `needsVerificationOperations = 133`
|
||
- `incompleteOperations = 0`
|
||
- `genericPersistenceOperations = 0`
|
||
- `ssePlaceholderOperations = 0`
|
||
- Events 当前唯一 operation 为 `streamEvents GET /app-api/muse/events = dedicated / needs_verification`。
|
||
- Market 当前 32 operations 均为 `dedicated / needs_verification`。
|
||
- `docs/memorys/2026-06-05-P1R7aEventsSSE真实API收口.md` 明确 P1R-7a 未接入 source owner propagation,未运行 source owner publish tests。
|
||
|
||
### 已有全局设计约束
|
||
|
||
- `CLAUDE.md` 明确本仓是设计文档 SSOT,核心架构决策中 Source 传播采用“事件驱动 + 各模块自治”,统一实时通信为 SSE。
|
||
- `design-docs/架构-02-核心数据结构与双轨模型.md` 明确:来源 owner 发出状态变化事件,各消费模块监听事件并自行处理反应逻辑,不需要独立 source 模块。
|
||
- `design-docs/后端-03-关键流程实现与接口契约.md` 明确:AI 调用、投影、索引、导出、评估、来源传播、New-API 归属和通知走异步任务,异步失败不得回滚已提交 Canonical。
|
||
- `docs/superpowers/plans/2026-06-05-P1R-7a-events-sse.md` 明确:跨 owner 发布模式固定为 source owner 本域 outbox + worker 调用 Events API;Events server 不允许反向依赖 source owner server,也不允许统一 SSE Controller 直接扫多域业务表。
|
||
|
||
### Events owner 当前能力
|
||
|
||
- `EventsPublishApi.publish(EventsPublishReqDTO)` 已存在于 `muse-module-events-api`。
|
||
- `EventsPublishReqDTO` 已包含 `commandId`、`tenantId`、`ownerUserId`、`sourceOwner`、`sourceType`、`sourceId`、`sourceRevision`、`eventType`、`resourceType`、`resourceId`、`payloadSummary`、`emittedAt`。
|
||
- `EventsPublishServiceImpl` 已按 commandId 优先、source tuple 次级做幂等回查。
|
||
- `EventsPublishServiceImpl` 使用 `insertIgnore`,避免用唯一约束异常作为正常幂等路径。
|
||
- `UnifiedEventMapper.selectVisibleEventsForOwner` 只查询 `publish_status = accepted`、同租户、同 ownerUserId、未删除、`sequenceNo` 大于 cursor 的事件。
|
||
- `EventsStreamServiceImpl` 将统一投影中的 `done` / `error` / `notification` 等事件转成 SSE。
|
||
|
||
### Source owner 当前能力差异
|
||
|
||
- AI 已有 `muse_ai_task_event` terminal fact、`MuseAiRuntimeJobDispatcher`、`MuseAiJobMapper.claimNextQueuedRuntimeJob()` 和 `FOR UPDATE SKIP LOCKED` worker 风格;当前未提交草稿新增了 AI event publish outbox,但尚未按全局方案完成复审。
|
||
- Knowledge 已有 `muse_knowledge_source_event` 和 `muse_knowledge_projection_task`;当前 `MuseKnowledgeSourceEventService` 同步更新 projection 后把 task 记录为 `completed`,注释明确避免制造无执行者的 queued task。
|
||
- Market 已有 `MarketAccountProjectionOutboxService`,但接口只有 `recordSynced` / `recordBlocked`,职责是 Market 到 Member Account projection 的同步状态,不是 Events publish worker。
|
||
- Member(Account) 已有 `MemberSecurityEventDO`、`AccountIntegrationCallDO`、调用归因 job 和安全事件脱敏转换;当前 mapper 以查询/归因记录为主,未发现完整 Events publish outbox claim/retry/dead-letter 链路。
|
||
- Content 正式设计中已有 `BlockSavedEvent` outbox 方向,但本次只读未验证当前 Content 已有可直接复用到 Events 的 publish worker。
|
||
|
||
## 推断
|
||
|
||
- 全局 source propagation 的核心不是新增一个“大 source 服务”,而是冻结所有 owner 都必须遵守的事件发布合同、幂等合同、失败补偿合同和审计合同。
|
||
- 直接把当前 AI outbox 草稿继续推进,容易把“第一切片实现细节”误当成全局架构决策;应先用全局审阅版统一约束,再修订 AI 执行版。
|
||
- Market 的 projection outbox 名称容易误导;它当前表达的是 Market -> Account 读模型同步状态,不适合直接复用为 Events publish outbox。
|
||
- Knowledge 已有 source event,但目前 projection task 同步完成,缺少异步 publish worker;进入 P1R-7c 前需要先补本域发布补偿语义。
|
||
- Account security event 适合做用户通知类 propagation,但必须先冻结安全 payload allowlist,避免把设备、IP、风控原始详情或内部判断泄露到统一 SSE。
|
||
- Content 的 BlockSavedEvent 更接近内部 followup trigger,不一定都应该进入用户 SSE;需要先区分“内部领域事件”和“用户可见统一事件”。
|
||
|
||
## 假设
|
||
|
||
- P1R-7 后续允许在各 source owner 内新增 publish outbox 表或等价本域状态表。
|
||
- P1R-7 后续允许在 source owner server 中新增 `muse-module-events-api` 依赖,但不允许依赖 `muse-module-events-server`。
|
||
- 当前 `EventsPublishReqDTO` envelope 足够承载后续 owner 的用户可见事件;如果后续发现字段不足,应另起 OpenAPI / contract 变更审批,而不是在实现里绕过。
|
||
- 当前 P1R-7 完成状态仍需用户单独批准;本方案只定义如何补证据链。
|
||
|
||
## 全局目标
|
||
|
||
1. 建立跨 owner 的统一 source propagation 规则,保证每个 owner 都按相同可审核链路发布事件。
|
||
2. 保持 source owner 自治:事实归 owner,发布补偿归 owner,统一可见投影归 Events。
|
||
3. 让 `/app-api/muse/events` SSE 成为用户可见事件的统一出口,而不是跨表扫描器。
|
||
4. 为 P1R-7 completed approval 准备真实证据:成功路径、幂等、失败、重试、dead-letter、权限、审计、数据库落库和 SSE 可见性。
|
||
|
||
本文只定义“用户可见 Events 发布链路”。它不替代正式 Source / Authorization 内部传播链路;内部来源状态、授权快照、传播到目标 owner 的阻断和禁用仍由正式设计中的 Source Status Event / Source Propagation Job 或等价本域机制承担。
|
||
|
||
## 非目标
|
||
|
||
1. 不建立独立 source 模块。
|
||
2. 不把 Events server 改造成 source owner 编排器。
|
||
3. 不让 Events server 反向依赖 AI / Knowledge / Market / Member / Content server。
|
||
4. 不把所有内部领域事件都暴露给 SSE。
|
||
5. 不复用职责不匹配的 Market Account projection outbox。
|
||
6. 不用修改 OpenAPI、scanner 或 coverage 报告掩盖缺口。
|
||
7. 不把当前 P1R-7b AI 草稿视为已完成实现。
|
||
8. 不推进 completed 状态。
|
||
|
||
## 推荐架构
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Owner["每个 Source Owner"]
|
||
Fact["本域权威事实<br/>Canonical 或 Shadow terminal fact"]
|
||
Outbox["本域 publish outbox<br/>queued/running/retryable/published/dead_letter"]
|
||
Worker["本域 worker<br/>claim + lease + retry"]
|
||
Fact --> Outbox --> Worker
|
||
end
|
||
|
||
Worker --> Api["EventsPublishApi<br/>events-api only"]
|
||
|
||
subgraph Events["Events Owner"]
|
||
Api --> Publish["幂等发布服务<br/>commandId + source tuple"]
|
||
Publish --> Store[("muse_unified_event<br/>accepted/rejected/blocked")]
|
||
Store --> Stream["SSE stream service<br/>tenant + owner + sequence"]
|
||
end
|
||
|
||
Stream --> Client["muse-studio fetch SSE"]
|
||
```
|
||
|
||
关键原则:
|
||
|
||
1. Source owner 只发布已经落库的、可审计的本域事实,不发布临时内存状态。
|
||
2. Source owner outbox 与本域事实绑定,但不能因为 Events 暂时不可用回滚已提交 Canonical。
|
||
3. Worker 不拼接自由 payload,只从 outbox 固化的 allowlist payload 发布。
|
||
4. Events owner 只校验和投影,不反查 source owner 表补上下文。
|
||
5. SSE 只读 `muse_unified_event` accepted 事件,不直接查询 AI / Knowledge / Market / Account / Content 表。
|
||
|
||
## 全局发布合同
|
||
|
||
每个 owner 的发布请求必须满足:
|
||
|
||
| 字段 | 全局规则 |
|
||
|---|---|
|
||
| `commandId` | 稳定、短、幂等;推荐 `<owner>_evt:<sha256-32>`,长度不超过 128 |
|
||
| `tenantId` | 必填;worker 线程必须恢复租户上下文 |
|
||
| `ownerUserId` | 必填;决定 SSE 可见性 |
|
||
| `sourceOwner` | 固定 owner 枚举,如 `ai`、`knowledge`、`market`、`account`、`content` |
|
||
| `sourceType` | 固定业务事实类型,不使用表名泄露内部结构 |
|
||
| `sourceId` | 本域事实稳定 id 或业务 id |
|
||
| `sourceRevision` | 本域事实版本;没有版本时使用明确占位,但 owner 执行版必须解释 |
|
||
| `eventType` | 只能使用 Events OpenAPI 已声明类型;新增类型必须另起合同审批 |
|
||
| `resourceType/resourceId` | 用户界面定位对象;不得代替 source tuple 幂等 |
|
||
| `payloadSummary` | 只包含 OpenAPI schema 允许字段和安全摘要 |
|
||
| `emittedAt` | 本域事实发生时间,不是 worker 发布时间 |
|
||
|
||
payload 全局禁令:
|
||
|
||
1. 不发布 prompt、provider request/response raw body、完整正文、知识资料原文、授权头、token、apiKey、secret、password。
|
||
2. 不发布内部风控原始规则、管理员备注原文、失败堆栈、数据库错误详情。
|
||
3. 不发布未在 OpenAPI 中声明的扩展字段。
|
||
4. 不把 `rejected` 或 `blocked` 事件作为 SSE 可见事件。
|
||
|
||
## 全局 outbox 状态机
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> queued: owner fact 已落库并创建 publish intent
|
||
queued --> running: worker claim + lease
|
||
retryable --> running: retry 到期 claim
|
||
running --> running: stale lease reclaim
|
||
running --> published: Events accepted 或 duplicate accepted
|
||
running --> retryable: temporary failure
|
||
running --> dead_letter: payload invalid / rejected / blocked / retry exhausted
|
||
retryable --> dead_letter: retry exhausted
|
||
published --> [*]
|
||
dead_letter --> [*]
|
||
```
|
||
|
||
全局要求:
|
||
|
||
1. claim 必须是原子 SQL,优先使用 `FOR UPDATE SKIP LOCKED` 或等价安全领取机制。
|
||
2. claim 必须包含 lease,避免 crash-after-claim 后永久 running。
|
||
3. attempt 只在 claim 时递增,失败处理不得二次递增。
|
||
4. 幂等插入必须优先使用 `ON CONFLICT DO NOTHING` / `insertIgnore` + 回查,不把唯一约束异常当正常路径;PostgreSQL 同事务异常可能让事务进入 aborted 状态。
|
||
5. `dead_letter` 不能自动 replay;只有确认是临时故障的记录才能被重置为 `retryable`。
|
||
6. 所有状态转换必须有中文日志,至少包含 owner、tenantId、ownerUserId、source tuple、outboxId、attempt 和错误摘要。
|
||
7. owner 执行版必须显式冻结最小字段词汇:`max_attempt`、`next_retry_at`、`claim_expires_at`、固定退避策略、claim 索引和依赖树验证命令。
|
||
|
||
## 用户可见事件判定准则
|
||
|
||
只有同时满足以下条件的事件才进入统一 SSE:
|
||
|
||
1. 用户需要实时感知该变化,且变化能影响当前用户下一步操作。
|
||
2. 事件 payload 可以被压缩成 OpenAPI 已声明 schema 内的安全摘要。
|
||
3. 事件不要求前端读取内部风控、授权、source propagation 或 worker 调度细节。
|
||
4. 事件 owner 能提供稳定 source tuple、幂等 commandId 和 ownerUserId。
|
||
|
||
默认不进入统一 SSE 的事件:
|
||
|
||
1. 只服务内部 followup trigger 的领域事件,例如 `BlockSavedEvent`。
|
||
2. projection rebuild、source propagation target、retry job、worker heartbeat 等内部任务状态。
|
||
3. 需要完整正文、知识资料、provider raw body、授权详情或安全风控原始证据才能解释的事件。
|
||
|
||
## 阶段化路线
|
||
|
||
### P1R-7b:全局规则冻结 + AI terminal event 第一切片
|
||
|
||
目标:
|
||
|
||
- 修订现有 AI 第一切片执行版,使其服从本全局方案。
|
||
- AI terminal `done/error` 通过本域 outbox + worker 发布到 Events。
|
||
- 修正当前草稿中可能存在的同事务唯一冲突异常风险,采用 `ON CONFLICT DO NOTHING` / insert-ignore + 回查。
|
||
- 证明 AI 只依赖 events-api,不依赖 events-server。
|
||
|
||
不做:
|
||
|
||
- 不把 AI 以外 owner 混入 P1R-7b 实现。
|
||
- 不标 completed。
|
||
|
||
### P1R-7c:Knowledge propagation
|
||
|
||
目标:
|
||
|
||
- 选择 Knowledge source status / projection summary 中最小用户可见事件。
|
||
- 在 Knowledge owner 内新增或复用 publish outbox 语义,补 claim/retry/dead-letter。
|
||
- 区分内部 projection task 和用户可见 Events notification。
|
||
|
||
不做:
|
||
|
||
- 不把同步完成的 projection task 伪装成异步 worker。
|
||
- 不发布知识资料原文。
|
||
|
||
### P1R-7d:Market propagation
|
||
|
||
目标:
|
||
|
||
- 覆盖 publish review、asset delist/recall、license/install/purchase 对用户可见状态的通知。
|
||
- 保留 Market Account projection outbox 的原职责,另建或扩展专门 Events publish outbox。
|
||
|
||
不做:
|
||
|
||
- 不把 Market 32 operations 标为 completed。
|
||
- 不让 Market 事件接管源作品、源智能体或源知识库事实 owner。
|
||
|
||
### P1R-7e:Account(Member) propagation
|
||
|
||
目标:
|
||
|
||
- 覆盖 security event、entitlement/quota notification、New-API attribution summary 中适合用户可见的事件。
|
||
- 复用既有安全事件脱敏转换,但新增统一 Events payload allowlist。
|
||
- 如果后续目标偏“用户通知”而不是“source status propagation”,Account security event 可以作为 Knowledge 之后的低风险 second slice 备选。
|
||
|
||
不做:
|
||
|
||
- 不发布设备风控原始细节、IP 内部判断、接口调用原始日志或成本原始 authority。
|
||
|
||
### P1R-7f:Content propagation
|
||
|
||
目标:
|
||
|
||
- 评估 BlockSavedEvent、export task、import task 中哪些应进入用户可见 SSE。
|
||
- 明确内部 followup trigger 与用户通知的分界。
|
||
|
||
不做:
|
||
|
||
- 不把正文保存的每次内部事件都推给 SSE。
|
||
- 不发布正文全文或 block content。
|
||
|
||
## 完成审批门槛
|
||
|
||
P1R-7 completed approval 必须另起任务,并至少具备:
|
||
|
||
1. Events owner 自身 gate:route ownership、OpenAPI schema、payload sanitizer、idempotency、SSE replay、frontend fetch SSE。
|
||
2. 至少覆盖 AI、Knowledge、Market、Account(Member)、Content 中被声明为 P1R source owner 的真实发布链路。
|
||
3. 每个 owner 都有成功、重复幂等、临时失败重试、retry exhausted、dead-letter、敏感 payload fail-closed、跨 owner 依赖方向测试。
|
||
4. PostgreSQL `_test` Flyway / migration 证据覆盖新增 outbox 表和 `muse_unified_event`。
|
||
5. HTTP / MockMvc / SSE 或等价 focused E2E 证据证明当前用户只能看到同 tenant + ownerUserId 的 accepted 事件。
|
||
6. 审计或日志证据证明事件发布链路可追踪、可排查、可回放。
|
||
7. 用户明确批准从 `needs_verification` 推进到 `completed`。
|
||
|
||
## 风险
|
||
|
||
- 抽象过早:如果现在先做共享 publish 库,可能在第二个 owner 前抽错接口;因此先冻结合同和测试模板,等 AI + Knowledge 两个 owner 通过后再抽小型 support module。
|
||
- 事务风险:同事务内 outbox 插入如果靠捕获唯一约束异常处理,PostgreSQL 可能使事务不可继续;实现必须使用 insert-ignore 或先锁定事实行。
|
||
- Payload 风险:不同 owner 的“摘要”含义不同,必须逐 owner allowlist,不允许泛化 Map 透传。
|
||
- 完成状态风险:source propagation evidence 增加不等于 P1R-7 completed;completed 必须另有审批。
|
||
- Dirty baseline 风险:当前 worktree 已有 AI 草稿,后续执行前必须决定是修订沿用、拆分提交,还是用户批准后重置对应草稿;本审阅版不处理。P1R-7b 执行版修订并双 review PASS 前,当前 AI outbox / V17 / docs 草稿不得继续测试、提交、推送或作为完成证据。
|
||
|
||
## 待确认项
|
||
|
||
1. 是否确认以本文作为新的 P1R-7 source owner propagation 全局主线,替代此前 AI-only 审阅版作为最高层方案。
|
||
2. 是否确认 P1R-7b 仍从 AI terminal event 第一切片落地,但必须先修订执行版以符合本文全局规则。
|
||
3. 是否确认 completed approval 另起任务,当前所有阶段只推进 `needs_verification` 证据。
|
||
|
||
## 下一步
|
||
|
||
1. 对本文进行 fresh spec compliance review。
|
||
2. 对本文进行 fresh quality / feasibility review。
|
||
3. 双 PASS 后修订 `docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation执行版.md`,把 AI 第一切片执行版降级为全局方案下的 P1R-7b 子计划。
|
||
4. P1R-7b 子计划双 review PASS 后,才允许处理当前 AI 草稿实现和后续 worker 任务。
|