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

30 KiB
Raw Blame History

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 reviewPASS无 P0/P1/P2。
  • Galileo quality/feasibility reviewPASS无 P0/P1/P2。

P3 已纳入本执行版:

  • recall 第一切片只发布一条 market_governance_action 通知,muse_market_source_status_event 只作为 recall 事实旁证和后续扩展点。
  • resourceRef.resourceId 必须用数值型,不能把 DTO 的 string resourceId 直接塞入 payload。
  • command replay 不补历史 outbox历史 governance fact backfill 不是 P1R-7d 第一切片目标。
  • 实现必须由 fresh implementer 执行,完成后再做 fresh spec review + fresh quality / feasibility review。

边界

本轮目标

  • 在 Market owner 内新增独立 Events publish outbox。
  • 只接入 AdminMarketGovernanceServiceImpl.delistAssetAdminMarketGovernanceServiceImpl.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 独立 SSErecall 的 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.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

已验证事实

  • 正确 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 已提交并 push3db5fbe230152c
  • P1R-7c 已提交并 pushcef686be55618f
  • P1R-7d 审阅版路径为 docs/agent-specs/2026-06-07-P1R7dMarketSourceOwnerPropagation审阅版.md
  • P1R-7d 审阅版 fresh review 已双 PASSPtolemy spec PASSGalileo quality PASS。
  • 当前 coverage 边界仍为 completedOperations=100needsVerificationOperations=133incompleteOperations=0genericPersistenceOperations=0ssePlaceholderOperations=0
  • Events streamEvents 当前是 dedicated / needs_verification
  • Market 32 operations 当前是 dedicated / needs_verification
  • 当前受保护文件 diff 为空。
  • 当前最新 migration 是 muse-cloud/sql/muse/V18__extend_knowledge_events_publish_outbox.sqlP1R-7d 下一 migration 编号应为 V19。若实现前 live 文件列表已出现更高版本,必须停止并修订本执行版的 migration 编号。
  • EventsPublishReqDTO 必填 commandIdtenantIdownerUserIdsourceOwnersourceTypesourceIdeventTypepayloadSummaryemittedAt
  • EventsPublishServiceImpl 当前先按 commandId 回放,再按 source tuple 回放。
  • EventsPublishServiceImpl 当前已声明 notification event type。
  • EventsPublishServiceImpl 当前已声明 governance_action notification 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 包含 actionIdpreviewIdassetIdactorUserIdownerUserIdactionTypecommandIdrequestHashstatusrevisionactionSnapshotreason
  • MuseMarketSourceStatusEventDO 包含 sourceEventIdsourceOwnersourceTypesourceIdsourceRevisionownerUserIdassetIdsourceStatusactionPolicyrecheckReasonseventSummaryerrorCodeerrorMessage
  • 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-api direct 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 = market
    • sourceType = market_governance_action
    • sourceId = actionId
    • sourceRevision = 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 允许保存以下字段:

  • actionId
  • assetId
  • actionType
  • sourceCommandId
  • actorUserId
  • ownerUserId
  • previewId
  • requestHash
  • sourceRevision
  • message
  • resourceType
  • resourceId

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 构造时可回退到 outbox createTime
  • 不得发布 reason、完整 actionSnapshot、preview riskSummary、authorization snapshot、handoff token、target owner、admin note、error stack、requestHash。
  • payloadSummary 必须是 exact allowlist顶层只能包含 typemessageresourceReftimestampresourceRef 只能包含 resourceTyperesourceId

状态机合同

Outbox 本域状态只允许:

  • queued
  • running
  • retryable
  • published
  • dead_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-ms
    • muse.market.events.publish-worker.fixed-delay-ms
    • muse.market.events.publish-worker.max-attempt
    • muse.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 0fresh implementer preflight

  • 核对 pwdgit status --short --branchgit 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 1V19 migration 与 SQL gate

新增 muse_market_event_publish_outbox

必须字段:

  • id
  • outbox_id
  • action_id
  • asset_id
  • action_type
  • source_command_id
  • owner_user_id
  • actor_user_id
  • preview_id
  • request_hash
  • source_revision
  • event_type
  • notification_type
  • payload_summary
  • command_id
  • publish_status
  • attempt_count
  • max_attempt
  • claimed_at
  • claim_expires_at
  • next_retry_at
  • last_error_code
  • last_error_message
  • published_event_id
  • published_sequence_no
  • tenant_id
  • deleted
  • creator
  • updater
  • create_time
  • update_time
  • update_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 > 0
  • attempt_count >= 0
  • max_attempt > 0

必须唯一键:

  • (tenant_id, outbox_id)
  • (tenant_id, command_id)
  • (tenant_id, action_id, notification_type)

必须索引:

  • claim indextenant + publish_status + next_retry_at + id
  • owner status indextenant + owner_user_id + publish_status
  • action indextenant + action_id

SQL gate 必须验证:

  • 表存在。
  • check 约束存在且不是宽松 OR 表达式。
  • unique key 和 claim index 存在。
  • update_time trigger 存在。
  • resourceRef.resourceId 不在 DB 约束中表达,留给 payload 单元测试验证。

Task 2Outbox DO / Mapper

实现 DO 与 Mapper。

Mapper 必须包含:

  • insertIgnore
  • selectByTenantIdAndOutboxId
  • selectByTenantIdAndCommandId
  • selectByTenantIdAndActionIdAndNotificationType
  • claimNext
  • markPublished
  • markRetryable
  • markDeadLetter

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 3Outbox 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 5Worker

新增 Market publish worker。

必须行为:

  • 默认 disabled不 claim。
  • enabled 时 claim 一条并发布。
  • 构造 EventsPublishReqDTO
    • sourceOwner=market
    • sourceType=market_governance_action
    • sourceId=actionId
    • sourceRevision=sourceRevision__none__
    • eventType=notification
    • resourceType=market_asset
    • resourceId=assetId.toString()
    • payloadSummary 使用 outbox 中 JSON 解析后的 allowlist map
    • ownerUserId=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 6Focused tests

必须覆盖:

  • delist action 创建 queued outbox。
  • recall action 只创建一条 governance action outboxsource status event 存在但不创建第二条 outbox。
  • command replay 不补历史 outbox。
  • ownerUserId invalid 不创建 outbox。
  • payload 使用 exact allowlist顶层只能有 type/message/resourceRef/timestampresourceRef 只能有 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 -> retryablelast attempt -> dead_letterlastError 只保留安全摘要,不持久化原始错误正文。
  • worker exception -> retryablelast attempt -> dead_letter。
  • stale claim terminal update 被忽略并记录日志。

Task 7P1R mixed gates

必须新增:

  • P1rMarketEventsPublishDependencyTest
  • P1rMarketEventsPublishEndToEndTest
  • P1rMarketEventsPublishMigrationSqlTest
  • P1rMarketEventsPublishFlywayMigrationIT

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 命令必须继续包括:

  • P1rApiCoverageReportTest
  • P1rEventsRealApiGateTest
  • P1rEventsRouteOwnershipTest
  • P1rAiRealApiGateTest
  • P1rAiRouteOwnershipTest
  • P1rAiEventsPublishMigrationSqlTest
  • P1rAiEventsPublishDependencyTest
  • P1rAiEventsPublishEndToEndTest
  • P1rKnowledgeEventsPublishMigrationSqlTest
  • P1rKnowledgeEventsPublishDependencyTest
  • P1rKnowledgeEventsPublishEndToEndTest
  • P1R-7d Market 新增 gate

Task 8Flyway _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 9Coverage 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_verification evidence 收口,不代表 completed。

最终验证命令

实现完成后按顺序运行:

  1. 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
  1. 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
  1. 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
  1. 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
  1. 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
  1. 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
  1. 隔离 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
  1. 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 表可保留到后续清理迁移。