30 KiB
P1R7d Market Source Owner Propagation 执行版
结论
P1R-7d 只执行 Market source owner propagation 的第三 owner 切片:把 Market governance terminal fact 转成 Market 本域 Events publish outbox,再由 Market worker 调用 EventsPublishApi 写入 muse_unified_event,最终通过 /app-api/muse/events 对资产发布者 SSE 可见。
本执行版不允许把 Events / P1R-7 / Market 推进到 completed,不允许修改 OpenAPI、coverage scanner 或 coverage report,不允许复用 Account projection outbox 作为 Events publish outbox。
flowchart LR
Command["adminDelistAsset / adminRecallAsset<br/>expectedStatus + impactPreview"] --> Fact["muse_market_governance_action<br/>applied"]
Command --> SourceStatus["muse_market_source_status_event<br/>recall only, evidence only"]
Fact --> Outbox["muse_market_event_publish_outbox<br/>one row per governance action"]
Outbox --> Worker["Market publish worker<br/>claim/retry/dead_letter"]
Worker --> Api["EventsPublishApi<br/>events-api only"]
Api --> Unified["muse_unified_event<br/>notification/governance_action"]
Unified --> Stream["/app-api/muse/events<br/>publisher visible SSE"]
Review Gate
P1R-7d 审阅版已通过 fresh review:
- Ptolemy spec compliance review:PASS,无 P0/P1/P2。
- Galileo quality/feasibility review:PASS,无 P0/P1/P2。
P3 已纳入本执行版:
recall第一切片只发布一条market_governance_action通知,muse_market_source_status_event只作为 recall 事实旁证和后续扩展点。resourceRef.resourceId必须用数值型,不能把 DTO 的 stringresourceId直接塞入 payload。- command replay 不补历史 outbox;历史 governance fact backfill 不是 P1R-7d 第一切片目标。
- 实现必须由 fresh implementer 执行,完成后再做 fresh spec review + fresh quality / feasibility review。
边界
本轮目标
- 在 Market owner 内新增独立 Events publish outbox。
- 只接入
AdminMarketGovernanceServiceImpl.delistAsset和AdminMarketGovernanceServiceImpl.recallAsset成功路径。 - 只发布
muse_market_governance_action形成的用户可见治理终态通知。 - 通过 Market worker 调用
EventsPublishApi。 - 用 focused tests、P1R mixed gates、Flyway
_test、dependency gate 和隔离 coverage scanner 证明链路进入needs_verification证据层。
本轮非目标
- 不发布
previewGovernanceImpact。 - 不发布 purchase / install / license / handoff / Account projection 同步事件。
- 不发布
muse_market_source_status_event独立 SSE;recall 的 source status event 只作为 E2E 事实旁证。 - 不发布 authorization snapshot、handoff token、target owner 私有事实、完整 actionSnapshot、preview riskSummary、管理员内部备注原文或治理证据详情。
- 不复用
muse_market_account_projection作为 Events publish outbox。 - 不抽取 AI / Knowledge / Market 共享 outbox 框架。
- 不为历史 governance action 做 backfill。
- 不修改
docs/api-contracts/*/openapi.yaml。 - 不修改
muse-cloud/scripts/p1r-audit-api-coverage.py。 - 不修改
docs/superpowers/reports/p1r-api-coverage.json或.md。
受保护文件
实现代理不得修改以下文件:
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
已验证事实
- 正确 worktree 是
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0。 - 当前分支是
dev/1.0.0,远端跟踪是origin/dev/1.0.0。 - 当前 HEAD 为
e55618f test(p1r): 收口 Knowledge 事件传播真实链路门禁。 - P1R-7b 已提交并 push:
3db5fbe与230152c。 - P1R-7c 已提交并 push:
cef686b与e55618f。 - P1R-7d 审阅版路径为
docs/agent-specs/2026-06-07-P1R7dMarketSourceOwnerPropagation审阅版.md。 - P1R-7d 审阅版 fresh review 已双 PASS:Ptolemy spec PASS,Galileo quality PASS。
- 当前 coverage 边界仍为
completedOperations=100、needsVerificationOperations=133、incompleteOperations=0、genericPersistenceOperations=0、ssePlaceholderOperations=0。 - Events
streamEvents当前是dedicated / needs_verification。 - Market 32 operations 当前是
dedicated / needs_verification。 - 当前受保护文件 diff 为空。
- 当前最新 migration 是
muse-cloud/sql/muse/V18__extend_knowledge_events_publish_outbox.sql;P1R-7d 下一 migration 编号应为V19。若实现前 live 文件列表已出现更高版本,必须停止并修订本执行版的 migration 编号。 EventsPublishReqDTO必填commandId、tenantId、ownerUserId、sourceOwner、sourceType、sourceId、eventType、payloadSummary、emittedAt。EventsPublishServiceImpl当前先按commandId回放,再按 source tuple 回放。EventsPublishServiceImpl当前已声明notificationevent type。EventsPublishServiceImpl当前已声明governance_actionnotification type。- Events OpenAPI
SSENotificationEvent.data.resourceRef.resourceId为 int64。 AdminMarketGovernanceServiceImpl.delistAsset成功后写muse_market_governance_action。AdminMarketGovernanceServiceImpl.recallAsset成功后写muse_market_governance_action,并额外写muse_market_source_status_event。MuseMarketGovernanceActionDO包含actionId、previewId、assetId、actorUserId、ownerUserId、actionType、commandId、requestHash、status、revision、actionSnapshot、reason。MuseMarketSourceStatusEventDO包含sourceEventId、sourceOwner、sourceType、sourceId、sourceRevision、ownerUserId、assetId、sourceStatus、actionPolicy、recheckReasons、eventSummary、errorCode、errorMessage。MarketAccountProjectionOutboxServiceImpl当前只有recordSynced/recordBlocked,无 Events publish claim / retry / dead_letter / published event 语义。muse-module-market-server/pom.xml当前已有对 member server 的依赖;实现前必须只新增 events-api direct dependency,不得新增 events-server dependency。
推断
- P1R-7d 可复用 P1R-7b/P1R-7c 的 outbox 状态机、worker claim、stale claim guard、dependency gate 和 Flyway
_test模式。 - Market 第一切片只发 governance action,比同时发布 governance action + source status event 更稳:用户收到一条明确治理通知,source status event 仍保留为 Market 内部 source propagation evidence。
- Account projection outbox 和 Events publish outbox 必须保持两张表 / 两套状态机,否则 Account 读模型同步失败与 SSE 发布失败会互相污染。
假设
- P1R-7d 允许 Market server 新增
muse-module-events-apidirect dependency。 - P1R-7d 允许新增
muse_market_event_publish_outbox。 - P1R-7d 第一批通知的 SSE owner 是资产发布者
ownerUserId,不是 admin actor。 - P1R-7d 第一批使用 Events 现有
notification/governance_action,不新增 OpenAPI event type 或 notification type。
设计合同
事件合同
第一批只允许以下 Market governance action 进入 publish outbox:
| Market source fact | actionType | Events eventType | notification type | SSE owner | message |
|---|---|---|---|---|---|
muse_market_governance_action |
delist |
notification |
governance_action |
ownerUserId |
资产已下架 |
muse_market_governance_action |
recall |
notification |
governance_action |
ownerUserId |
资产已召回 |
muse_market_source_status_event(sourceStatus=recalled) 不单独创建 outbox。实现测试必须证明 recall 成功路径上 source status event 存在,但最终只产生一条 governance action notification。
幂等合同
- 每个 governance action 只生成一条 outbox。
commandId格式:mk_evt:+ SHA-256(tenantId|actionId|ownerUserId|governance_action) 前 32 位 hex。outboxId格式:mk_out:+ SHA-256(tenantId|actionId|ownerUserId|governance_action) 前 32 位 hex。- Events source tuple:
sourceOwner = marketsourceType = market_governance_actionsourceId = actionIdsourceRevision = revision,为空时使用 Events 现有__none__eventType = notification
- 同一 governance action 重放必须得到同一 Events
eventId/sequenceNo。 - 同一 asset 的不同 governance action 必须生成不同 commandId 和不同 source tuple。
- Command replay 策略:
delistAsset/recallAsset命中 Market command replay 时不补历史 outbox;只有首次成功写入 governance action 的事务内创建 outbox。历史 backfill 若需要,必须另起任务。
Payload 合同
Internal outbox 允许保存以下字段:
actionIdassetIdactionTypesourceCommandIdactorUserIdownerUserIdpreviewIdrequestHashsourceRevisionmessageresourceTyperesourceId
Events payloadSummary 只能发布:
{
"type": "governance_action",
"message": "资产已召回",
"resourceRef": {
"resourceType": "market_asset",
"resourceId": 1001
},
"timestamp": "2026-06-07T12:00:00"
}
约束:
resourceRef.resourceId必须是数值型 Long / Integer 语义,不能是字符串。message只允许资产已下架或资产已召回。timestamp使用 governance action 的createTime或 worker 构造时可回退到 outboxcreateTime。- 不得发布
reason、完整actionSnapshot、preview riskSummary、authorization snapshot、handoff token、target owner、admin note、error stack、requestHash。 payloadSummary必须是 exact allowlist:顶层只能包含type、message、resourceRef、timestamp;resourceRef只能包含resourceType、resourceId。
状态机合同
Outbox 本域状态只允许:
queuedrunningretryablepublisheddead_letter
状态流转:
stateDiagram-v2
[*] --> queued
queued --> running: claim
retryable --> running: retry due
running --> running: expired lease reclaim
running --> published: Events accepted
running --> dead_letter: Events rejected/blocked
running --> retryable: temporary failure and attempts remain
running --> dead_letter: retry exhausted
Worker claim 要求:
- 使用 PostgreSQL
FOR UPDATE SKIP LOCKED或等价UPDATE ... RETURNING原子领取。 - 调度线程无请求租户上下文时必须先忽略租户拦截器领取,再使用 outbox 行内
tenantId恢复租户上下文发布。 - Claim 阶段唯一递增
attemptCount。 - 临时失败只释放 claim、设置
nextRetryAt和安全错误摘要,不二次递增 attempt count。 markPublished/markRetryable/markDeadLetter必须绑定publish_status='running' AND attempt_count=#{claimedAttemptCount}。- terminal update 返回 0 时必须记录 stale claim 诊断日志,包含 tenantId、ownerUserId、outboxId、actionId、attempt、targetStatus、errorCode。
- Worker 默认关闭:
muse.market.events.publish-worker.enabled=false。 - 调度配置键:
muse.market.events.publish-worker.initial-delay-msmuse.market.events.publish-worker.fixed-delay-msmuse.market.events.publish-worker.max-attemptmuse.market.events.publish-worker.claim-timeout-seconds
失败路径合同
| 路径 | 结果 |
|---|---|
| 无 governance action | 不创建 outbox |
actionType 非 delist/recall |
不创建 outbox |
| ownerUserId null 或 <= 0 | 不创建 outbox,记录安全日志 |
| payload 构造缺 required 字段 | 创建 outbox 前拒绝并记录安全日志,不创建 outbox,不发布,错误码固定为 MARKET_EVENTS_PAYLOAD_INVALID |
| Events accepted | outbox -> published,回写 eventId / sequenceNo |
| Events duplicate accepted | outbox -> published,回写既有 eventId / sequenceNo |
| Events rejected / blocked | outbox -> dead_letter |
| CommonResult error / exception | attempt 未耗尽 -> retryable;耗尽 -> dead_letter |
| command replay | 不补历史 outbox,不重复发布 |
涉及文件
生产代码
- Modify:
muse-cloud/muse-module-market/muse-module-market-server/pom.xml - Modify:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/application/muse/AdminMarketGovernanceServiceImpl.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/application/muse/MarketEventPublishOutboxService.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/application/muse/MarketEventPublishOutboxServiceImpl.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/application/muse/MarketEventPublishWorker.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/dal/dataobject/muse/MuseMarketEventPublishOutboxDO.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/dal/mysql/muse/MuseMarketEventPublishOutboxMapper.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/framework/config/MarketEventsConfiguration.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/framework/config/MarketEventsProperties.java - Create:
muse-cloud/sql/muse/V19__extend_market_events_publish_outbox.sql
测试代码
- Modify:
muse-cloud/muse-module-market/muse-module-market-server/src/test/java/cn/iocoder/muse/module/market/application/muse/AdminMarketGovernanceServiceTest.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/test/java/cn/iocoder/muse/module/market/application/muse/MarketEventPublishOutboxServiceTest.java - Create:
muse-cloud/muse-module-market/muse-module-market-server/src/test/java/cn/iocoder/muse/module/market/application/muse/MarketEventPublishWorkerTest.java - Create:
muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishDependencyTest.java - Create:
muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishEndToEndTest.java - Create:
muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishFlywayMigrationIT.java - Create:
muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishMigrationSqlTest.java
文档
- Modify:
docs/agent-specs/.agent - Create:
docs/memorys/YYYY-MM-DD-P1R7dMarketSourceOwnerPropagation真实链路.md
实施任务
Task 0:fresh implementer preflight
- 核对
pwd、git status --short --branch、git log --oneline -5。 git pull --ff-only origin dev/1.0.0。- 核对受保护文件 diff 为空。
- Git / protected diff / coverage scanner 均从 worktree 根目录执行;Maven / Flyway 命令必须先
cd muse-cloud,因为 reactor POM 位于muse-cloud/pom.xml。 - 核对
muse-cloud/sql/muse最新 migration 是否仍为 V18;若不是,停止并修订本执行版。 - 只读核对 Market server 当前没有 events-api dependency。
- 只读核对 Events server 仍不依赖 Market server。
Task 1:V19 migration 与 SQL gate
新增 muse_market_event_publish_outbox。
必须字段:
idoutbox_idaction_idasset_idaction_typesource_command_idowner_user_idactor_user_idpreview_idrequest_hashsource_revisionevent_typenotification_typepayload_summarycommand_idpublish_statusattempt_countmax_attemptclaimed_atclaim_expires_atnext_retry_atlast_error_codelast_error_messagepublished_event_idpublished_sequence_notenant_iddeletedcreatorupdatercreate_timeupdate_timeupdate_time自动维护 trigger
必须约束:
event_type = 'notification'notification_type = 'governance_action'action_type IN ('delist','recall')publish_status IN ('queued','running','retryable','published','dead_letter')owner_user_id > 0attempt_count >= 0max_attempt > 0
必须唯一键:
(tenant_id, outbox_id)(tenant_id, command_id)(tenant_id, action_id, notification_type)
必须索引:
- claim index:tenant + publish_status + next_retry_at + id
- owner status index:tenant + owner_user_id + publish_status
- action index:tenant + action_id
SQL gate 必须验证:
- 表存在。
- check 约束存在且不是宽松 OR 表达式。
- unique key 和 claim index 存在。
- update_time trigger 存在。
resourceRef.resourceId不在 DB 约束中表达,留给 payload 单元测试验证。
Task 2:Outbox DO / Mapper
实现 DO 与 Mapper。
Mapper 必须包含:
insertIgnoreselectByTenantIdAndOutboxIdselectByTenantIdAndCommandIdselectByTenantIdAndActionIdAndNotificationTypeclaimNextmarkPublishedmarkRetryablemarkDeadLetter
Mapper 约束:
claimNext必须只领取queued/retryable/running expired。claimNext必须设置publish_status='running'、递增attempt_count、设置claimed_at/claim_expires_at。markPublished/markRetryable/markDeadLetter必须要求publish_status='running' AND attempt_count=#{claimedAttemptCount}。
Task 3:Outbox service
实现 MarketEventPublishOutboxService.createForGovernanceAction(action)。
必须行为:
- 只接受
delist/recall。 - ownerUserId null 或 <= 0 时跳过并记录安全日志。
- 构造 payload 时
resourceRef.resourceId必须是数值型 action.assetId。 - message 按 actionType 固定:
delist->资产已下架recall->资产已召回
- 使用
insertIgnore + 回查幂等。 sourceRevision为空时归一为__none__。- 不读取
reason或完整actionSnapshot拼 payload。 - command replay 不补历史 outbox。
Task 4:接入 governance service
修改 AdminMarketGovernanceServiceImpl.applyGovernanceAction:
- 在 governance action insert 成功后创建 outbox。
- 保持同事务:治理事实写入成功且 outbox 创建失败时,整个 governance command 回滚,避免治理事实和 publish intent 不一致。
recallAsset仍写 source status event,但不为 source status event 创建独立 outbox。- 命中 command replay 时仍直接回放,不补历史 outbox。
Task 5:Worker
新增 Market publish worker。
必须行为:
- 默认 disabled,不 claim。
- enabled 时 claim 一条并发布。
- 构造
EventsPublishReqDTO:sourceOwner=marketsourceType=market_governance_actionsourceId=actionIdsourceRevision=sourceRevision或__none__eventType=notificationresourceType=market_assetresourceId=assetId.toString()payloadSummary使用 outbox 中 JSON 解析后的 allowlist mapownerUserId=outbox.ownerUserId
EventsPublishReqDTO.resourceId仍按 Events API DTO 使用字符串;payloadSummary.resourceRef.resourceId必须保持 Number / Long 语义,测试必须分别断言两者,不能混写。- Events accepted ->
published。 - Events rejected/blocked ->
dead_letter。 - CommonResult error / exception ->
retryable或 retry exhausted 后dead_letter。 - terminal update 0 rows 记录 stale claim 诊断日志。
Task 6:Focused tests
必须覆盖:
- delist action 创建 queued outbox。
- recall action 只创建一条 governance action outbox;source status event 存在但不创建第二条 outbox。
- command replay 不补历史 outbox。
- ownerUserId invalid 不创建 outbox。
- payload 使用 exact allowlist:顶层只能有
type/message/resourceRef/timestamp,resourceRef只能有resourceType/resourceId。 - payload 不包含 reason / actionSnapshot / preview riskSummary / authorization snapshot / handoff token / target owner / admin note / error stack / requestHash。
- payload invalid fail-closed 使用固定错误码
MARKET_EVENTS_PAYLOAD_INVALID,并断言只进入安全日志或安全 lastError 摘要,不创建 outbox、不发布。 - resourceRef.resourceId 是数值型。
- duplicate create 复用既有 outbox。
- worker disabled 不 claim。
- worker accepted -> published。
- worker duplicate accepted -> published。
- worker rejected / blocked -> dead_letter。
- worker CommonResult error -> retryable;last attempt -> dead_letter;lastError 只保留安全摘要,不持久化原始错误正文。
- worker exception -> retryable;last attempt -> dead_letter。
- stale claim terminal update 被忽略并记录日志。
Task 7:P1R mixed gates
必须新增:
P1rMarketEventsPublishDependencyTestP1rMarketEventsPublishEndToEndTestP1rMarketEventsPublishMigrationSqlTestP1rMarketEventsPublishFlywayMigrationIT
E2E 必须证明:
- Market governance action -> Market outbox -> worker -> EventsPublishApi ->
muse_unified_event。 - publisher owner 可见。
- admin actor 不因 actorUserId 可见。
- 非 owner 不可见。
- recall 只发布一条 governance action notification。
- blocked/rejected 不进入 visible query。
Mixed gate 命令必须继续包括:
P1rApiCoverageReportTestP1rEventsRealApiGateTestP1rEventsRouteOwnershipTestP1rAiRealApiGateTestP1rAiRouteOwnershipTestP1rAiEventsPublishMigrationSqlTestP1rAiEventsPublishDependencyTestP1rAiEventsPublishEndToEndTestP1rKnowledgeEventsPublishMigrationSqlTestP1rKnowledgeEventsPublishDependencyTestP1rKnowledgeEventsPublishEndToEndTest- P1R-7d Market 新增 gate
Task 8:Flyway _test
新增 P1rMarketEventsPublishFlywayMigrationIT。
要求:
- 只允许
_test结尾数据库。 - 禁止 JDBC URL 携带 password / credential query。
- password 只能来自
P1R_FLYWAY_PASSWORD。 - 推荐复用禁用 JVM 代理参数:
-Djava.net.useSystemProxies=false-DsocksProxyHost=-DsocksProxyPort=-Dhttp.proxyHost=-Dhttp.proxyPort=-Dhttps.proxyHost=-Dhttps.proxyPort=
- 验证 V19 表、约束、索引、trigger。
- 真实 PostgreSQL 负向 insert 探针至少覆盖:
- illegal event_type
- illegal notification_type
- illegal action_type
- illegal publish_status
- owner_user_id <= 0
Task 9:Coverage scanner 与 protected diff
只允许在 /tmp 隔离副本运行:
tmpdir=$(mktemp -d /tmp/p1r7d-coverage-scan.XXXXXX)
rsync -a --delete --exclude .git --exclude muse-cloud/target --exclude 'muse-cloud/**/target' \
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/ "$tmpdir/"
cd "$tmpdir"
python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check
真实 worktree 必须继续确认 protected diff 为空。
Task 10:文档留痕与 review
- 更新
docs/agent-specs/.agent。 - 新增
docs/memorys/YYYY-MM-DD-P1R7dMarketSourceOwnerPropagation真实链路.md。 - 实现完成后必须 fresh spec review + fresh quality/feasibility review。
- 双 PASS 也只代表
needs_verificationevidence 收口,不代表 completed。
最终验证命令
实现完成后按顺序运行:
- Reactor build:
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o clean install -DskipTests -Dspring-boot.repackage.skip=true
- Market focused tests:
先断言新增测试类存在,避免 failIfNoSpecifiedTests=false 掩盖新测试漏建或拼错:
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
test -f muse-cloud/muse-module-market/muse-module-market-server/src/test/java/cn/iocoder/muse/module/market/application/muse/MarketEventPublishOutboxServiceTest.java && \
test -f muse-cloud/muse-module-market/muse-module-market-server/src/test/java/cn/iocoder/muse/module/market/application/muse/MarketEventPublishWorkerTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishMigrationSqlTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishDependencyTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishEndToEndTest.java && \
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketEventsPublishFlywayMigrationIT.java
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test -pl muse-module-market/muse-module-market-server -Dtest=AdminMarketGovernanceServiceTest,MarketEventPublishOutboxServiceTest,MarketEventPublishWorkerTest -Dsurefire.failIfNoSpecifiedTests=false
- P1R mixed gates:
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test -pl muse-server -am -Dtest=P1rMarketEventsPublishMigrationSqlTest,P1rMarketEventsPublishDependencyTest,P1rMarketEventsPublishEndToEndTest,P1rKnowledgeEventsPublishMigrationSqlTest,P1rKnowledgeEventsPublishDependencyTest,P1rKnowledgeEventsPublishEndToEndTest,P1rAiEventsPublishMigrationSqlTest,P1rAiEventsPublishDependencyTest,P1rAiEventsPublishEndToEndTest,P1rApiCoverageReportTest,P1rEventsRealApiGateTest,P1rEventsRouteOwnershipTest,P1rAiRealApiGateTest,P1rAiRouteOwnershipTest -Dsurefire.failIfNoSpecifiedTests=false
- 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 -o -pl muse-module-market/muse-module-market-server -am dependency:tree \
> /tmp/p1r7d-market-server-dependency-tree.txt && \
rg 'muse-module-events-api' /tmp/p1r7d-market-server-dependency-tree.txt && \
! rg 'muse-module-events-server' /tmp/p1r7d-market-server-dependency-tree.txt
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 -pl muse-module-events/muse-module-events-server -am dependency:tree \
> /tmp/p1r7d-events-server-dependency-tree.txt && \
! rg 'muse-module-(ai|knowledge|market|member|content)-server' /tmp/p1r7d-events-server-dependency-tree.txt
- 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 P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD"
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test -pl muse-server -am \
-Dtest=P1rMarketEventsPublishFlywayMigrationIT \
-Dflyway.postgresql.transactional.lock=false \
-Dp1r.flyway.locations=filesystem:sql/muse \
-Dp1r.flyway.url="jdbc:postgresql://$MUSE_POSTGRES_HOST:$MUSE_POSTGRES_PORT/muse_p1r7d_events_publish_test" \
-Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
-Djava.net.useSystemProxies=false \
-DsocksProxyHost= \
-DsocksProxyPort= \
-Dhttp.proxyHost= \
-Dhttp.proxyPort= \
-Dhttps.proxyHost= \
-Dhttps.proxyPort= \
-Dsurefire.failIfNoSpecifiedTests=false
- Surefire XML 计数断言:
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
python3 - <<'PY'
from pathlib import Path
import xml.etree.ElementTree as ET
targets = [
"muse-cloud/muse-module-market/muse-module-market-server/target/surefire-reports/TEST-cn.iocoder.muse.module.market.application.muse.MarketEventPublishOutboxServiceTest.xml",
"muse-cloud/muse-module-market/muse-module-market-server/target/surefire-reports/TEST-cn.iocoder.muse.module.market.application.muse.MarketEventPublishWorkerTest.xml",
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketEventsPublishMigrationSqlTest.xml",
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketEventsPublishDependencyTest.xml",
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketEventsPublishEndToEndTest.xml",
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketEventsPublishFlywayMigrationIT.xml",
]
for target in targets:
path = Path(target)
if not path.exists():
raise SystemExit(f"missing surefire xml: {target}")
root = ET.parse(path).getroot()
tests = int(root.attrib.get("tests", "0"))
failures = int(root.attrib.get("failures", "0"))
errors = int(root.attrib.get("errors", "0"))
skipped = int(root.attrib.get("skipped", "0"))
if tests <= 0 or failures or errors or skipped:
raise SystemExit(f"bad surefire result: {target} tests={tests} failures={failures} errors={errors} skipped={skipped}")
PY
- 隔离 coverage scanner:
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
tmpdir=$(mktemp -d /tmp/p1r7d-coverage-scan.XXXXXX) && \
rsync -a --delete --exclude .git --exclude muse-cloud/target --exclude 'muse-cloud/**/target' \
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/ "$tmpdir/" && \
cd "$tmpdir" && \
python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check
- Protected diff:
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
git diff --quiet -- 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
完成条件
- P1R-7d Market governance action -> Market outbox -> worker -> EventsPublishApi ->
muse_unified_event-> SSE visible 证据成立。 - 所有 focused / mixed / dependency tree / Flyway / coverage / protected diff gate 通过。
- fresh implementation spec review + fresh quality/feasibility review 双 PASS。
- 不修改 OpenAPI、scanner、coverage report。
- 不推进 Events / P1R-7 / Market completed。
回滚策略
- 配置关闭
muse.market.events.publish-worker.enabled=false后 worker 不 claim。 - 数据层可保留
muse_market_event_publish_outbox作为审计事实,不影响现有 Market API。 - 如需要撤销应用行为,回滚
AdminMarketGovernanceServiceImpl对 outbox service 的调用和 worker bean 配置;V19 表可保留到后续清理迁移。