# 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["本域权威事实
Canonical 或 Shadow terminal fact"] Outbox["本域 publish outbox
queued/running/retryable/published/dead_letter"] Worker["本域 worker
claim + lease + retry"] Fact --> Outbox --> Worker end Worker --> Api["EventsPublishApi
events-api only"] subgraph Events["Events Owner"] Api --> Publish["幂等发布服务
commandId + source tuple"] Publish --> Store[("muse_unified_event
accepted/rejected/blocked")] Store --> Stream["SSE stream service
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` | 稳定、短、幂等;推荐 `_evt:`,长度不超过 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 任务。