81 KiB
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。
本轮计划修订的硬边界:
- 只允许修改本文件和
docs/agent-specs/.agent。 - 不修改任何 Java / SQL / test /
pom.xml文件。 - 不修改 OpenAPI、scanner、coverage JSON/Markdown。
- 不清理、不回退、不废弃当前 dirty baseline;任何废弃草稿动作都必须等用户明确批准。
- 不引入独立 source 模块,也不把 shared publish 库作为当前 P1R-7b 必做项。
目标与范围边界
目标:
- 在全局 P1R7 Source Owner Propagation 执行版约束下,以 AI task terminal event 为第一切片,证明真实 owner 事实可以传播到统一 Events 投影。
- 只发布用户可见 terminal
done/error摘要事件,payload 严格匹配docs/api-contracts/events/openapi.yaml现有 schema。 - 新增 AI 本域 publish outbox/job,异步 worker 调用
EventsPublishApi。 - 建立可验证链路:AI owner 本域事实 -> AI publish outbox/job -> AI worker ->
EventsPublishApi->muse_unified_event->/app-api/muse/eventsSSE 可见。 - 形成 TDD、focused tests、migration tests、dependency gate、coverage gate、review gate 的可执行实施计划。
范围边界:
- 只在
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0工作。 - 当前分支必须保持
dev/1.0.0。 - 当前 dirty baseline 已包含 AI 草稿代码、迁移、测试和
docs/agent-specs/文档草稿;本子计划只记录处置策略,不清理、回退或改写 dirty/untracked baseline。 - 不在
/Users/qingse/Sync/local-git/oh-my-muse写任何文件。 - 不修改受保护文件:
docs/api-contracts/market/openapi.yamldocs/api-contracts/ai/openapi.yamldocs/api-contracts/knowledge/openapi.yamldocs/api-contracts/events/openapi.yamlmuse-cloud/scripts/p1r-audit-api-coverage.pydocs/superpowers/reports/p1r-api-coverage.jsondocs/superpowers/reports/p1r-api-coverage.md
非目标
- 不实现代码;本文件只定义执行计划。
- 不修改任何 OpenAPI 合同。
- 不修改 scanner 或 coverage JSON/Markdown 来达成 coverage。
- 不把 Events、P1R-7、Market 32 operations 标为
completed。 - 不复用 AI runtime job 作为 Events publish 队列。
- 不复用 Market projection outbox。
- 不同时改造 Knowledge、Market、Member(Account) 的 source owner propagation。
- 不让 Events server 反向依赖 AI / Knowledge / Market / Member / Content server。
- 不让统一 SSE Controller 扫描 AI 业务表。
- 不处理 Knowledge / Market / Account / Content 的 propagation 实现。
- 不新增独立 source 模块,不把 shared publish 库设为本切片必做前置。
- 不把
DuplicateKeyException捕获作为正常幂等路径。
已验证事实
git status --short在正确 worktree 显示 AI P1R-7b 草稿代码、迁移、测试和docs/agent-specs/文档草稿;这些 dirty/untracked 文件尚未通过本子计划 fresh review,也尚未运行本子计划验证。git branch --show-current输出dev/1.0.0。git log -1 --oneline输出fa29753 test(p1r): 收口 Events SSE 门禁。AGENTS.md只引用@CLAUDE.md;CLAUDE.md明确本仓是设计文档 SSOT,Source 传播采用事件驱动 + 各模块自治,统一实时通信为 SSE。docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation审阅版.md推荐 P1R-7b 只选 AI task terminal event 第一切片,done/error为最小事件类型,不进入 completed approval。docs/memorys/2026-06-05-P1R7aEventsSSE真实API收口.md记录 P1R-7a 未接入 source owner propagation,未运行 source owner publish tests,Events 和 Market 仍不得写成completed。docs/superpowers/plans/2026-06-05-P1R-7a-events-sse.md固定跨 owner 发布策略为 source owner 本域 outbox + worker 调用 Events API;Events server 不反向拉取 source owner 数据。- Coverage JSON 当前
summary为:total 233、completed 100、needsVerification 133、incomplete 0、genericPersistence 0、ssePlaceholder 0。 - Coverage operation 明细显示
events streamEvents GET /app-api/muse/events = dedicated / needs_verification / P1R-7 End-to-End Acceptance。 - Coverage operation 明细显示 Market 32 个 operation 的 completionStatus 均为
needs_verification。 EventsPublishApi.publish(EventsPublishReqDTO)已存在于muse-cloud/muse-module-events/muse-module-events-api/src/main/java/cn/iocoder/muse/module/events/api/publish/EventsPublishApi.java。EventsPublishReqDTO字段包含commandId、tenantId、ownerUserId、sourceOwner、sourceType、sourceId、sourceRevision、eventType、resourceType、resourceId、payloadSummary、emittedAt。EventsPublishServiceImpl已实现 commandId 优先、source tuple 次级的幂等回查,非法 payload 写入rejected并不进入可见 SSE 查询。UnifiedEventMapper.selectVisibleEventsForOwner只查询publish_status = accepted、同租户、同 ownerUserId、未删除、sequenceNo大于 cursor 的事件。EventsStreamServiceImpl将doneevent 投影为taskId、suggestionId和可选summary;将errorevent 投影为code、message、可选detail、可选retryable。docs/api-contracts/events/openapi.yaml已声明SSEDoneEvent必填taskId、suggestionId,可选summary;SSEErrorEvent必填code、message,可选detail、retryable。muse_ai_task_event已由 V12 创建,字段包含task_id、sequence_no、event_type、task_status、owner_user_id、actor_user_id、job_id、correlation_id、payload_summary、error_code、error_message、emitted_at,并有 terminal 唯一约束uk_muse_ai_task_event_terminal。MuseAiTaskEventMapper.selectTerminalByTaskId已限定 terminal event 为done/error。MuseAiRuntimeProjectionService.applyRuntimeResponse成功终态写入donetask event,非可重试失败终态写入errortask event;可重试失败只让 job 回到queued,不写 terminal event。MuseJobServiceImpl.appendTaskCancellationEventIfAbsent在 cancel 路径写入eventType = error、taskStatus = cancelled、errorCode = AI_TASK_CANCELLED、errorMessage = AI task cancelled,并且代码注释明确取消原因不能进入 SSE payload。MuseAiRuntimeJobDispatcher采用@Scheduled(initialDelayString=..., fixedDelayString=...)调用dispatchOnce()的最小 worker 生命周期风格。MuseSourceEventServiceImpl.retrySourceEvent只提交source_event_retryjob,并有注释说明不能把来源传播伪造成完成。- 当前 dirty
muse-module-ai-server/pom.xml已出现muse-module-events-api依赖;它仍只是 AI 草稿的一部分,必须在实现阶段重新通过 dependency tree 证明只依赖 events-api、不依赖 events-server。 BaseDO暴露createTime/updateTime/creator/updater/deleted,TenantBaseDO继承BaseDO并新增tenantId;项目 PostgreSQL DDL 约定字段名为create_time/update_time。muse-cloud/sql/muse/V1__init_content_schema.sql中update_updated_at_column()更新的是NEW.update_time。- 当前
muse-cloud/sql/muse文件列表按数字排序最新已包含未提交草稿V17__extend_ai_events_publish_outbox.sql;它不是已通过 migration 证据,后续必须在 P1R-7b 实现阶段决定修订沿用该 V17,或经用户明确批准后废弃重做。 - 当前草稿的
MuseAiEventPublishOutboxServiceImpl存在捕获DuplicateKeyException作为 outbox 创建幂等回查路径的实现痕迹;这违反全局执行版“ON CONFLICT DO NOTHING/insertIgnore+ 回查”的幂等合同,必须修订。 - 当前受保护文件定向
git status --short无输出:
docs/api-contracts/market/openapi.yamldocs/api-contracts/ai/openapi.yamldocs/api-contracts/knowledge/openapi.yamldocs/api-contracts/events/openapi.yamlmuse-cloud/scripts/p1r-audit-api-coverage.pydocs/superpowers/reports/p1r-api-coverage.jsondocs/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;其他审阅版/全局执行版/草稿不能被顺手改写。 |
否;废弃或删除需要 |
废弃规则:
- 上表任何代码、迁移、测试草稿如果后续选择“废弃重做”,必须先向用户说明废弃范围、原因、替代方案和回滚影响,并取得明确批准。
- 未获批准前,不得删除 untracked 文件,不得
git checkout --回退已修改文件,不得用清理 dirty baseline 的方式制造“计划已收口”的假象。
推断
- AI 是 P1R-7b 的最小 owner 切片,因为它已有
muse_ai_task_event终态事实、ownerUserId、tenantId、sequence/replay 基础和 focused tests。 - 需要新增清晰职责的 AI publish outbox/job,因为现有 AI runtime job 的职责是调用 New-API runtime,不是统一 Events 发布补偿。
donepayload 不能直接复用MuseAiRuntimeProjectionService.donePayload,因为现有 payload 缺少 Events OpenAPI 必填的taskId,需要由 outbox mapper/service 从 task event + task/suggestion 事实组合生成 allowlist payload。errorpayload 可以从MuseAiTaskEventDO.errorCode/errorMessage/payloadSummary.retryable归一化生成,但必须先做敏感词和字段 allowlist 过滤。- 如果 worker 与 terminal fact 写入不在同一事务内补 outbox,存在终态事实已落库但未发布的不可追踪缺口;因此 outbox 写入必须和 terminal fact 绑定在同一事务边界内。
假设
- P1R-7b 允许在 AI owner 内新增
muse_ai_event_publish_outbox或等价职责表。 - 实现阶段可以只在 AI 模块新增
muse-module-events-api编译依赖,不需要引入 events-server。 - 实现阶段可以新增 focused integration test 或 mapper-level test 来证明
muse_ai_event_publish_outboxmigration、claim、retry、dead_letter 和 SSE 可见链路。 - P1R
_testPostgreSQL 验证仍使用项目既有~/.config/muse-repo/infra.env方式,但运行前必须确认目标库是独立测试库。
全局架构主线
- AI owner 只写本域事实和本域 outbox,不直接写
muse_unified_event。 - AI worker 只调用
EventsPublishApi,不依赖 Events server 实现类。 - Events owner 只接收发布 DTO、做幂等、payload 校验、脱敏、落库和 SSE 可见性过滤。
- SSE 查询只读
muse_unified_event,不反查 AI 表。 - replay 只能重放 AI 本域 outbox 记录;禁止 operator 或补偿任务绕过 source owner 直接向 Events 注入伪造 payload。
继承全局合同
结论:P1R-7b 不重新发明合同,只把全局 P1R7 Source Owner Propagation 合同投影到 AI terminal event 第一切片。若本文件与全局执行版冲突,以全局执行版为准;后续实现前必须先修订本文件并重新 review。
依赖合同
muse-module-ai-server只允许新增或保留muse-module-events-api依赖。muse-module-ai-server禁止依赖muse-module-events-server。muse-module-events-server禁止依赖 AI / Knowledge / Market / Member / Content server。muse-server可以装配 AI server 与 Events server,但不能把装配关系写成业务反向依赖。- P1R-7b 实现前和实现后都必须运行 dependency tree 验证;验证命令只作为框架写入本计划,本轮不运行。
发布 envelope 合同
commandId必须稳定、短、幂等,推荐ai_evt:<sha256-32>,长度<= 128。tenantId、ownerUserId、sourceOwner=ai、sourceType=ai_task_event、sourceId、sourceRevision、eventType、resourceType/resourceId、payloadSummary、emittedAt都必须在 outbox 中可追溯。eventType只允许使用 Events OpenAPI 既有done/error,不得新增 AI 私有事件类型绕过合同。sourceRevision使用 terminal eventsequence_no字符串;为空时不得创建可发布 outbox。emittedAt使用 AI terminal fact 发生时间,不使用 worker 发布时间替代事实时间。
用户可见事件判定
- P1R-7b 只发布用户可见 terminal
done/error摘要。 - 可重试 runtime failure、worker heartbeat、projection rebuild、source retry job、内部调度状态不进入统一 SSE。
- cancellation 只能作为
error摘要发布,且只包含AI_TASK_CANCELLED、AI task cancelled、retryable=false,不得包含用户提交的取消原因或内部审计备注。 - payload 需要完整 prompt、provider raw、正文、授权详情、风控原始证据才能解释时,必须 fail-closed,不得发布到 SSE。
- 本切片不处理 Knowledge / Market / Account / Content 的用户可见事件。
Outbox 合同
- AI outbox 必须支持
queued/running/retryable/published/dead_letter或全局执行版认可的等价状态。 - claim 必须使用
FOR UPDATE SKIP LOCKED或等价原子领取机制,并覆盖 queued、到期 retryable、stale running。 - claim 时递增
attempt_count;失败处理不得二次递增 attempt。 - 必须有
max_attempt、next_retry_at、claimed_at、claim_expires_at、claim 索引、固定退避策略。 - 幂等插入必须使用
ON CONFLICT DO NOTHING/insertIgnore+ 回查;禁止捕获DuplicateKeyException作为正常幂等路径。 dead_letter不自动 replay;只有确认是临时故障的记录才能由用户批准后重置为retryable。- 状态转换必须有中文日志,包含 owner、tenantId、ownerUserId、source tuple、outboxId、attempt、状态转换和安全错误摘要。
Payload 合同
donepayload 只允许taskId、suggestionId、可选安全短summary。errorpayload 只允许code、message、可选安全detail、可选retryable。taskId和suggestionId必须满足 Events OpenAPI int64 语义;不满足时 fail-closed 到dead_letter,不得调用EventsPublishApi。- 禁止 prompt、userInstruction、provider request/response raw body、token、apiKey、authorization、bearer、secret、runtimePermissionEnvelope、source private context、full output、contentSnapshot 正文进入 payload。
- payload invalid、Events rejected、Events blocked 都不得进入 SSE 可见查询。
涉及模块与文件路径
必须只读核对
AGENTS.mdCLAUDE.mddocs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation审阅版.mddocs/memorys/2026-06-05-P1R7aEventsSSE真实API收口.mddocs/superpowers/plans/2026-06-05-P1R-7a-events-sse.mddocs/api-contracts/events/openapi.yamldocs/superpowers/reports/p1r-api-coverage.jsondocs/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.xmlmuse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/dataobject/muse/MuseAiEventPublishOutboxDO.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/dal/mysql/muse/MuseAiEventPublishOutboxMapper.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxService.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceImpl.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishWorker.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionService.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/main/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceImpl.javamuse-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.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishOutboxServiceTest.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiEventPublishWorkerTest.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseAiRuntimeProjectionServiceTest.javamuse-cloud/muse-module-ai/muse-module-ai-server/src/test/java/cn/iocoder/muse/module/ai/application/muse/MuseJobServiceTest.javamuse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishMigrationSqlTest.javamuse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishFlywayMigrationIT.javamuse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishDependencyTest.javamuse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiEventsPublishEndToEndTest.java
必须保持不修改
docs/api-contracts/market/openapi.yamldocs/api-contracts/ai/openapi.yamldocs/api-contracts/knowledge/openapi.yamldocs/api-contracts/events/openapi.yamlmuse-cloud/scripts/p1r-audit-api-coverage.pydocs/superpowers/reports/p1r-api-coverage.jsondocs/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
依赖方向:
muse-module-ai-server可以新增muse-module-events-api依赖。muse-module-ai-server禁止依赖muse-module-events-server。muse-module-events-server禁止依赖 AI / Knowledge / Market / Member / Content server。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 规则:
muse_ai_task_event.task_id是VARCHAR,但 SSEdonepayload 的taskId必须是 int64 语义数字;P1R-7b 最小规则要求done和errorterminal event 在进入 Events publish 前都先校验 taskId 可解析为 int64。- 非数字 taskId 必须 fail-closed 到 AI outbox
dead_letter,last_error_code = AI_EVENTS_TASK_ID_NOT_NUMERIC,不得调用EventsPublishApi,不得发布done或error到 SSE 可见事件。 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"
}
规则:
taskId必须是 int64 语义数字。suggestionId必须是 int64 语义数字;如果 task 没有 suggestionId,则不得发布done,outbox 写入或保持dead_letter,last_error_code = AI_EVENTS_DONE_PAYLOAD_INCOMPLETE,并且不得进入 SSE 可见事件。summary可选,必须是安全短文本;不得来自 provider raw body 或完整输出正文。
error payload allowlist:
{
"code": "AI_NEW_API_UNAVAILABLE",
"message": "AI task failed",
"detail": "runtime unavailable",
"retryable": false
}
规则:
code必须来自 AI 本域归一化错误码,缺失时使用AI_TASK_FAILED。message必须是安全错误摘要,不能包含 provider 原始响应、prompt、token、授权头或私有上下文。detail可选,只允许开发安全摘要;如包含敏感关键词则丢弃。retryable可选;terminal error 对应非可重试失败时为false。- cancellation terminal event 是
MuseJobServiceImpl.appendTaskCancellationEventIfAbsent写出的合同内error事件,payload allowlist 只能包含code = AI_TASK_CANCELLED、message = AI task cancelled、retryable = false;不得包含detail,不得携带 admin/app 用户提交的取消原因。
禁止字段:
- prompt / userInstruction 原文。
- provider raw body / provider request body / provider response body。
- token / apiKey / authorization / bearer / secret。
- runtimePermissionEnvelope 原文。
- source private context / full output / contentSnapshot 正文。
- 未在
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 |
约束与索引:
uk_muse_ai_event_publish_outbox_command:UNIQUE (tenant_id, command_id)。uk_muse_ai_event_publish_outbox_source:UNIQUE (tenant_id, task_id, source_revision, event_type)。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。idx_muse_ai_event_publish_outbox_owner_status:(tenant_id, owner_user_id, publish_status, create_time)。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 --> [*]
状态含义:
queued:待发布,attempt_count = 0,claimed_at/claim_expires_at为空。running:被 worker 原子领取,必须写claimed_at = now、claim_expires_at = now + lease,并有中文日志记录 outbox id、tenantId、taskId、eventType、attempt、claimExpiresAt。published:EventsCommonResult成功且EventsPublishRespDTO.publishStatus = accepted,或publishStatus = accepted且duplicate = true;必须记录published_event_id和published_sequence_no。retryable:临时失败,设置next_retry_at,保留安全错误码和摘要。dead_letter:payload 不合规、Events 返回 rejected/blocked、或超过最大重试次数;不得进入 SSE 可见事件。
注意:
- AI outbox 本地
publish_status不存在blocked;Events 侧accepted/rejected/blocked是统一事件投影层状态,不是 AI outbox 本地状态。 - worker 不能只看
CommonResult成功/失败;必须读取EventsPublishRespDTO.publishStatus并按accepted/duplicate/rejected/blocked分流。CommonResult成功但publishStatus为空或未知时,必须 fail-closed 写retryable或dead_letter,不得伪造成published。 - claim 查询必须覆盖三类可领取记录:
queued、retryable AND next_retry_at <= now、running AND claim_expires_at < now。未到期的running不得被领取。 - attempt 语义冻结为 claim 次数:每次从
queued、到期retryable或 stalerunning被 claim 时统一attempt_count + 1;失败处理不得再次递增 attempt,避免一次 publish 失败被计算两次。 - stale running reclaim 采用
running -> running最小语义:重领时刷新claimed_at/claim_expires_at并递增 attempt;如果递增后超过max_attempt,必须进入dead_letter,错误码AI_EVENTS_PUBLISH_RETRY_EXHAUSTED,避免 crash-after-claim 无限循环。
幂等策略
- AI outbox 创建幂等:同一
(tenant_id, task_id, source_revision, event_type)只能有一条 outbox。 - command 幂等:同一
(tenant_id, command_id)只能有一条 outbox。 - Events publish 幂等:同一
commandId或同一 source tuple 重放必须得到相同eventId / sequenceNo。 - worker 幂等:worker 重试只读取 outbox 记录,不重新读取不稳定上下文构造不同 payload。
- terminal fact 幂等:
MuseAiTaskEventMapper.selectTerminalByTaskId和 DB terminal 唯一约束继续阻止同一 task 多个 terminal event。 - replay 幂等:人工 replay 只允许把
dead_letter中确认为临时故障的记录重置为retryable;AI_EVENTS_DONE_PAYLOAD_INCOMPLETE等 payload 不合规记录不得直接 replay。
失败、重试与 dead_letter
临时失败:
EventsPublishApi调用抛出网络、超时、RPC unavailable 等异常。CommonResult表示临时不可用或无响应。- worker 处理时发生非 payload/contract 类异常。
处理方式:
attempt_count已在 claim 时递增,失败处理不得再次+ 1。- 当前
attempt_count未达到上限时写retryable,next_retry_at = now + backoff,并清空或保留 lease 字段时必须有一致测试;推荐清空claimed_at/claim_expires_at,让下一次 claim 只由next_retry_at控制。 - backoff 建议最小固定序列
1s, 2s, 4s, 8s, 16s,超出使用 16s;实现代码必须用中文注释说明这是 P1R-7b 最小退避,不引入全局任务框架。 - 当前
attempt_count达到或超过上限写dead_letter,错误码AI_EVENTS_PUBLISH_RETRY_EXHAUSTED。
stale running:
- worker crash-after-claim 后,记录会停留在
running,直到claim_expires_at < now。 - stale running 由后续 claim 原子重领,状态保持
running -> running,刷新claimed_at/claim_expires_at,并按 claim 次数递增attempt_count。 - stale running 重领前或重领后发现
attempt_count超过max_attempt,必须写dead_letter,不得无限重领。 claim_expires_at为空的running属于不合规历史状态;实现阶段必须 fail-closed 到retryable或dead_letter,并用测试固定规则,不得被永久卡住。
永久失败:
- payload 缺少 OpenAPI 必填字段。
- payload 包含 prompt、provider raw body、token、authorization、bearer、secret 等敏感内容。
EventsPublishApi返回rejected或blocked。
处理方式:
- 立即写
dead_letter。 - 不重试。
- 记录安全日志和审计摘要。
- 不向 SSE 伪造事件。
Worker 生命周期与停止策略
MuseAiEventPublishWorker采用现有MuseAiRuntimeJobDispatcher的最小生命周期风格:@Scheduled(initialDelayString = "${muse.ai.events.publish-worker.initial-delay-ms:1000}", fixedDelayString = "${muse.ai.events.publish-worker.fixed-delay-ms:1000}")只调用dispatchOnce()。- 新增配置开关
muse.ai.events.publish-worker.enabled,默认true;实现阶段如项目配置类已有更适合的布尔默认约定,必须保持默认启用或在实现报告写明偏差原因。 enabled=false时 scheduled 入口和手动入口都不得 claim outbox;enabled=true时 scheduled 入口调用dispatchOnce()并按 claim 结果处理一批或一条记录。dispatchOnce()必须保留可测试的同步方法,测试不依赖真实时间、不等待 scheduler。- 回滚或应急停止方式:关闭
muse.ai.events.publish-worker.enabled,或在更高风险回滚中移除 worker bean 装配。已经 claim 为running的记录不做人工改写;重新启用 worker 后由claim_expires_atlease 回收 stale running,并按 retry/dead_letter 规则继续处理。
日志、审计与可观测性
实现阶段必须补中文日志和注释:
- 创建 outbox:记录
tenantId、ownerUserId、taskId、eventType、sourceRevision、commandId,不记录 payload 正文。 - worker claim:记录 outbox id、attempt、状态转换、claimedAt、claimExpiresAt,以及是否 stale running reclaim。
- publish 成功:记录 outbox id、
publishedEventId、publishedSequenceNo、duplicate。 - publish rejected/blocked:记录 outbox id、错误码、安全摘要。
- temporary failure:记录 outbox id、错误类型、nextRetryAt。
- dead_letter:记录 outbox id、最终错误码、attemptCount。
- payload sanitizer 或 allowlist 拒绝:必须有安全日志,不能输出原始敏感值。
审计要求:
- 如 AI 现有
MuseAiAuditService适用于内部业务审计,则记录operationId = aiEventsPublish或实现阶段冻结的等价 operation id。 - 审计 request/response summary 只能包含 outbox id、taskId、eventType、publishStatus、publishedEventId、publishedSequenceNo、errorCode。
- 审计不阻塞主业务事实落库;审计失败不得伪造成 publish 成功。
TDD 与 review gate 总规则
P1R-7b 子计划 gate:
- 本文件必须先完成 fresh spec review。
- 本文件必须再完成 fresh quality / feasibility review。
- 双 PASS 前不得继续当前 AI 草稿实现,不得运行当前草稿测试,不得提交或推送。
- 若任一 review FAIL,只修订本文件和必要
.agent状态说明;不得通过改代码、改 OpenAPI、改 scanner、改 coverage 绕过 review。
每个实现 Task 必须按以下顺序执行:
- fresh subagent implementer 领取单个 Task。
- 先写失败测试或门禁。
- 运行 focused test,确认因目标缺口失败。
- 写最小实现。
- 运行 focused tests,确认通过。
- 运行该 Task 指定验证命令。
- fresh spec review。
- fresh quality review。
- 双 PASS 后才允许进入下一 Task。
任何 Task 若出现 FAIL:
- implementer 只修当前 Task 范围内问题。
- 修复后重新跑 focused tests。
- 重新进行 fresh spec review + fresh quality review。
- 不得用 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 文件。
步骤:
- 只读核对全局审阅版和全局执行版已把 P1R-7b 定义为 AI terminal event 第一切片。
- 只读核对本文件是否继承依赖合同、发布 envelope 合同、用户可见事件判定、outbox 合同和 payload 合同。
- 只读核对当前 dirty 文件处置表是否覆盖 AI 草稿代码、迁移、测试和
docs/agent-specs/*。 - 只读核对
DuplicateKeyException幂等路径是否被列为必须修订点。 - 只读核对本文件是否明确双 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
完成条件:
- Fresh spec review PASS。
- Fresh quality / feasibility review PASS。
- protected files 定向状态无改动。
- 输出下一步是“由 fresh implementer 处理 Task 1/Task 2”,不是直接继续当前 AI 草稿。
Task 1:Preflight 与依赖/现状冻结
执行角色: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/**
步骤:
- 记录
pwd、git status --short --branch、git branch --show-current、git log -1 --oneline。 - 定向检查受保护文件和 coverage 报告无改动。
- 读取 coverage summary 和 Events/Market operation 明细。
- 检查
muse-module-ai-server/pom.xml当前 dirty 草稿中的muse-module-events-api依赖,并确认没有muse-module-events-server依赖。 - 检查
muse-cloud/sql/muse当前最新 migration 版本,区分已推送基线和当前未提交 V17 草稿。 - 检查 Events API、Events publish service、UnifiedEvent mapper、Events stream service 的现状。
- 检查 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
完成条件:
- 工作区、分支、dirty baseline 与本执行版约束一致。
- 受保护文件和 coverage 报告无改动。
- AI 当前
muse-module-events-api/muse-module-events-server依赖状态有 live 证据。 - 最新 migration 版本有 live 证据,且必须按
V后数字排序;如果当前 V17 草稿仍存在,必须把它列为“修订沿用候选”,不能误写成已通过 migration。 - spec review PASS + quality review PASS。
Task 2:AI 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。
测试范围:
- SQL 文本测试必须断言表名、字段、唯一约束、claim 索引、状态 CHECK、event_type CHECK、
update_timetrigger,且source_event_id字段类型唯一冻结为BIGINT NOT NULL。 - SQL 文本测试必须断言 outbox schema 能支持后续 claim:
claimed_at TIMESTAMP、claim_expires_at TIMESTAMP两个 lease 字段存在,publish_status、next_retry_at、attempt_count字段存在,且 claim 索引包含publish_status, claim_expires_at, next_retry_at, create_time, id。 - SQL 文本测试必须断言 outbox 表使用项目约定字段
creator/create_time/updater/update_time/deleted,并通过断言BaseDO.createTime/updateTime、TenantBaseDO extends BaseDO或等价反射/文本证据锁定项目基类约定。 - SQL 文本测试必须断言
update_updated_at_column()更新update_time,并断言 outbox migration 不使用非项目约定的时间字段名。 - SQL 文本测试必须断言
command_id VARCHAR(128),并配套 service/mapper 测试冻结ai_evt:<sha256-32>或等价短 hash 格式,生成长度<= 128。 - SQL 文本测试只断言 schema 层 claim 支撑能力:
publish_status状态 CHECK、attempt_count非负约束、lease 字段和 retry 字段可空性、claim 索引字段顺序;不得要求断言具体 mapper claim SQL 或 mapper 注解。 - Flyway
_test必须在独立测试库执行,不能 clean 开发库或真实库。 - migration 必须和 V12/V16 共存,不修改既有表语义。
- 如果沿用当前 V17 草稿,必须先修订所有不符合全局 outbox 合同的字段、索引、状态和幂等语义;不能因为文件已存在就跳过 TDD 失败测试和 fresh review。
TDD 步骤:
- 先写或修订
P1rAiEventsPublishMigrationSqlTest,让它针对当前 V17 草稿的真实缺口失败;如果 V17 草稿被用户批准废弃后不存在,则测试应因 migration 未定义失败。 - 运行 focused test,确认失败原因来自 schema 合同缺口,而不是测试未运行。
- 修订或新增 migration,包含本执行版字段、lease 字段、约束、索引和 trigger。
- 运行 SQL 文本测试通过。
- 新增或扩展 Flyway IT,证明所有 migrations 在独立
_test库可执行。 - 运行 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"
完成条件:
- migration 版本号来自 Task 1 live 查证。
- 表字段覆盖本执行版迁移计划要求;
source_event_id必须是BIGINT NOT NULL,引用muse_ai_task_event.id,如果实现阶段发现 DO/mapper 名称不同,也必须先 live 查证后保持 Long id 语义。 - 唯一约束覆盖 commandId 和 task/source/event tuple。
- SQL 文本测试通过,且 Task 2 完成证据只来自 migration SQL 与 migration tests,不依赖 Task 4 才新增或修改的 mapper 文件。
- 独立
_testFlyway IT 通过,并断言目标 version、description、已执行 migration count 随 Task 1 live 下一版本号更新;保留_test库名保护,非_test库必须 fail-fast。 - SQL 文本测试和 Flyway IT 都必须证明目标测试类实际运行:
target/surefire-reports/TEST-...P1rAiEventsPublishMigrationSqlTest.xml与TEST-...P1rAiEventsPublishFlywayMigrationIT.xml存在,且各自 test count > 0。 - spec review PASS + quality review PASS。
Task 3:AI 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
测试范围:
- DO 字段映射覆盖 migration 字段。
- Mapper 能按 commandId 和 source tuple 查重。
- Service 只为
done/errorterminal event 创建 outbox;cancelledtask status 只能通过eventType = error发布,不新增 cancelled event type。 - Service 对
done缺少 suggestionId 的事件写入或保持dead_letter,记录last_error_code = AI_EVENTS_DONE_PAYLOAD_INCOMPLETE,并证明不会发布到 SSE 可见事件。 - Service 对非数字 taskId 必须 fail-closed 写入或保持
dead_letter,last_error_code = AI_EVENTS_TASK_ID_NOT_NUMERIC,并证明不会调用EventsPublishApi,不会发布done或error到 SSE 可见事件。 - Service 生成 payload allowlist,不包含 prompt/provider raw body/token/auth/private context/full output。
- Service 生成
command_id必须使用ai_evt:<sha256-32>或等价短 hash 格式,测试覆盖长度<= 128,禁止长拼接格式作为推荐或默认实现。 MuseAiRuntimeProjectionService在写 terminal task event 的同一事务内调用 outbox service。MuseJobServiceImpl.appendTaskCancellationEventIfAbsent在写 cancellationerrorterminal event 的同一事务内调用 outbox service;payload 只能包含code=AI_TASK_CANCELLED、message=AI task cancelled、retryable=false,不能携带用户取消原因。- 可重试 failure 不写 terminal event,也不写 Events publish outbox。
- outbox 创建幂等必须通过 mapper
insertIgnore/ SQLON CONFLICT DO NOTHING返回插入行数,再按 commandId/source tuple 回查;测试必须禁止把DuplicateKeyException作为正常分支。
TDD 步骤:
- 针对当前 dirty DO/Mapper/Service/Test 草稿写或修失败测试,失败点必须来自真实合同缺口,而不是“类不存在”。
- 优先写 duplicate 幂等失败测试:当前草稿如果仍捕获
DuplicateKeyException作为正常路径,测试必须失败;目标路径必须是insertIgnore/ON CONFLICT DO NOTHING返回 0 + commandId/source tuple 回查。 - 写 projection 失败测试,断言 terminal done/error 后必须创建 outbox;如果当前草稿已有调用点,测试必须核对事务边界、sourceEventId、payload 和幂等语义。
- 写
MuseJobServiceTest失败测试,断言 cancel admin/app 触发appendTaskCancellationEventIfAbsent后必须同事务创建 outbox,且 cancellation payload 不包含 request reason。 - 写 payload 安全失败测试,构造包含敏感字段的 payload,断言 outbox payload 不包含敏感值。
- 写非数字 taskId 失败测试,断言 taskId 为
abc、UUID 或带前缀字符串时 fail-closed 到dead_letter,不会产生 SSE 可见事件。 - 写 commandId 长度测试,断言
ai_evt:<sha256-32>或等价短 hash 格式长度<= 128,且不使用长拼接格式。 - 运行 focused tests,确认失败原因来自上述真实缺口且测试类实际运行。
- 修订 DO/Mapper/Service 最小实现,先实现
insertIgnore/ON CONFLICT DO NOTHING+ 回查,再接 terminal fact 调用点。 - 复核
muse-module-ai-server/pom.xml当前 dirty 草稿中的muse-module-events-api依赖;保留或修订时只允许 api,不允许 server。 - 在
MuseAiRuntimeProjectionService.appendTaskEvent写入 terminal event 后调用 outbox service;关键事务和安全 allowlist 必须有中文注释。 - 在
MuseJobServiceImpl.appendTaskCancellationEventIfAbsent写入 cancellationerrorterminal event 后调用 outbox service;中文注释必须说明取消原因不能进入统一 Events payload。 - 运行 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
完成条件:
muse-module-ai-server只新增muse-module-events-api依赖。- runtime done/error terminal fact 与 outbox 写入在同一事务链路内。
- cancellation error terminal fact 与 outbox 写入在同一事务链路内,且 cancellation payload 只包含
AI_TASK_CANCELLED、AI task cancelled、retryable=false三个安全事实。 - payload allowlist 测试覆盖 done/error/cancellation、非数字 taskId fail-closed、commandId 短 hash 长度
<= 128和敏感字段排除。 MuseAiEventPublishOutboxServiceImpl不捕获DuplicateKeyException作为正常幂等路径;对应测试证明 duplicate 走insertIgnore=0+ 回查。- focused tests 通过,并必须证明
MuseAiEventPublishOutboxMapperTest、MuseAiEventPublishOutboxServiceTest、MuseAiRuntimeProjectionServiceTest、MuseJobServiceTest实际运行:对应 surefire reports XML 存在且 test count > 0。 - dependency tree 证明 AI 不依赖 events-server。
- spec review PASS + quality review PASS。
Task 4:AI 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
测试范围:
- Mapper claim 使用单条 SQL 原子
UPDATE ... WHERE id=(SELECT ... FOR UPDATE SKIP LOCKED) RETURNING *或等价机制。 - claim 只领取
queued、到期retryable、以及running AND claim_expires_at < now的 stale running,不领取未到期running、published、dead_letter。 - claim 时必须原子写
publish_status = running、claimed_at = now、claim_expires_at = now + lease、attempt_count = attempt_count + 1。 - stale running reclaim 必须证明
running -> running、刷新 lease、递增 attempt;递增后超过 max attempt 时进入dead_letter,错误码AI_EVENTS_PUBLISH_RETRY_EXHAUSTED。 - worker 的
@Scheduled(initialDelayString=..., fixedDelayString=...)入口只调用dispatchOnce()。 muse.ai.events.publish-worker.enabled=false时 worker 不 claim;enabled=true时 scheduled 入口调用dispatchOnce()。- worker 对
CommonResult成功且publishStatus=accepted标记published,写入published_event_id/published_sequence_no。 - worker 对
publishStatus=accepted且duplicate=true标记published,复用返回 eventId/sequenceNo。 - worker 对
publishStatus=rejected/blocked标记dead_letter。 - worker 对
publishStatus为空或未知 fail-closed,不得标记published。 - worker 对 CommonResult 失败、调用异常或临时不可用标记
retryable并设置next_retry_at,失败处理不得再次递增attempt_count。 - worker 达到最大 attempt 后标记
dead_letter。 - worker 不把 payload 或敏感错误原文写入日志/审计。
TDD 步骤:
- 写 worker 失败测试,mock
EventsPublishApi返回publishStatus=accepted/accepted+duplicate/rejected/blocked/unknown、CommonResult 失败和抛异常。 - 写生命周期失败测试,断言
enabled=false不 claim,enabled=true的 scheduled 入口调用dispatchOnce()。 - 写 mapper claim SQL 反射测试,断言 SQL 包含
FOR UPDATE SKIP LOCKED、claimed_at、claim_expires_at、attempt_count + 1、queued、到期retryable、stalerunning、next_retry_at条件。 - 写 stale running 测试,断言未到期 running 不领取,
claim_expires_at < now的 running 可 reclaim,且超过 max attempt 进入dead_letter。 - 运行 focused tests,确认失败。
- 实现 mapper claim、worker
dispatchOnce()、@Scheduledscheduled 入口、配置开关、状态转换、lease 和日志。 - 实现固定 backoff、claim lease 和最大 attempt,中文注释说明 P1R-7b 最小策略。
- 运行 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
完成条件:
- worker 能处理 accepted、duplicate、rejected、blocked、temporary failure、retry exhausted。
- worker 能证明
enabled=false不 claim,enabled=true调dispatchOnce();@Scheduled(initialDelayString=..., fixedDelayString=...)入口存在且只负责触发。 - worker 能证明不只看
CommonResult成功失败,而是按EventsPublishRespDTO.publishStatus的accepted/duplicate/rejected/blocked分流。 - claim 具备并发安全语义。
- 日志不泄露 payload 原文或敏感错误。
- focused tests 通过,并必须证明
MuseAiEventPublishWorkerTest、MuseAiEventPublishOutboxMapperTest实际运行:对应 surefire reports XML 存在且 test count > 0。 - 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。
测试范围:
- 构造 AI terminal
donefact,写入 outbox,worker 调用EventsPublishApi,Events 写入muse_unified_eventaccepted。 - 查询
EventsPublishService.listVisibleEvents或EventsStreamService.streamEvents,证明当前 tenant + ownerUserId 可见。 - 构造 AI terminal
errorfact,证明 error payload 可见且不泄露敏感 detail。 - 构造重复 publish,证明 Events 返回同一
eventId/sequenceNo,outbox 仍为published。 - 构造 rejected payload,证明 outbox dead_letter 且 SSE 不可见。
TDD 步骤:
- 先写 E2E focused test,断言当前链路缺少 AI outbox/worker 时失败。
- 接入 Task 3/4 实现后的最小 fixture。
- 运行 E2E focused test。
- 若 E2E 需要 Spring 装配,优先复用现有 P1R gate 测试风格;不要引入新测试框架。
验证命令:
cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -pl muse-server -am \
-Dtest=P1rAiEventsPublishEndToEndTest test
完成条件:
- AI done/error 两类 terminal event 均有 source owner -> Events -> SSE 可见证据。
- duplicate/rejected 安全路径有 focused evidence。
- 不需要修改 OpenAPI/scanner/coverage。
- 必须证明
P1rAiEventsPublishEndToEndTest实际运行:对应 surefire reports XML 存在且 test count > 0。 - 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 - 运行并前后检查 diff:
muse-cloud/scripts/p1r-audit-api-coverage.py - Task 6 scanner 必须在临时隔离副本中运行;当前 worktree 的 coverage JSON/Markdown 与 scanner 脚本仍是受保护文件,运行前后 diff 必须为空。
- 检查:受保护文件和 coverage 报告状态
测试范围:
- AI server 依赖
muse-module-events-api。 - AI server 不依赖
muse-module-events-server。 - Events server 不依赖 AI / Knowledge / Market / Member / Content server。
P1rApiCoverageReportTest、P1rEventsRealApiGateTest、P1rEventsRouteOwnershipTest、P1rAiRealApiGateTest、P1rAiRouteOwnershipTest仍通过。- Coverage scanner
--check必须真实运行,但只能在临时隔离副本写报告;当前 worktree 运行前后受保护文件 diff 必须为空。不得通过修改 scanner/coverage 达成状态。
TDD 步骤:
- 写或修订 dependency test,断言 live 依赖方向:AI server 可以有
muse-module-events-api,不得有muse-module-events-server;Events server 不得依赖 AI / Knowledge / Market / Member / Content server。 - 运行 dependency test,确认它针对当前 dirty
pom.xml真实依赖状态做机械断言;不得再以缺失态作为预期失败前提。 - 运行 P1R focused gates。
- 用
rsync或等价方式复制当前 worktree 到/tmp临时隔离目录,排除.git、target、node_modules等构建产物;在副本中运行 coverage scanner--check,记录副本中的 scanner exit code、summary、Events/Market 状态,并检查当前 worktree 受保护文件 diff 仍为空。 - 运行 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'
完成条件:
- 依赖方向 gate 通过。
- P1R focused gates 通过。
- scanner
--check在临时隔离副本中 exit 0,副本报告 summary 符合 P1R-7b 边界,当前 worktree 运行前后受保护文件和 coverage 报告 diff 为空。 - Coverage 状态不得被推进到 completed。
- Market 32 operations 保持
dedicated / needs_verification。 - 必须证明
P1rAiEventsPublishDependencyTest以及本命令列出的 P1R gate 测试类实际运行:对应 surefire reports XML 存在且 test count > 0。 - 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 状态。
测试范围:
- AI module focused tests。
- Events module focused tests。
- P1R gate tests。
- Flyway
_test。 - dependency tree。
- coverage scanner。
- reactor build。
- 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
完成条件:
- 所有 focused tests 和 gates 有命令、exit code、关键输出。
- 必须证明新增目标测试类和混合 gate 中列出的目标测试类实际运行:对应 surefire reports XML 存在且 test count > 0;不能仅凭 Maven exit 0 判断。
- 必须证明真实 Flyway
_testITP1rAiEventsPublishFlywayMigrationIT实际运行:target/surefire-reports/TEST-...P1rAiEventsPublishFlywayMigrationIT.xml存在且 test count > 0;命令必须使用独立_test库、flyway.postgresql.transactional.lock=false、p1r.flyway.locations=filesystem:sql/muse。 - source propagation evidence 明确推进到 needs_verification 证据层。
- Events / P1R-7 / Market 不被标记 completed。
- 受保护文件无改动。
- git status 报告必须区分本任务实现、本任务文档留痕和已记录且用户未批准清理的既有 dirty baseline;不得为满足最终状态而清理、回退或删除既有 dirty baseline。
- 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 必须记录:
- implementer agent 名称或 ID。
- spec reviewer 名称或 ID、结果、关键意见。
- quality reviewer 名称或 ID、结果、关键意见。
- FAIL 修复摘要和 re-review 结果。
- 本 Task 的 focused test 命令与结果。
- 本 Task 是否触碰受保护文件。
双 PASS 定义:
- spec review PASS:确认范围、契约、依赖方向、状态机、测试计划符合本执行版。
- quality review PASS:确认实现最小、可维护、错误路径完整、日志/审计安全、测试有实际断言。
- 任一 FAIL 均不得进入下一 Task。
完成条件
执行版计划完成条件:
- 本文件存在于
docs/agent-specs/2026-06-06-P1R7bSourceOwnerPropagation执行版.md。 - 包含目标、边界、全局架构、非目标、事实/推断/假设、文件路径、数据流、契约、迁移、状态机、幂等、失败重试、日志审计、测试、验证命令、review gate、完成条件、回滚策略。
- 每个实现 Task 都包含文件范围、测试范围、验证命令和完成条件。
- 不含不可执行空洞表述。
- 受保护文件无改动。
- 本轮计划修订差异只允许落在本执行版和必要
.agent状态说明;整体git status可以继续包含“当前 dirty 文件处置表”列出的既有 AI 草稿 baseline,禁止为满足计划完成条件而清理、回退或删除这些 baseline。
实现阶段完成条件:
- AI done/error terminal event 能通过本域 outbox/worker 发布到 Events。
muse_unified_event有 accepted 记录,且 SSE 可见查询能读到当前 tenant + ownerUserId 的事件。- duplicate/rejected/temporary failure/retry/dead_letter 均有测试。
- payload allowlist 和敏感信息拒绝均有测试。
- 依赖方向 gate 证明 AI 只依赖 events-api,Events server 不反向依赖 source owner server。
- Coverage 不被改口径;Market 32 operations 保持
dedicated / needs_verification。 - 所有新增测试类、混合 gate 目标测试类和
P1rAiEventsPublishFlywayMigrationIT都必须有 surefire XML 与 test count > 0 证据,禁止用空测试命令或未匹配测试类的 Maven 成功输出冒充通过。
回滚策略
计划阶段回滚:
- 仅删除本执行版文档和
docs/agent-specs/.agent即可回滚本轮计划产物。 - 不触碰审阅版文档和其他 dirty baseline。
实现阶段代码回滚:
- 回滚 AI server 对
muse-module-events-api的依赖。 - 回滚 AI outbox DO/Mapper/Service/Worker 与相关测试。
- 回滚
MuseAiRuntimeProjectionService写 outbox 的调用点。 - 保留或回滚 migration 必须按数据库发布状态决策:如果 migration 未发布,可随代码一起回滚;如果 migration 已发布,不删除表,改为停止 worker 和保留空表,避免破坏已部署数据库。
- 停止 worker 后,AI terminal fact 仍保留,业务行为回到 P1R-7a 后的状态;统一 Events 不再接收新的 AI terminal event。
- 因故停止 worker 期间已经 claim 为
running的记录不得人工批量改状态;重新启用后由claim_expires_atlease 识别 stale running 并进入 reclaim/retry/dead_letter 规则。
计划自检清单
- 本计划没有要求修改 OpenAPI 或 scanner。
- 本计划没有要求修改 coverage JSON/Markdown 来达成 coverage。
- 本计划没有把 Events、P1R-7 或 Market 标为
completed。 - 本计划没有复用 AI runtime job 或 Market projection outbox。
- 本计划明确了 AI publish outbox/job、worker、EventsPublishApi、
muse_unified_event、SSE 可见的真实链路。 - 本计划每个实现 Task 都要求 fresh subagent implementer + fresh spec review + fresh quality review。
- 本计划要求双 PASS 前不得进入下一 Task。
- 本计划把执行版完成和实现完成分开:执行版完成仍只是计划;实现完成也只推进 source propagation evidence / needs_verification。