oh-my-muse/docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation执行版.md

81 KiB
Raw Blame History

P1R7b Source Owner Propagation 执行版

结论

P1R-7b 是 docs/agent-specs/2026-06-06-P1R7SourceOwnerPropagation全局执行版.md 下的 AI terminal event 第一切片子计划。本文件只修订计划,不实现代码,不运行测试,不提交,不推送,不推进 Events / P1R-7 / Market completed approval。

当前 AI 草稿实现只能作为“修订沿用候选”,不能作为已通过、已完成或可继续实现的事实。必须先让本子计划通过 fresh spec review + fresh quality / feasibility review 双 PASS才允许处理当前 AI 草稿实现;双 PASS 前不得继续 AI 草稿测试、提交、推送或把任何 dirty baseline 清理成“完成状态”。

执行主线固定为:

flowchart LR
    Fact["AI owner 本域事实<br/>muse_ai_task_event done/error"] --> Outbox["AI publish outbox/job<br/>muse_ai_event_publish_outbox"]
    Outbox --> Worker["AI publish worker<br/>claim + retry + dead_letter"]
    Worker --> Api["EventsPublishApi<br/>只依赖 muse-module-events-api"]
    Api --> Store[("muse_unified_event<br/>accepted/rejected/blocked")]
    Store --> Query["Events visible query<br/>tenant + ownerUserId + sequence"]
    Query --> SSE["/app-api/muse/events<br/>SSE 可见"]

执行版完成后仍只是计划;必须经过 fresh spec review + fresh quality review 双 PASS才允许进入实现。实现完成后也只推进 source propagation evidence / needs_verification不自动把 Events、P1R-7 或 Market 32 operations 标为 completed

本轮计划修订的硬边界:

  1. 只允许修改本文件和 docs/agent-specs/.agent
  2. 不修改任何 Java / SQL / test / pom.xml 文件。
  3. 不修改 OpenAPI、scanner、coverage JSON/Markdown。
  4. 不清理、不回退、不废弃当前 dirty baseline任何废弃草稿动作都必须等用户明确批准。
  5. 不引入独立 source 模块,也不把 shared publish 库作为当前 P1R-7b 必做项。

目标与范围边界

目标:

  1. 在全局 P1R7 Source Owner Propagation 执行版约束下,以 AI task terminal event 为第一切片,证明真实 owner 事实可以传播到统一 Events 投影。
  2. 只发布用户可见 terminal done / error 摘要事件payload 严格匹配 docs/api-contracts/events/openapi.yaml 现有 schema。
  3. 新增 AI 本域 publish outbox/job异步 worker 调用 EventsPublishApi
  4. 建立可验证链路AI owner 本域事实 -> AI publish outbox/job -> AI worker -> EventsPublishApi -> muse_unified_event -> /app-api/muse/events SSE 可见。
  5. 形成 TDD、focused tests、migration tests、dependency gate、coverage gate、review gate 的可执行实施计划。

范围边界:

  1. 只在 /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 工作。
  2. 当前分支必须保持 dev/1.0.0
  3. 当前 dirty baseline 已包含 AI 草稿代码、迁移、测试和 docs/agent-specs/ 文档草稿;本子计划只记录处置策略,不清理、回退或改写 dirty/untracked baseline。
  4. 不在 /Users/qingse/Sync/local-git/oh-my-muse 写任何文件。
  5. 不修改受保护文件:
    • 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

非目标

  1. 不实现代码;本文件只定义执行计划。
  2. 不修改任何 OpenAPI 合同。
  3. 不修改 scanner 或 coverage JSON/Markdown 来达成 coverage。
  4. 不把 Events、P1R-7、Market 32 operations 标为 completed
  5. 不复用 AI runtime job 作为 Events publish 队列。
  6. 不复用 Market projection outbox。
  7. 不同时改造 Knowledge、Market、Member(Account) 的 source owner propagation。
  8. 不让 Events server 反向依赖 AI / Knowledge / Market / Member / Content server。
  9. 不让统一 SSE Controller 扫描 AI 业务表。
  10. 不处理 Knowledge / Market / Account / Content 的 propagation 实现。
  11. 不新增独立 source 模块,不把 shared publish 库设为本切片必做前置。
  12. 不把 DuplicateKeyException 捕获作为正常幂等路径。

已验证事实

  1. git status --short 在正确 worktree 显示 AI P1R-7b 草稿代码、迁移、测试和 docs/agent-specs/ 文档草稿;这些 dirty/untracked 文件尚未通过本子计划 fresh review也尚未运行本子计划验证。
  2. git branch --show-current 输出 dev/1.0.0
  3. git log -1 --oneline 输出 fa29753 test(p1r): 收口 Events SSE 门禁
  4. AGENTS.md 只引用 @CLAUDE.mdCLAUDE.md 明确本仓是设计文档 SSOTSource 传播采用事件驱动 + 各模块自治,统一实时通信为 SSE。
  5. docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation审阅版.md 推荐 P1R-7b 只选 AI task terminal event 第一切片,done / error 为最小事件类型,不进入 completed approval。
  6. docs/memorys/2026-06-05-P1R7aEventsSSE真实API收口.md 记录 P1R-7a 未接入 source owner propagation未运行 source owner publish testsEvents 和 Market 仍不得写成 completed
  7. docs/superpowers/plans/2026-06-05-P1R-7a-events-sse.md 固定跨 owner 发布策略为 source owner 本域 outbox + worker 调用 Events APIEvents server 不反向拉取 source owner 数据。
  8. Coverage JSON 当前 summarytotal 233、completed 100、needsVerification 133、incomplete 0、genericPersistence 0、ssePlaceholder 0。
  9. Coverage operation 明细显示 events streamEvents GET /app-api/muse/events = dedicated / needs_verification / P1R-7 End-to-End Acceptance
  10. Coverage operation 明细显示 Market 32 个 operation 的 completionStatus 均为 needs_verification
  11. EventsPublishApi.publish(EventsPublishReqDTO) 已存在于 muse-cloud/muse-module-events/muse-module-events-api/src/main/java/cn/iocoder/muse/module/events/api/publish/EventsPublishApi.java
  12. EventsPublishReqDTO 字段包含 commandIdtenantIdownerUserIdsourceOwnersourceTypesourceIdsourceRevisioneventTyperesourceTyperesourceIdpayloadSummaryemittedAt
  13. EventsPublishServiceImpl 已实现 commandId 优先、source tuple 次级的幂等回查,非法 payload 写入 rejected 并不进入可见 SSE 查询。
  14. UnifiedEventMapper.selectVisibleEventsForOwner 只查询 publish_status = accepted、同租户、同 ownerUserId、未删除、sequenceNo 大于 cursor 的事件。
  15. EventsStreamServiceImpldone event 投影为 taskIdsuggestionId 和可选 summary;将 error event 投影为 codemessage、可选 detail、可选 retryable
  16. docs/api-contracts/events/openapi.yaml 已声明 SSEDoneEvent 必填 taskIdsuggestionId,可选 summarySSEErrorEvent 必填 codemessage,可选 detailretryable
  17. muse_ai_task_event 已由 V12 创建,字段包含 task_idsequence_noevent_typetask_statusowner_user_idactor_user_idjob_idcorrelation_idpayload_summaryerror_codeerror_messageemitted_at,并有 terminal 唯一约束 uk_muse_ai_task_event_terminal
  18. MuseAiTaskEventMapper.selectTerminalByTaskId 已限定 terminal event 为 done / error
  19. MuseAiRuntimeProjectionService.applyRuntimeResponse 成功终态写入 done task event非可重试失败终态写入 error task event可重试失败只让 job 回到 queued,不写 terminal event。
  20. MuseJobServiceImpl.appendTaskCancellationEventIfAbsent 在 cancel 路径写入 eventType = errortaskStatus = cancellederrorCode = AI_TASK_CANCELLEDerrorMessage = AI task cancelled,并且代码注释明确取消原因不能进入 SSE payload。
  21. MuseAiRuntimeJobDispatcher 采用 @Scheduled(initialDelayString=..., fixedDelayString=...) 调用 dispatchOnce() 的最小 worker 生命周期风格。
  22. MuseSourceEventServiceImpl.retrySourceEvent 只提交 source_event_retry job并有注释说明不能把来源传播伪造成完成。
  23. 当前 dirty muse-module-ai-server/pom.xml 已出现 muse-module-events-api 依赖;它仍只是 AI 草稿的一部分,必须在实现阶段重新通过 dependency tree 证明只依赖 events-api、不依赖 events-server。
  24. BaseDO 暴露 createTime/updateTime/creator/updater/deletedTenantBaseDO 继承 BaseDO 并新增 tenantId;项目 PostgreSQL DDL 约定字段名为 create_time/update_time
  25. muse-cloud/sql/muse/V1__init_content_schema.sqlupdate_updated_at_column() 更新的是 NEW.update_time
  26. 当前 muse-cloud/sql/muse 文件列表按数字排序最新已包含未提交草稿 V17__extend_ai_events_publish_outbox.sql;它不是已通过 migration 证据,后续必须在 P1R-7b 实现阶段决定修订沿用该 V17或经用户明确批准后废弃重做。
  27. 当前草稿的 MuseAiEventPublishOutboxServiceImpl 存在捕获 DuplicateKeyException 作为 outbox 创建幂等回查路径的实现痕迹;这违反全局执行版“ON CONFLICT DO NOTHING / insertIgnore + 回查”的幂等合同,必须修订。
  28. 当前受保护文件定向 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

当前 dirty 文件处置表

结论:以下 dirty baseline 不是完成证据。P1R-7b 子计划双 review PASS 前,所有 AI 草稿代码、迁移和测试都只能停留在候选或待修订状态;任何废弃、清理或回退都必须等用户明确批准。

path 处置策略 理由 是否需要用户批准
muse-cloud/muse-module-ai/muse-module-ai-server/pom.xml 修订沿用候选 可能需要新增 muse-module-events-api 依赖,但必须重新证明只依赖 api、不依赖 server。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.java 修订沿用候选 可能承接 terminal fact 同事务创建 outbox但必须重新验证事务边界、payload allowlist 和非 terminal 不发布。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceImpl.java 修订沿用候选 cancellation terminal error 可作为用户可见摘要候选,但必须保证取消原因不进入 SSE payload。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionServiceTest.java 修订沿用候选 可作为 terminal fact/outbox 绑定测试素材,但必须补本全局合同断言。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceTest.java 修订沿用候选 可作为 cancellation error 安全 payload 测试素材,但不能替代 fresh review。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxService.java 修订沿用候选 AI 本域 publish outbox service 职责符合方向,但接口必须复核是否只覆盖 done/error terminal 摘要。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceImpl.java 必须修订 当前草稿捕获 DuplicateKeyException 作为正常幂等路径P1R-7b 必须改为 ON CONFLICT DO NOTHING / insertIgnore + 回查,避免 PostgreSQL 同事务异常导致 terminal fact 回滚风险。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/dataobject/muse/MuseAiEventPublishOutboxDO.java 修订沿用候选 DO 可承接 AI outbox 表,但字段必须与全局 outbox 合同、BaseDO/TenantBaseDO 字段约定和 V17 migration 一致。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapper.java 必须修订 mapper 必须提供 insertIgnore 或等价 ON CONFLICT DO NOTHING,并用原子 claim 支持 queued/retryable/stale running。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceTest.java 必须修订 现有测试如断言 DuplicateKeyException 幂等路径,必须改为 insertIgnore 返回 0 + 回查;否则会固化错误实现。 否;废弃或回退需要
muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapperTest.java 修订沿用候选 可承接 mapper claim / insertIgnore SQL 断言,但必须覆盖全局 outbox 状态机。 否;废弃或回退需要
muse-cloud/sql/muse/V17__extend_ai_events_publish_outbox.sql 修订沿用候选 V17 版本号需在实现前重新 live 查证;表结构必须对齐 outbox 合同、claim 索引和 update_time trigger。 否;废弃或回退需要
muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishMigrationSqlTest.java 修订沿用候选 可作为 migration SQL 文本门禁,但必须补 V17/字段/索引/trigger/独立测试库保护断言。 否;废弃或回退需要
muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishFlywayMigrationIT.java 修订沿用候选 可作为 Flyway _test 验证素材,但必须证明只跑独立 _test 库且 surefire XML 有真实 test count。 否;废弃或回退需要
docs/agent-specs/* 保持不动 本轮只允许修订本执行版和 .agent;其他审阅版/全局执行版/草稿不能被顺手改写。 否;废弃或删除需要

废弃规则:

  1. 上表任何代码、迁移、测试草稿如果后续选择“废弃重做”,必须先向用户说明废弃范围、原因、替代方案和回滚影响,并取得明确批准。
  2. 未获批准前,不得删除 untracked 文件,不得 git checkout -- 回退已修改文件,不得用清理 dirty baseline 的方式制造“计划已收口”的假象。

推断

  1. AI 是 P1R-7b 的最小 owner 切片,因为它已有 muse_ai_task_event 终态事实、ownerUserId、tenantId、sequence/replay 基础和 focused tests。
  2. 需要新增清晰职责的 AI publish outbox/job因为现有 AI runtime job 的职责是调用 New-API runtime不是统一 Events 发布补偿。
  3. done payload 不能直接复用 MuseAiRuntimeProjectionService.donePayload,因为现有 payload 缺少 Events OpenAPI 必填的 taskId,需要由 outbox mapper/service 从 task event + task/suggestion 事实组合生成 allowlist payload。
  4. error payload 可以从 MuseAiTaskEventDO.errorCode/errorMessage/payloadSummary.retryable 归一化生成,但必须先做敏感词和字段 allowlist 过滤。
  5. 如果 worker 与 terminal fact 写入不在同一事务内补 outbox存在终态事实已落库但未发布的不可追踪缺口因此 outbox 写入必须和 terminal fact 绑定在同一事务边界内。

假设

  1. P1R-7b 允许在 AI owner 内新增 muse_ai_event_publish_outbox 或等价职责表。
  2. 实现阶段可以只在 AI 模块新增 muse-module-events-api 编译依赖,不需要引入 events-server。
  3. 实现阶段可以新增 focused integration test 或 mapper-level test 来证明 muse_ai_event_publish_outbox migration、claim、retry、dead_letter 和 SSE 可见链路。
  4. P1R _test PostgreSQL 验证仍使用项目既有 ~/.config/muse-repo/infra.env 方式,但运行前必须确认目标库是独立测试库。

全局架构主线

  1. AI owner 只写本域事实和本域 outbox不直接写 muse_unified_event
  2. AI worker 只调用 EventsPublishApi,不依赖 Events server 实现类。
  3. Events owner 只接收发布 DTO、做幂等、payload 校验、脱敏、落库和 SSE 可见性过滤。
  4. SSE 查询只读 muse_unified_event,不反查 AI 表。
  5. replay 只能重放 AI 本域 outbox 记录;禁止 operator 或补偿任务绕过 source owner 直接向 Events 注入伪造 payload。

继承全局合同

结论P1R-7b 不重新发明合同,只把全局 P1R7 Source Owner Propagation 合同投影到 AI terminal event 第一切片。若本文件与全局执行版冲突,以全局执行版为准;后续实现前必须先修订本文件并重新 review。

依赖合同

  1. muse-module-ai-server 只允许新增或保留 muse-module-events-api 依赖。
  2. muse-module-ai-server 禁止依赖 muse-module-events-server
  3. muse-module-events-server 禁止依赖 AI / Knowledge / Market / Member / Content server。
  4. muse-server 可以装配 AI server 与 Events server但不能把装配关系写成业务反向依赖。
  5. P1R-7b 实现前和实现后都必须运行 dependency tree 验证;验证命令只作为框架写入本计划,本轮不运行。

发布 envelope 合同

  1. commandId 必须稳定、短、幂等,推荐 ai_evt:<sha256-32>,长度 <= 128
  2. tenantIdownerUserIdsourceOwner=aisourceType=ai_task_eventsourceIdsourceRevisioneventTyperesourceType/resourceIdpayloadSummaryemittedAt 都必须在 outbox 中可追溯。
  3. eventType 只允许使用 Events OpenAPI 既有 done / error,不得新增 AI 私有事件类型绕过合同。
  4. sourceRevision 使用 terminal event sequence_no 字符串;为空时不得创建可发布 outbox。
  5. emittedAt 使用 AI terminal fact 发生时间,不使用 worker 发布时间替代事实时间。

用户可见事件判定

  1. P1R-7b 只发布用户可见 terminal done/error 摘要。
  2. 可重试 runtime failure、worker heartbeat、projection rebuild、source retry job、内部调度状态不进入统一 SSE。
  3. cancellation 只能作为 error 摘要发布,且只包含 AI_TASK_CANCELLEDAI task cancelledretryable=false,不得包含用户提交的取消原因或内部审计备注。
  4. payload 需要完整 prompt、provider raw、正文、授权详情、风控原始证据才能解释时必须 fail-closed不得发布到 SSE。
  5. 本切片不处理 Knowledge / Market / Account / Content 的用户可见事件。

Outbox 合同

  1. AI outbox 必须支持 queued/running/retryable/published/dead_letter 或全局执行版认可的等价状态。
  2. claim 必须使用 FOR UPDATE SKIP LOCKED 或等价原子领取机制,并覆盖 queued、到期 retryable、stale running。
  3. claim 时递增 attempt_count;失败处理不得二次递增 attempt。
  4. 必须有 max_attemptnext_retry_atclaimed_atclaim_expires_at、claim 索引、固定退避策略。
  5. 幂等插入必须使用 ON CONFLICT DO NOTHING / insertIgnore + 回查;禁止捕获 DuplicateKeyException 作为正常幂等路径。
  6. dead_letter 不自动 replay只有确认是临时故障的记录才能由用户批准后重置为 retryable
  7. 状态转换必须有中文日志,包含 owner、tenantId、ownerUserId、source tuple、outboxId、attempt、状态转换和安全错误摘要。

Payload 合同

  1. done payload 只允许 taskIdsuggestionId、可选安全短 summary
  2. error payload 只允许 codemessage、可选安全 detail、可选 retryable
  3. taskIdsuggestionId 必须满足 Events OpenAPI int64 语义;不满足时 fail-closed 到 dead_letter,不得调用 EventsPublishApi
  4. 禁止 prompt、userInstruction、provider request/response raw body、token、apiKey、authorization、bearer、secret、runtimePermissionEnvelope、source private context、full output、contentSnapshot 正文进入 payload。
  5. payload invalid、Events rejected、Events blocked 都不得进入 SSE 可见查询。

涉及模块与文件路径

必须只读核对

  • AGENTS.md
  • CLAUDE.md
  • docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation审阅版.md
  • docs/memorys/2026-06-05-P1R7aEventsSSE真实API收口.md
  • docs/superpowers/plans/2026-06-05-P1R-7a-events-sse.md
  • docs/api-contracts/events/openapi.yaml
  • docs/superpowers/reports/p1r-api-coverage.json
  • docs/superpowers/reports/p1r-api-coverage.md

实现阶段允许修改

说明:本节的“新增/修改”描述 P1R-7b clean baseline 目标写集;如果当前 dirty baseline 已经存在同名草稿文件,后续 implementer 必须先按“当前 dirty 文件处置表”判断修订沿用或用户批准后废弃重做,不能因为文件已存在就跳过 TDD、验证或 fresh review。

  • muse-cloud/sql/muse/V17__extend_ai_events_publish_outbox.sql,当前 dirty baseline 已存在该草稿;实现阶段若选择修订沿用,必须重新证明版本号、字段、索引和 Flyway _test 全部合规;若选择废弃重做,必须先获得用户明确批准。
  • muse-cloud/muse-module-ai/muse-module-ai-server/pom.xml
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/dataobject/muse/MuseAiEventPublishOutboxDO.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapper.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxService.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceImpl.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishWorker.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceImpl.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/framework/ai/config/MuseAiProperties.java,如果实现阶段复用项目集中配置承载 worker 开关;否则必须在实现报告写明等价配置承载点。

实现阶段允许新增或修改测试

  • muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapperTest.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceTest.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishWorkerTest.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionServiceTest.java
  • muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceTest.java
  • muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishMigrationSqlTest.java
  • muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishFlywayMigrationIT.java
  • muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishDependencyTest.java
  • muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishEndToEndTest.java

必须保持不修改

  • 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

数据流与依赖方向

sequenceDiagram
    participant Runtime as MuseAiRuntimeProjectionService
    participant TaskEvent as muse_ai_task_event
    participant Outbox as muse_ai_event_publish_outbox
    participant Worker as MuseAiEventPublishWorker
    participant Api as EventsPublishApi
    participant Unified as muse_unified_event
    participant SSE as /app-api/muse/events

    Runtime->>TaskEvent: 同事务写 done/error terminal fact
    Runtime->>Outbox: 同事务写 queued publish job
    Worker->>Outbox: SKIP LOCKED claim queued/retryable
    Worker->>Api: publish(EventsPublishReqDTO)
    Api->>Unified: 幂等落库 accepted/rejected/blocked
    Api-->>Worker: eventId/sequenceNo/status/duplicate
    Worker->>Outbox: published 或 dead_letter 或 retryable
    SSE->>Unified: 只读 accepted + tenant + ownerUserId + sequence

依赖方向:

  1. muse-module-ai-server 可以新增 muse-module-events-api 依赖。
  2. muse-module-ai-server 禁止依赖 muse-module-events-server
  3. muse-module-events-server 禁止依赖 AI / Knowledge / Market / Member / Content server。
  4. muse-server 继续装配 AI server 和 Events server。

数据契约与 payload allowlist

统一发布 DTO

字段 AI outbox 来源 规则
commandId outbox.command_id 必填,冻结为短格式或 hash 格式,推荐 ai_evt:<sha256-32>;禁止使用包含 tenantId/taskId/sourceRevision/eventType 的长拼接格式作为推荐格式,测试必须覆盖长度 <= 128
tenantId outbox.tenant_id 必填
ownerUserId task event owner_user_id 必填
sourceOwner 常量 ai 必填
sourceType 常量 ai_task_event 必填
sourceId task event task_id 必填,字符串形式
sourceRevision task event sequence_no 必填,字符串形式;为空时不允许生成 outbox
eventType task event event_type 只允许 done / error
resourceType 常量 ai_task 必填或可空,但写入 outbox 时保持一致
resourceId task event task_id 字符串形式
payloadSummary allowlist mapper 生成 只允许下方字段
emittedAt task event emitted_at 必填

全局 payload 规则:

  1. muse_ai_task_event.task_idVARCHAR,但 SSE done payload 的 taskId 必须是 int64 语义数字P1R-7b 最小规则要求 doneerror terminal event 在进入 Events publish 前都先校验 taskId 可解析为 int64。
  2. 非数字 taskId 必须 fail-closed 到 AI outbox dead_letterlast_error_code = AI_EVENTS_TASK_ID_NOT_NUMERIC,不得调用 EventsPublishApi,不得发布 doneerror 到 SSE 可见事件。
  3. command_id 必须由稳定 source tuple 计算短 hash例如对 tenantId|taskId|sourceRevision|eventType 做 sha256 后取 32 位十六进制摘要并加 ai_evt: 前缀;测试必须证明生成结果长度 <= 128

done payload allowlist

{
  "taskId": 3001,
  "suggestionId": 2001,
  "summary": "AI task completed"
}

规则:

  1. taskId 必须是 int64 语义数字。
  2. suggestionId 必须是 int64 语义数字;如果 task 没有 suggestionId则不得发布 doneoutbox 写入或保持 dead_letterlast_error_code = AI_EVENTS_DONE_PAYLOAD_INCOMPLETE,并且不得进入 SSE 可见事件。
  3. summary 可选,必须是安全短文本;不得来自 provider raw body 或完整输出正文。

error payload allowlist

{
  "code": "AI_NEW_API_UNAVAILABLE",
  "message": "AI task failed",
  "detail": "runtime unavailable",
  "retryable": false
}

规则:

  1. code 必须来自 AI 本域归一化错误码,缺失时使用 AI_TASK_FAILED
  2. message 必须是安全错误摘要,不能包含 provider 原始响应、prompt、token、授权头或私有上下文。
  3. detail 可选,只允许开发安全摘要;如包含敏感关键词则丢弃。
  4. retryable 可选terminal error 对应非可重试失败时为 false
  5. cancellation terminal event 是 MuseJobServiceImpl.appendTaskCancellationEventIfAbsent 写出的合同内 error 事件payload allowlist 只能包含 code = AI_TASK_CANCELLEDmessage = AI task cancelledretryable = false;不得包含 detail,不得携带 admin/app 用户提交的取消原因。

禁止字段:

  1. prompt / userInstruction 原文。
  2. provider raw body / provider request body / provider response body。
  3. token / apiKey / authorization / bearer / secret。
  4. runtimePermissionEnvelope 原文。
  5. source private context / full output / contentSnapshot 正文。
  6. 未在 docs/api-contracts/events/openapi.yaml 声明的扩展字段。

迁移与表设计计划

表名:muse_ai_event_publish_outbox

当前 dirty baseline 已存在未提交 V17__extend_ai_events_publish_outbox.sql 草稿。实现前必须重新 live 查证 muse-cloud/sql/muse 版本号:如果 V17 草稿仍存在,只能修订沿用或经用户明确批准后废弃重做;如果上游已新增后续版本,必须先停下报告冲突,并按用户确认选择下一 migration 号。

字段计划:

字段 类型 约束
id BIGINT GENERATED ALWAYS AS IDENTITY 主键
tenant_id BIGINT NOT NULL
owner_user_id BIGINT NOT NULL
task_id VARCHAR(128) NOT NULL
source_event_id BIGINT NOT NULL,引用 muse_ai_task_event.id 这个 AI task event 本域自增主键
source_revision VARCHAR(80) NOT NULL,使用 task event sequence_no 字符串
event_type VARCHAR(32) NOT NULL CHECK IN ('done','error')
payload_summary JSONB NOT NULL DEFAULT '{}'::jsonb
command_id VARCHAR(128) NOT NULL
publish_status VARCHAR(32) NOT NULL CHECK IN ('queued','running','published','retryable','dead_letter')
attempt_count INT NOT NULL DEFAULT 0
claimed_at TIMESTAMP 可空worker claim 时写入当前时间
claim_expires_at TIMESTAMP 可空worker claim 时写入 now + lease,用于 crash-after-claim 后回收 stale running
next_retry_at TIMESTAMP 可空
last_error_code VARCHAR(64) 可空
last_error_message TEXT 可空,必须是安全摘要
published_event_id VARCHAR(128) 可空
published_sequence_no BIGINT 可空
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,对齐 BaseDO.createTime / 项目 DDL 字段约定
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,对齐 BaseDO.updateTime / 项目 DDL 字段约定
creator VARCHAR(64) NOT NULL DEFAULT '',如项目基类需要
updater VARCHAR(64) NOT NULL DEFAULT '',如项目基类需要
deleted BOOLEAN NOT NULL DEFAULT FALSE

约束与索引:

  1. uk_muse_ai_event_publish_outbox_commandUNIQUE (tenant_id, command_id)
  2. uk_muse_ai_event_publish_outbox_sourceUNIQUE (tenant_id, task_id, source_revision, event_type)
  3. idx_muse_ai_event_publish_outbox_claim(publish_status, claim_expires_at, next_retry_at, create_time, id),过滤 deleted = FALSE;必须支持 claim 查询同时覆盖 queued、到期 retryable、以及 running AND claim_expires_at < now 的 stale running。
  4. idx_muse_ai_event_publish_outbox_owner_status(tenant_id, owner_user_id, publish_status, create_time)
  5. trg_muse_ai_event_publish_outbox_update_time:复用 update_updated_at_column(),触发器必须更新 update_time

状态机

stateDiagram-v2
    [*] --> queued: terminal fact 同事务创建 outbox
    queued --> running: claim, attempt_count + 1, 写 lease
    retryable --> running: next_retry_at 到期 claim, attempt_count + 1, 写 lease
    running --> running: claim_expires_at < now stale reclaim, attempt_count + 1, 刷新 lease
    running --> published: Events accepted 或 duplicate
    running --> dead_letter: Events rejected/blocked 或 payload 不合规
    running --> retryable: Events temporary failure 或调用异常
    running --> dead_letter: stale reclaim 后 attempt_count 超过 max attempt
    retryable --> dead_letter: claim 或失败处理发现 attempt_count 超过 max attempt
    published --> [*]
    dead_letter --> [*]

状态含义:

  1. queued:待发布,attempt_count = 0claimed_at / claim_expires_at 为空。
  2. running:被 worker 原子领取,必须写 claimed_at = nowclaim_expires_at = now + lease,并有中文日志记录 outbox id、tenantId、taskId、eventType、attempt、claimExpiresAt。
  3. publishedEvents CommonResult 成功且 EventsPublishRespDTO.publishStatus = accepted,或 publishStatus = acceptedduplicate = true;必须记录 published_event_idpublished_sequence_no
  4. retryable:临时失败,设置 next_retry_at,保留安全错误码和摘要。
  5. dead_letterpayload 不合规、Events 返回 rejected/blocked、或超过最大重试次数不得进入 SSE 可见事件。

注意:

  1. AI outbox 本地 publish_status 不存在 blockedEvents 侧 accepted/rejected/blocked 是统一事件投影层状态,不是 AI outbox 本地状态。
  2. worker 不能只看 CommonResult 成功/失败;必须读取 EventsPublishRespDTO.publishStatus 并按 accepted/duplicate/rejected/blocked 分流。CommonResult 成功但 publishStatus 为空或未知时,必须 fail-closed 写 retryabledead_letter,不得伪造成 published
  3. claim 查询必须覆盖三类可领取记录:queuedretryable AND next_retry_at <= nowrunning AND claim_expires_at < now。未到期的 running 不得被领取。
  4. attempt 语义冻结为 claim 次数:每次从 queued、到期 retryable 或 stale running 被 claim 时统一 attempt_count + 1;失败处理不得再次递增 attempt避免一次 publish 失败被计算两次。
  5. stale running reclaim 采用 running -> running 最小语义:重领时刷新 claimed_at/claim_expires_at 并递增 attempt如果递增后超过 max_attempt,必须进入 dead_letter,错误码 AI_EVENTS_PUBLISH_RETRY_EXHAUSTED,避免 crash-after-claim 无限循环。

幂等策略

  1. AI outbox 创建幂等:同一 (tenant_id, task_id, source_revision, event_type) 只能有一条 outbox。
  2. command 幂等:同一 (tenant_id, command_id) 只能有一条 outbox。
  3. Events publish 幂等:同一 commandId 或同一 source tuple 重放必须得到相同 eventId / sequenceNo
  4. worker 幂等worker 重试只读取 outbox 记录,不重新读取不稳定上下文构造不同 payload。
  5. terminal fact 幂等:MuseAiTaskEventMapper.selectTerminalByTaskId 和 DB terminal 唯一约束继续阻止同一 task 多个 terminal event。
  6. replay 幂等:人工 replay 只允许把 dead_letter 中确认为临时故障的记录重置为 retryableAI_EVENTS_DONE_PAYLOAD_INCOMPLETE 等 payload 不合规记录不得直接 replay。

失败、重试与 dead_letter

临时失败:

  1. EventsPublishApi 调用抛出网络、超时、RPC unavailable 等异常。
  2. CommonResult 表示临时不可用或无响应。
  3. worker 处理时发生非 payload/contract 类异常。

处理方式:

  1. attempt_count 已在 claim 时递增,失败处理不得再次 + 1
  2. 当前 attempt_count 未达到上限时写 retryablenext_retry_at = now + backoff,并清空或保留 lease 字段时必须有一致测试;推荐清空 claimed_at/claim_expires_at,让下一次 claim 只由 next_retry_at 控制。
  3. backoff 建议最小固定序列 1s, 2s, 4s, 8s, 16s,超出使用 16s实现代码必须用中文注释说明这是 P1R-7b 最小退避,不引入全局任务框架。
  4. 当前 attempt_count 达到或超过上限写 dead_letter,错误码 AI_EVENTS_PUBLISH_RETRY_EXHAUSTED

stale running

  1. worker crash-after-claim 后,记录会停留在 running,直到 claim_expires_at < now
  2. stale running 由后续 claim 原子重领,状态保持 running -> running,刷新 claimed_at/claim_expires_at,并按 claim 次数递增 attempt_count
  3. stale running 重领前或重领后发现 attempt_count 超过 max_attempt,必须写 dead_letter,不得无限重领。
  4. claim_expires_at 为空的 running 属于不合规历史状态;实现阶段必须 fail-closed 到 retryabledead_letter,并用测试固定规则,不得被永久卡住。

永久失败:

  1. payload 缺少 OpenAPI 必填字段。
  2. payload 包含 prompt、provider raw body、token、authorization、bearer、secret 等敏感内容。
  3. EventsPublishApi 返回 rejectedblocked

处理方式:

  1. 立即写 dead_letter
  2. 不重试。
  3. 记录安全日志和审计摘要。
  4. 不向 SSE 伪造事件。

Worker 生命周期与停止策略

  1. MuseAiEventPublishWorker 采用现有 MuseAiRuntimeJobDispatcher 的最小生命周期风格:@Scheduled(initialDelayString = "${muse.ai.events.publish-worker.initial-delay-ms:1000}", fixedDelayString = "${muse.ai.events.publish-worker.fixed-delay-ms:1000}") 只调用 dispatchOnce()
  2. 新增配置开关 muse.ai.events.publish-worker.enabled,默认 true;实现阶段如项目配置类已有更适合的布尔默认约定,必须保持默认启用或在实现报告写明偏差原因。
  3. enabled=false 时 scheduled 入口和手动入口都不得 claim outboxenabled=true 时 scheduled 入口调用 dispatchOnce() 并按 claim 结果处理一批或一条记录。
  4. dispatchOnce() 必须保留可测试的同步方法,测试不依赖真实时间、不等待 scheduler。
  5. 回滚或应急停止方式:关闭 muse.ai.events.publish-worker.enabled,或在更高风险回滚中移除 worker bean 装配。已经 claim 为 running 的记录不做人工改写;重新启用 worker 后由 claim_expires_at lease 回收 stale running并按 retry/dead_letter 规则继续处理。

日志、审计与可观测性

实现阶段必须补中文日志和注释:

  1. 创建 outbox记录 tenantIdownerUserIdtaskIdeventTypesourceRevisioncommandId,不记录 payload 正文。
  2. worker claim记录 outbox id、attempt、状态转换、claimedAt、claimExpiresAt以及是否 stale running reclaim。
  3. publish 成功:记录 outbox id、publishedEventIdpublishedSequenceNoduplicate
  4. publish rejected/blocked记录 outbox id、错误码、安全摘要。
  5. temporary failure记录 outbox id、错误类型、nextRetryAt。
  6. dead_letter记录 outbox id、最终错误码、attemptCount。
  7. payload sanitizer 或 allowlist 拒绝:必须有安全日志,不能输出原始敏感值。

审计要求:

  1. 如 AI 现有 MuseAiAuditService 适用于内部业务审计,则记录 operationId = aiEventsPublish 或实现阶段冻结的等价 operation id。
  2. 审计 request/response summary 只能包含 outbox id、taskId、eventType、publishStatus、publishedEventId、publishedSequenceNo、errorCode。
  3. 审计不阻塞主业务事实落库;审计失败不得伪造成 publish 成功。

TDD 与 review gate 总规则

P1R-7b 子计划 gate

  1. 本文件必须先完成 fresh spec review。
  2. 本文件必须再完成 fresh quality / feasibility review。
  3. 双 PASS 前不得继续当前 AI 草稿实现,不得运行当前草稿测试,不得提交或推送。
  4. 若任一 review FAIL只修订本文件和必要 .agent 状态说明;不得通过改代码、改 OpenAPI、改 scanner、改 coverage 绕过 review。

每个实现 Task 必须按以下顺序执行:

  1. fresh subagent implementer 领取单个 Task。
  2. 先写失败测试或门禁。
  3. 运行 focused test确认因目标缺口失败。
  4. 写最小实现。
  5. 运行 focused tests确认通过。
  6. 运行该 Task 指定验证命令。
  7. fresh spec review。
  8. fresh quality review。
  9. 双 PASS 后才允许进入下一 Task。

任何 Task 若出现 FAIL

  1. implementer 只修当前 Task 范围内问题。
  2. 修复后重新跑 focused tests。
  3. 重新进行 fresh spec review + fresh quality review。
  4. 不得用 OpenAPI/scanner/coverage 状态改动绕过失败。

Task 0子计划 fresh review gate

执行角色fresh spec reviewer + fresh quality / feasibility reviewer。

文件范围:

  • 允许修改:docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation执行版.md
  • 允许修改:docs/agent-specs/.agent
  • 保持不动:任何 Java / SQL / test / pom.xml / OpenAPI / scanner / coverage 文件。

步骤:

  1. 只读核对全局审阅版和全局执行版已把 P1R-7b 定义为 AI terminal event 第一切片。
  2. 只读核对本文件是否继承依赖合同、发布 envelope 合同、用户可见事件判定、outbox 合同和 payload 合同。
  3. 只读核对当前 dirty 文件处置表是否覆盖 AI 草稿代码、迁移、测试和 docs/agent-specs/*
  4. 只读核对 DuplicateKeyException 幂等路径是否被列为必须修订点。
  5. 只读核对本文件是否明确双 review PASS 前不得继续 AI 草稿实现。

验证命令框架,本轮计划修订不运行:

git status --short --branch
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
rg -n "P1R-7b|AI terminal|fresh spec review|fresh quality|DuplicateKeyException|insertIgnore|ON CONFLICT|completed" docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation执行版.md

完成条件:

  1. Fresh spec review PASS。
  2. Fresh quality / feasibility review PASS。
  3. protected files 定向状态无改动。
  4. 输出下一步是“由 fresh implementer 处理 Task 1/Task 2”不是直接继续当前 AI 草稿。

Task 1Preflight 与依赖/现状冻结

执行角色fresh subagent implementer + fresh spec review + fresh quality review。

文件范围:

  • 只读:AGENTS.md
  • 只读:CLAUDE.md
  • 只读:docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation审阅版.md
  • 只读:docs/memorys/2026-06-05-P1R7aEventsSSE真实API收口.md
  • 只读:docs/superpowers/plans/2026-06-05-P1R-7a-events-sse.md
  • 只读:docs/superpowers/reports/p1r-api-coverage.json
  • 只读:muse-cloud/muse-module-events/**
  • 只读:muse-cloud/muse-module-ai/**
  • 只读:muse-cloud/sql/muse/**

步骤:

  1. 记录 pwdgit status --short --branchgit branch --show-currentgit log -1 --oneline
  2. 定向检查受保护文件和 coverage 报告无改动。
  3. 读取 coverage summary 和 Events/Market operation 明细。
  4. 检查 muse-module-ai-server/pom.xml 当前 dirty 草稿中的 muse-module-events-api 依赖,并确认没有 muse-module-events-server 依赖。
  5. 检查 muse-cloud/sql/muse 当前最新 migration 版本,区分已推送基线和当前未提交 V17 草稿。
  6. 检查 Events API、Events publish service、UnifiedEvent mapper、Events stream service 的现状。
  7. 检查 AI task event、runtime projection、runtime dispatcher、job mapper、source event retry 的现状。

验证命令:

pwd
git status --short --branch
git branch --show-current
git log -1 --oneline
git diff --name-only -- 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
jq '.summary' docs/superpowers/reports/p1r-api-coverage.json
jq -r '.operations[] | select(.domain=="events") | [.domain,.operationId,.method,.path,.implementationStatus,.completionStatus,.targetStage] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
jq -r '[.operations[] | select(.domain=="market") | (.implementationStatus + "/" + .completionStatus)] | group_by(.)[] | [(.[0]), length] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
rg -n "muse-module-events-api|muse-module-events-server" muse-cloud/muse-module-ai/muse-module-ai-server/pom.xml muse-cloud/muse-server/pom.xml muse-cloud/muse-module-events/muse-module-events-server/pom.xml
find muse-cloud/sql/muse -maxdepth 1 -type f -name 'V*__*.sql' \
  | sed -E 's#.*/V([0-9]+)__.*#\1&#' \
  | sort -n \
  | tail -n 10

完成条件:

  1. 工作区、分支、dirty baseline 与本执行版约束一致。
  2. 受保护文件和 coverage 报告无改动。
  3. AI 当前 muse-module-events-api / muse-module-events-server 依赖状态有 live 证据。
  4. 最新 migration 版本有 live 证据,且必须按 V 后数字排序;如果当前 V17 草稿仍存在,必须把它列为“修订沿用候选”,不能误写成已通过 migration。
  5. spec review PASS + quality review PASS。

Task 2AI publish outbox/job schema

执行角色fresh subagent implementer + fresh spec review + fresh quality review。Task 2 是第一个可写代码/迁移/测试的实现 Task必须等本子计划 Task 0 双 review PASS 后,由 fresh implementer 重新领取;当前会话不得直接继续 AI 草稿实现。

文件范围:

  • 修订或新增:muse-cloud/sql/muse/V17__extend_ai_events_publish_outbox.sql。当前 dirty baseline 已存在 V17 草稿Task 2 implementer 必须先判断它是修订沿用还是在用户批准后废弃重做,不能把已存在文件当成已通过事实。
  • 修订或新增:muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishMigrationSqlTest.java。当前 dirty baseline 已存在草稿Task 2 必须让测试针对当前 V17 真实缺口失败,再修订到通过。
  • 修订或新增:muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishFlywayMigrationIT.java。当前 dirty baseline 已存在草稿Task 2 必须重新证明独立 _test 库保护和真实 test count。

测试范围:

  1. SQL 文本测试必须断言表名、字段、唯一约束、claim 索引、状态 CHECK、event_type CHECK、update_time triggersource_event_id 字段类型唯一冻结为 BIGINT NOT NULL
  2. SQL 文本测试必须断言 outbox schema 能支持后续 claimclaimed_at TIMESTAMPclaim_expires_at TIMESTAMP 两个 lease 字段存在,publish_statusnext_retry_atattempt_count 字段存在,且 claim 索引包含 publish_status, claim_expires_at, next_retry_at, create_time, id
  3. SQL 文本测试必须断言 outbox 表使用项目约定字段 creator/create_time/updater/update_time/deleted,并通过断言 BaseDO.createTime/updateTimeTenantBaseDO extends BaseDO 或等价反射/文本证据锁定项目基类约定。
  4. SQL 文本测试必须断言 update_updated_at_column() 更新 update_time,并断言 outbox migration 不使用非项目约定的时间字段名。
  5. SQL 文本测试必须断言 command_id VARCHAR(128),并配套 service/mapper 测试冻结 ai_evt:<sha256-32> 或等价短 hash 格式,生成长度 <= 128
  6. SQL 文本测试只断言 schema 层 claim 支撑能力:publish_status 状态 CHECK、attempt_count 非负约束、lease 字段和 retry 字段可空性、claim 索引字段顺序;不得要求断言具体 mapper claim SQL 或 mapper 注解。
  7. Flyway _test 必须在独立测试库执行,不能 clean 开发库或真实库。
  8. migration 必须和 V12/V16 共存,不修改既有表语义。
  9. 如果沿用当前 V17 草稿,必须先修订所有不符合全局 outbox 合同的字段、索引、状态和幂等语义;不能因为文件已存在就跳过 TDD 失败测试和 fresh review。

TDD 步骤:

  1. 先写或修订 P1rAiEventsPublishMigrationSqlTest,让它针对当前 V17 草稿的真实缺口失败;如果 V17 草稿被用户批准废弃后不存在,则测试应因 migration 未定义失败。
  2. 运行 focused test确认失败原因来自 schema 合同缺口,而不是测试未运行。
  3. 修订或新增 migration包含本执行版字段、lease 字段、约束、索引和 trigger。
  4. 运行 SQL 文本测试通过。
  5. 新增或扩展 Flyway IT证明所有 migrations 在独立 _test 库可执行。
  6. 运行 Flyway _test

验证命令:

cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-server -am -Dtest=P1rAiEventsPublishMigrationSqlTest test
set -a
source ~/.config/muse-repo/infra.env
set +a
export P1R7B_TEST_DB="${P1R7B_TEST_DB:-muse_p1r7b_events_publish_test}"
case "$P1R7B_TEST_DB" in *_test) ;; *) echo "P1R7B_TEST_DB must end with _test"; exit 1;; esac
export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD"
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" timeout 240 mvn test \
  -pl muse-server \
  -Dtest=P1rAiEventsPublishFlywayMigrationIT \
  -Dflyway.postgresql.transactional.lock=false \
  -Djava.net.useSystemProxies=false \
  -DsocksProxyHost= \
  -DsocksProxyPort= \
  -DsocksNonProxyHosts="127.0.0.1|localhost|100.64.*|100.*" \
  -Dhttp.proxyHost= \
  -Dhttps.proxyHost= \
  -Dp1r.flyway.url="jdbc:postgresql://${MUSE_POSTGRES_HOST}:${MUSE_POSTGRES_PORT}/${P1R7B_TEST_DB}" \
  -Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
  -Dp1r.flyway.locations="filesystem:sql/muse"

完成条件:

  1. migration 版本号来自 Task 1 live 查证。
  2. 表字段覆盖本执行版迁移计划要求;source_event_id 必须是 BIGINT NOT NULL,引用 muse_ai_task_event.id,如果实现阶段发现 DO/mapper 名称不同,也必须先 live 查证后保持 Long id 语义。
  3. 唯一约束覆盖 commandId 和 task/source/event tuple。
  4. SQL 文本测试通过,且 Task 2 完成证据只来自 migration SQL 与 migration tests不依赖 Task 4 才新增或修改的 mapper 文件。
  5. 独立 _test Flyway IT 通过,并断言目标 version、description、已执行 migration count 随 Task 1 live 下一版本号更新;保留 _test 库名保护,非 _test 库必须 fail-fast。
  6. SQL 文本测试和 Flyway IT 都必须证明目标测试类实际运行:target/surefire-reports/TEST-...P1rAiEventsPublishMigrationSqlTest.xmlTEST-...P1rAiEventsPublishFlywayMigrationIT.xml 存在,且各自 test count > 0。
  7. spec review PASS + quality review PASS。

Task 3AI DO / Mapper / Service 与 terminal fact 同事务写 outbox

执行角色fresh subagent implementer + fresh spec review + fresh quality review。

文件范围:

  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/pom.xml
  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/dataobject/muse/MuseAiEventPublishOutboxDO.java
  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapper.java
  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxService.java
  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceImpl.java
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.java
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceImpl.java
  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapperTest.java
  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceTest.java
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionServiceTest.java
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceTest.java

测试范围:

  1. DO 字段映射覆盖 migration 字段。
  2. Mapper 能按 commandId 和 source tuple 查重。
  3. Service 只为 done / error terminal event 创建 outboxcancelled task status 只能通过 eventType = error 发布,不新增 cancelled event type。
  4. Service 对 done 缺少 suggestionId 的事件写入或保持 dead_letter,记录 last_error_code = AI_EVENTS_DONE_PAYLOAD_INCOMPLETE,并证明不会发布到 SSE 可见事件。
  5. Service 对非数字 taskId 必须 fail-closed 写入或保持 dead_letterlast_error_code = AI_EVENTS_TASK_ID_NOT_NUMERIC,并证明不会调用 EventsPublishApi,不会发布 doneerror 到 SSE 可见事件。
  6. Service 生成 payload allowlist不包含 prompt/provider raw body/token/auth/private context/full output。
  7. Service 生成 command_id 必须使用 ai_evt:<sha256-32> 或等价短 hash 格式,测试覆盖长度 <= 128,禁止长拼接格式作为推荐或默认实现。
  8. MuseAiRuntimeProjectionService 在写 terminal task event 的同一事务内调用 outbox service。
  9. MuseJobServiceImpl.appendTaskCancellationEventIfAbsent 在写 cancellation error terminal event 的同一事务内调用 outbox servicepayload 只能包含 code=AI_TASK_CANCELLEDmessage=AI task cancelledretryable=false,不能携带用户取消原因。
  10. 可重试 failure 不写 terminal event也不写 Events publish outbox。
  11. outbox 创建幂等必须通过 mapper insertIgnore / SQL ON CONFLICT DO NOTHING 返回插入行数,再按 commandId/source tuple 回查;测试必须禁止把 DuplicateKeyException 作为正常分支。

TDD 步骤:

  1. 针对当前 dirty DO/Mapper/Service/Test 草稿写或修失败测试,失败点必须来自真实合同缺口,而不是“类不存在”。
  2. 优先写 duplicate 幂等失败测试:当前草稿如果仍捕获 DuplicateKeyException 作为正常路径,测试必须失败;目标路径必须是 insertIgnore / ON CONFLICT DO NOTHING 返回 0 + commandId/source tuple 回查。
  3. 写 projection 失败测试,断言 terminal done/error 后必须创建 outbox如果当前草稿已有调用点测试必须核对事务边界、sourceEventId、payload 和幂等语义。
  4. MuseJobServiceTest 失败测试,断言 cancel admin/app 触发 appendTaskCancellationEventIfAbsent 后必须同事务创建 outbox且 cancellation payload 不包含 request reason。
  5. 写 payload 安全失败测试,构造包含敏感字段的 payload断言 outbox payload 不包含敏感值。
  6. 写非数字 taskId 失败测试,断言 taskId 为 abc、UUID 或带前缀字符串时 fail-closed 到 dead_letter,不会产生 SSE 可见事件。
  7. 写 commandId 长度测试,断言 ai_evt:<sha256-32> 或等价短 hash 格式长度 <= 128,且不使用长拼接格式。
  8. 运行 focused tests确认失败原因来自上述真实缺口且测试类实际运行。
  9. 修订 DO/Mapper/Service 最小实现,先实现 insertIgnore / ON CONFLICT DO NOTHING + 回查,再接 terminal fact 调用点。
  10. 复核 muse-module-ai-server/pom.xml 当前 dirty 草稿中的 muse-module-events-api 依赖;保留或修订时只允许 api不允许 server。
  11. MuseAiRuntimeProjectionService.appendTaskEvent 写入 terminal event 后调用 outbox service关键事务和安全 allowlist 必须有中文注释。
  12. MuseJobServiceImpl.appendTaskCancellationEventIfAbsent 写入 cancellation error terminal event 后调用 outbox service中文注释必须说明取消原因不能进入统一 Events payload。
  13. 运行 focused tests。

验证命令:

cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-module-ai/muse-module-ai-server -am \
  -Dtest=MuseAiEventPublishOutboxMapperTest,MuseAiEventPublishOutboxServiceTest,MuseAiRuntimeProjectionServiceTest,MuseJobServiceTest test
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-module-ai/muse-module-ai-server -am dependency:tree \
  -Dincludes=cn.iocoder.cloud:muse-module-events-api
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-module-ai/muse-module-ai-server -am dependency:tree \
  | rg 'muse-module-events-server' && exit 1 || true

完成条件:

  1. muse-module-ai-server 只新增 muse-module-events-api 依赖。
  2. runtime done/error terminal fact 与 outbox 写入在同一事务链路内。
  3. cancellation error terminal fact 与 outbox 写入在同一事务链路内,且 cancellation payload 只包含 AI_TASK_CANCELLEDAI task cancelledretryable=false 三个安全事实。
  4. payload allowlist 测试覆盖 done/error/cancellation、非数字 taskId fail-closed、commandId 短 hash 长度 <= 128 和敏感字段排除。
  5. MuseAiEventPublishOutboxServiceImpl 不捕获 DuplicateKeyException 作为正常幂等路径;对应测试证明 duplicate 走 insertIgnore=0 + 回查。
  6. focused tests 通过,并必须证明 MuseAiEventPublishOutboxMapperTestMuseAiEventPublishOutboxServiceTestMuseAiRuntimeProjectionServiceTestMuseJobServiceTest 实际运行:对应 surefire reports XML 存在且 test count > 0。
  7. dependency tree 证明 AI 不依赖 events-server。
  8. spec review PASS + quality review PASS。

Task 4AI worker / dispatcher 发布链路

执行角色fresh subagent implementer + fresh spec review + fresh quality review。

文件范围:

  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishWorker.java
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/framework/ai/config/MuseAiProperties.java,如果实现阶段选择用集中配置承载 muse.ai.events.publish-worker.enabled
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapper.java
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceImpl.java
  • 新增:muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishWorkerTest.java
  • 修改:muse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapperTest.java

测试范围:

  1. Mapper claim 使用单条 SQL 原子 UPDATE ... WHERE id=(SELECT ... FOR UPDATE SKIP LOCKED) RETURNING * 或等价机制。
  2. claim 只领取 queued、到期 retryable、以及 running AND claim_expires_at < now 的 stale running不领取未到期 runningpublisheddead_letter
  3. claim 时必须原子写 publish_status = runningclaimed_at = nowclaim_expires_at = now + leaseattempt_count = attempt_count + 1
  4. stale running reclaim 必须证明 running -> running、刷新 lease、递增 attempt递增后超过 max attempt 时进入 dead_letter,错误码 AI_EVENTS_PUBLISH_RETRY_EXHAUSTED
  5. worker 的 @Scheduled(initialDelayString=..., fixedDelayString=...) 入口只调用 dispatchOnce()
  6. muse.ai.events.publish-worker.enabled=false 时 worker 不 claimenabled=true 时 scheduled 入口调用 dispatchOnce()
  7. worker 对 CommonResult 成功且 publishStatus=accepted 标记 published,写入 published_event_id/published_sequence_no
  8. worker 对 publishStatus=acceptedduplicate=true 标记 published,复用返回 eventId/sequenceNo。
  9. worker 对 publishStatus=rejected/blocked 标记 dead_letter
  10. worker 对 publishStatus 为空或未知 fail-closed不得标记 published
  11. worker 对 CommonResult 失败、调用异常或临时不可用标记 retryable 并设置 next_retry_at,失败处理不得再次递增 attempt_count
  12. worker 达到最大 attempt 后标记 dead_letter
  13. worker 不把 payload 或敏感错误原文写入日志/审计。

TDD 步骤:

  1. 写 worker 失败测试mock EventsPublishApi 返回 publishStatus=accepted/accepted+duplicate/rejected/blocked/unknown、CommonResult 失败和抛异常。
  2. 写生命周期失败测试,断言 enabled=false 不 claimenabled=true 的 scheduled 入口调用 dispatchOnce()
  3. 写 mapper claim SQL 反射测试,断言 SQL 包含 FOR UPDATE SKIP LOCKEDclaimed_atclaim_expires_atattempt_count + 1queued、到期 retryable、stale runningnext_retry_at 条件。
  4. 写 stale running 测试,断言未到期 running 不领取,claim_expires_at < now 的 running 可 reclaim且超过 max attempt 进入 dead_letter
  5. 运行 focused tests确认失败。
  6. 实现 mapper claim、worker dispatchOnce()@Scheduled scheduled 入口、配置开关、状态转换、lease 和日志。
  7. 实现固定 backoff、claim lease 和最大 attempt中文注释说明 P1R-7b 最小策略。
  8. 运行 focused tests。

验证命令:

cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-module-ai/muse-module-ai-server -am \
  -Dtest=MuseAiEventPublishWorkerTest,MuseAiEventPublishOutboxMapperTest test

完成条件:

  1. worker 能处理 accepted、duplicate、rejected、blocked、temporary failure、retry exhausted。
  2. worker 能证明 enabled=false 不 claimenabled=truedispatchOnce()@Scheduled(initialDelayString=..., fixedDelayString=...) 入口存在且只负责触发。
  3. worker 能证明不只看 CommonResult 成功失败,而是按 EventsPublishRespDTO.publishStatusaccepted/duplicate/rejected/blocked 分流。
  4. claim 具备并发安全语义。
  5. 日志不泄露 payload 原文或敏感错误。
  6. focused tests 通过,并必须证明 MuseAiEventPublishWorkerTestMuseAiEventPublishOutboxMapperTest 实际运行:对应 surefire reports XML 存在且 test count > 0。
  7. spec review PASS + quality review PASS。

Task 5端到端 focused evidence 与 SSE 可见性

执行角色fresh subagent implementer + fresh spec review + fresh quality review。

文件范围:

  • 新增:muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishEndToEndTest.java
  • 必要时修改AI outbox service / worker test fixture不修改 OpenAPI、scanner 或 coverage。

测试范围:

  1. 构造 AI terminal done fact写入 outboxworker 调用 EventsPublishApiEvents 写入 muse_unified_event accepted。
  2. 查询 EventsPublishService.listVisibleEventsEventsStreamService.streamEvents,证明当前 tenant + ownerUserId 可见。
  3. 构造 AI terminal error fact证明 error payload 可见且不泄露敏感 detail。
  4. 构造重复 publish证明 Events 返回同一 eventId/sequenceNooutbox 仍为 published
  5. 构造 rejected payload证明 outbox dead_letter 且 SSE 不可见。

TDD 步骤:

  1. 先写 E2E focused test断言当前链路缺少 AI outbox/worker 时失败。
  2. 接入 Task 3/4 实现后的最小 fixture。
  3. 运行 E2E focused test。
  4. 若 E2E 需要 Spring 装配,优先复用现有 P1R gate 测试风格;不要引入新测试框架。

验证命令:

cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-server -am \
  -Dtest=P1rAiEventsPublishEndToEndTest test

完成条件:

  1. AI done/error 两类 terminal event 均有 source owner -> Events -> SSE 可见证据。
  2. duplicate/rejected 安全路径有 focused evidence。
  3. 不需要修改 OpenAPI/scanner/coverage。
  4. 必须证明 P1rAiEventsPublishEndToEndTest 实际运行:对应 surefire reports XML 存在且 test count > 0。
  5. spec review PASS + quality review PASS。

Task 6依赖方向、coverage gate 与迁移 gate

执行角色fresh subagent implementer + fresh spec review + fresh quality review。

文件范围:

  • 新增:muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishDependencyTest.java
  • 运行并前后检查 diffmuse-cloud/scripts/p1r-audit-api-coverage.py
  • Task 6 scanner 必须在临时隔离副本中运行;当前 worktree 的 coverage JSON/Markdown 与 scanner 脚本仍是受保护文件,运行前后 diff 必须为空。
  • 检查:受保护文件和 coverage 报告状态

测试范围:

  1. AI server 依赖 muse-module-events-api
  2. AI server 不依赖 muse-module-events-server
  3. Events server 不依赖 AI / Knowledge / Market / Member / Content server。
  4. P1rApiCoverageReportTestP1rEventsRealApiGateTestP1rEventsRouteOwnershipTestP1rAiRealApiGateTestP1rAiRouteOwnershipTest 仍通过。
  5. Coverage scanner --check 必须真实运行,但只能在临时隔离副本写报告;当前 worktree 运行前后受保护文件 diff 必须为空。不得通过修改 scanner/coverage 达成状态。

TDD 步骤:

  1. 写或修订 dependency test断言 live 依赖方向AI server 可以有 muse-module-events-api,不得有 muse-module-events-serverEvents server 不得依赖 AI / Knowledge / Market / Member / Content server。
  2. 运行 dependency test确认它针对当前 dirty pom.xml 真实依赖状态做机械断言;不得再以缺失态作为预期失败前提。
  3. 运行 P1R focused gates。
  4. rsync 或等价方式复制当前 worktree 到 /tmp 临时隔离目录,排除 .gittargetnode_modules 等构建产物;在副本中运行 coverage scanner --check,记录副本中的 scanner exit code、summary、Events/Market 状态,并检查当前 worktree 受保护文件 diff 仍为空。
  5. 运行 Flyway SQL/Flyway IT。

验证命令:

cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-server -am \
  -Dtest=P1rAiEventsPublishDependencyTest,P1rApiCoverageReportTest,P1rEventsRealApiGateTest,P1rEventsRouteOwnershipTest,P1rAiRealApiGateTest,P1rAiRouteOwnershipTest \
  test
git -C .. diff -- 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
cd ..
tmpdir=$(mktemp -d /tmp/p1r7b-coverage-scan.XXXXXX)
rsync -a --delete \
  --exclude .git \
  --exclude 'muse-cloud/**/target' \
  --exclude 'node_modules' \
  ./ "$tmpdir"/
cd "$tmpdir/muse-cloud"
python3 scripts/p1r-audit-api-coverage.py --check
cd "$tmpdir"
jq '.summary' docs/superpowers/reports/p1r-api-coverage.json
jq -r '.operations[] | select(.domain=="events") | [.domain,.operationId,.implementationStatus,.completionStatus] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
jq -r '[.operations[] | select(.domain=="market") | (.implementationStatus + "/" + .completionStatus)] | group_by(.)[] | [(.[0]), length] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud
git -C .. diff -- 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
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-module-ai/muse-module-ai-server -am dependency:tree \
  | rg 'muse-module-events|muse-module-(ai|knowledge|market|member|content)-server'

完成条件:

  1. 依赖方向 gate 通过。
  2. P1R focused gates 通过。
  3. scanner --check 在临时隔离副本中 exit 0副本报告 summary 符合 P1R-7b 边界,当前 worktree 运行前后受保护文件和 coverage 报告 diff 为空。
  4. Coverage 状态不得被推进到 completed。
  5. Market 32 operations 保持 dedicated / needs_verification
  6. 必须证明 P1rAiEventsPublishDependencyTest 以及本命令列出的 P1R gate 测试类实际运行:对应 surefire reports XML 存在且 test count > 0。
  7. spec review PASS + quality review PASS。

Task 7最终验证、文档留痕与交接

执行角色fresh subagent implementer + fresh spec review + fresh quality review。

文件范围:

  • 新增:docs/memorys/2026-06-06-P1R7bSourceOwnerPropagation真实链路计划.md 或实现收口时由用户确认的 10 到 20 字任务描述文件名。
  • 修改:必要的实现报告或计划状态文档;不得修改 coverage completed 状态。

测试范围:

  1. AI module focused tests。
  2. Events module focused tests。
  3. P1R gate tests。
  4. Flyway _test
  5. dependency tree。
  6. coverage scanner。
  7. reactor build。
  8. protected files status。

最终验证命令:

git status --short --branch
git diff --name-only -- 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
cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o clean install \
  -DskipTests \
  -Dspring-boot.repackage.skip=true \
  -pl muse-server -am
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-module-ai/muse-module-ai-server,muse-module-events/muse-module-events-server -am \
  -Dtest=MuseAiEventPublishOutboxMapperTest,MuseAiEventPublishOutboxServiceTest,MuseAiEventPublishWorkerTest,MuseAiRuntimeProjectionServiceTest,EventsPublishServiceTest,EventsStreamServiceTest test
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-server -am \
  -Dtest=P1rAiEventsPublishMigrationSqlTest,P1rAiEventsPublishDependencyTest,P1rAiEventsPublishEndToEndTest,P1rApiCoverageReportTest,P1rEventsRealApiGateTest,P1rEventsRouteOwnershipTest,P1rAiRealApiGateTest,P1rAiRouteOwnershipTest \
  test
set -a
source ~/.config/muse-repo/infra.env
set +a
export P1R7B_TEST_DB="${P1R7B_TEST_DB:-muse_p1r7b_events_publish_test}"
case "$P1R7B_TEST_DB" in *_test) ;; *) echo "P1R7B_TEST_DB must end with _test"; exit 1;; esac
export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD"
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" timeout 240 mvn test \
  -pl muse-server \
  -Dtest=P1rAiEventsPublishFlywayMigrationIT \
  -Dflyway.postgresql.transactional.lock=false \
  -Djava.net.useSystemProxies=false \
  -DsocksProxyHost= \
  -DsocksProxyPort= \
  -DsocksNonProxyHosts="127.0.0.1|localhost|100.64.*|100.*" \
  -Dhttp.proxyHost= \
  -Dhttps.proxyHost= \
  -Dp1r.flyway.url="jdbc:postgresql://${MUSE_POSTGRES_HOST}:${MUSE_POSTGRES_PORT}/${P1R7B_TEST_DB}" \
  -Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
  -Dp1r.flyway.locations="filesystem:sql/muse"
git -C .. diff --name-only -- 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
cd ..
tmpdir=$(mktemp -d /tmp/p1r7b-final-coverage-scan.XXXXXX)
rsync -a --delete \
  --exclude .git \
  --exclude 'muse-cloud/**/target' \
  --exclude 'node_modules' \
  ./ "$tmpdir"/
cd "$tmpdir/muse-cloud"
python3 scripts/p1r-audit-api-coverage.py --check
cd "$tmpdir"
jq '.summary' docs/superpowers/reports/p1r-api-coverage.json
jq -r '.operations[] | select(.domain=="events") | [.domain,.operationId,.implementationStatus,.completionStatus] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
jq -r '[.operations[] | select(.domain=="market") | (.implementationStatus + "/" + .completionStatus)] | group_by(.)[] | [(.[0]), length] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud
git -C .. diff --name-only -- 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

完成条件:

  1. 所有 focused tests 和 gates 有命令、exit code、关键输出。
  2. 必须证明新增目标测试类和混合 gate 中列出的目标测试类实际运行:对应 surefire reports XML 存在且 test count > 0不能仅凭 Maven exit 0 判断。
  3. 必须证明真实 Flyway _test IT P1rAiEventsPublishFlywayMigrationIT 实际运行:target/surefire-reports/TEST-...P1rAiEventsPublishFlywayMigrationIT.xml 存在且 test count > 0命令必须使用独立 _test 库、flyway.postgresql.transactional.lock=falsep1r.flyway.locations=filesystem:sql/muse
  4. source propagation evidence 明确推进到 needs_verification 证据层。
  5. Events / P1R-7 / Market 不被标记 completed。
  6. 受保护文件无改动。
  7. git status 报告必须区分本任务实现、本任务文档留痕和已记录且用户未批准清理的既有 dirty baseline不得为满足最终状态而清理、回退或删除既有 dirty baseline。
  8. spec review PASS + quality review PASS。

P1R-7b 验证命令框架

结论:以下命令是后续 fresh implementer 和 reviewer 的验证框架;本轮文档修订不运行这些命令,不把任何命令结果写成已通过事实。

Protected status

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
git status --short --branch
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

Focused Maven tests

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -o clean install \
  -DskipTests \
  -Dspring-boot.repackage.skip=true \
  -pl muse-server -am
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -pl muse-module-ai/muse-module-ai-server -am \
  -Dtest=MuseAiEventPublishOutboxMapperTest,MuseAiEventPublishOutboxServiceTest,MuseAiEventPublishWorkerTest,MuseAiRuntimeProjectionServiceTest,MuseJobServiceTest \
  -Dsurefire.failIfNoSpecifiedTests=false test
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -pl muse-server -am \
  -Dtest=P1rAiEventsPublishMigrationSqlTest,P1rAiEventsPublishDependencyTest,P1rAiEventsPublishEndToEndTest,P1rApiCoverageReportTest,P1rEventsRealApiGateTest,P1rEventsRouteOwnershipTest,P1rAiRealApiGateTest,P1rAiRouteOwnershipTest \
  -Dsurefire.failIfNoSpecifiedTests=false test

Dependency tree

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -pl muse-module-ai/muse-module-ai-server -am dependency:tree \
  -Dincludes=cn.iocoder.cloud:muse-module-events-api
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -pl muse-module-ai/muse-module-ai-server -am dependency:tree \
  | rg 'muse-module-events-server' && exit 1 || true
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -pl muse-module-events/muse-module-events-server -am dependency:tree \
  | rg 'muse-module-(ai|knowledge|market|member|content)-server' && exit 1 || true

Migration SQL test

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" mvn -pl muse-server -am \
  -Dtest=P1rAiEventsPublishMigrationSqlTest \
  -Dsurefire.failIfNoSpecifiedTests=false test

Flyway _test 独立库

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud
set -a
source ~/.config/muse-repo/infra.env
set +a
export P1R7B_TEST_DB="${P1R7B_TEST_DB:-muse_p1r7b_events_publish_test}"
case "$P1R7B_TEST_DB" in *_test) ;; *) echo "P1R7B_TEST_DB must end with _test"; exit 1;; esac
export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD"
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" timeout 240 mvn test \
  -pl muse-server \
  -Dtest=P1rAiEventsPublishFlywayMigrationIT \
  -Dflyway.postgresql.transactional.lock=false \
  -Djava.net.useSystemProxies=false \
  -DsocksProxyHost= \
  -DsocksProxyPort= \
  -DsocksNonProxyHosts="127.0.0.1|localhost|100.64.*|100.*" \
  -Dhttp.proxyHost= \
  -Dhttps.proxyHost= \
  -Dp1r.flyway.url="jdbc:postgresql://${MUSE_POSTGRES_HOST}:${MUSE_POSTGRES_PORT}/${P1R7B_TEST_DB}" \
  -Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
  -Dp1r.flyway.locations="filesystem:sql/muse"

Coverage 状态只读核查

cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
jq '.summary' docs/superpowers/reports/p1r-api-coverage.json
jq -r '.operations[] | select(.domain=="events") | [.domain,.operationId,.method,.path,.implementationStatus,.completionStatus,.targetStage] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
jq -r '[.operations[] | select(.domain=="market") | (.implementationStatus + "/" + .completionStatus)] | group_by(.)[] | [(.[0]), length] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
git diff -- docs/superpowers/reports/p1r-api-coverage.json docs/superpowers/reports/p1r-api-coverage.md muse-cloud/scripts/p1r-audit-api-coverage.py

Review Gate

每个 Task 的 review 必须记录:

  1. implementer agent 名称或 ID。
  2. spec reviewer 名称或 ID、结果、关键意见。
  3. quality reviewer 名称或 ID、结果、关键意见。
  4. FAIL 修复摘要和 re-review 结果。
  5. 本 Task 的 focused test 命令与结果。
  6. 本 Task 是否触碰受保护文件。

双 PASS 定义:

  1. spec review PASS确认范围、契约、依赖方向、状态机、测试计划符合本执行版。
  2. quality review PASS确认实现最小、可维护、错误路径完整、日志/审计安全、测试有实际断言。
  3. 任一 FAIL 均不得进入下一 Task。

完成条件

执行版计划完成条件:

  1. 本文件存在于 docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation执行版.md
  2. 包含目标、边界、全局架构、非目标、事实/推断/假设、文件路径、数据流、契约、迁移、状态机、幂等、失败重试、日志审计、测试、验证命令、review gate、完成条件、回滚策略。
  3. 每个实现 Task 都包含文件范围、测试范围、验证命令和完成条件。
  4. 不含不可执行空洞表述。
  5. 受保护文件无改动。
  6. 本轮计划修订差异只允许落在本执行版和必要 .agent 状态说明;整体 git status 可以继续包含“当前 dirty 文件处置表”列出的既有 AI 草稿 baseline禁止为满足计划完成条件而清理、回退或删除这些 baseline。

实现阶段完成条件:

  1. AI done/error terminal event 能通过本域 outbox/worker 发布到 Events。
  2. muse_unified_event 有 accepted 记录,且 SSE 可见查询能读到当前 tenant + ownerUserId 的事件。
  3. duplicate/rejected/temporary failure/retry/dead_letter 均有测试。
  4. payload allowlist 和敏感信息拒绝均有测试。
  5. 依赖方向 gate 证明 AI 只依赖 events-apiEvents server 不反向依赖 source owner server。
  6. Coverage 不被改口径Market 32 operations 保持 dedicated / needs_verification
  7. 所有新增测试类、混合 gate 目标测试类和 P1rAiEventsPublishFlywayMigrationIT 都必须有 surefire XML 与 test count > 0 证据,禁止用空测试命令或未匹配测试类的 Maven 成功输出冒充通过。

回滚策略

计划阶段回滚:

  1. 仅删除本执行版文档和 docs/agent-specs/.agent 即可回滚本轮计划产物。
  2. 不触碰审阅版文档和其他 dirty baseline。

实现阶段代码回滚:

  1. 回滚 AI server 对 muse-module-events-api 的依赖。
  2. 回滚 AI outbox DO/Mapper/Service/Worker 与相关测试。
  3. 回滚 MuseAiRuntimeProjectionService 写 outbox 的调用点。
  4. 保留或回滚 migration 必须按数据库发布状态决策:如果 migration 未发布,可随代码一起回滚;如果 migration 已发布,不删除表,改为停止 worker 和保留空表,避免破坏已部署数据库。
  5. 停止 worker 后AI terminal fact 仍保留,业务行为回到 P1R-7a 后的状态;统一 Events 不再接收新的 AI terminal event。
  6. 因故停止 worker 期间已经 claim 为 running 的记录不得人工批量改状态;重新启用后由 claim_expires_at lease 识别 stale running 并进入 reclaim/retry/dead_letter 规则。

计划自检清单

  1. 本计划没有要求修改 OpenAPI 或 scanner。
  2. 本计划没有要求修改 coverage JSON/Markdown 来达成 coverage。
  3. 本计划没有把 Events、P1R-7 或 Market 标为 completed
  4. 本计划没有复用 AI runtime job 或 Market projection outbox。
  5. 本计划明确了 AI publish outbox/job、worker、EventsPublishApi、muse_unified_event、SSE 可见的真实链路。
  6. 本计划每个实现 Task 都要求 fresh subagent implementer + fresh spec review + fresh quality review。
  7. 本计划要求双 PASS 前不得进入下一 Task。
  8. 本计划把执行版完成和实现完成分开:执行版完成仍只是计划;实现完成也只推进 source propagation evidence / needs_verification。