# P1R7d Market Source Owner Propagation 审阅版 ## 结论 推荐进入 P1R-7d,但第一切片只覆盖 Market governance / source status 的用户可见通知,不覆盖 purchase、install、handoff、Account projection 同步或目标 owner 授权消费。 推荐方案: 1. 选择 `AdminMarketGovernanceServiceImpl` 已落库的 `muse_market_governance_action` / `muse_market_source_status_event` 作为 P1R-7d 第一批 source fact。 2. 新增 Market 本域 Events publish outbox / worker,调用 `EventsPublishApi`,发布 Events 已声明的 `notification/governance_action`。 3. 不复用 `MarketAccountProjectionOutboxService` 作为 Events publish outbox;它只表达 Market -> Member Account 投影同步状态。 4. 不修改 OpenAPI、scanner 或 coverage report;不推进 Events / P1R-7 / Market completed。 ```mermaid flowchart LR Admin["Admin governance command
delist / recall"] --> Fact["Market source fact
governance_action / source_status_event"] Fact --> Outbox["Market events publish outbox
queued / running / retryable / published / dead_letter"] Outbox --> Worker["Market worker
claim + retry + audit log"] Worker --> Api["EventsPublishApi
events-api only"] Api --> Unified[("muse_unified_event
notification / governance_action")] Unified --> SSE["/app-api/muse/events
publisher visible"] ``` 本审阅版不实现代码,只冻结目标、非目标、取舍、影响范围、风险和验收标准。执行版、实现和提交必须在本审阅版 fresh spec review + fresh quality review 双 PASS 后再继续。 ## 已验证事实 ### 工作区与状态 - 正确 worktree:`/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0`。 - 分支:`dev/1.0.0`。 - 当前本地与 `origin/dev/1.0.0` 对齐到 `e55618f test(p1r): 收口 Knowledge 事件传播真实链路门禁`。 - 受保护文件 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` ### 全局 P1R-7 边界 - P1R-7 全局审阅版建议阶段: - P1R-7b:AI terminal event。 - P1R-7c:Knowledge source status / projection event。 - P1R-7d:Market lifecycle / account projection / governance event。 - P1R-7 completed approval 必须另起任务并由用户单独批准。 - P1R-7 全局执行版要求每个 owner 都必须 fresh implementer + fresh spec review + fresh quality / feasibility review。 - 全局合同要求 source owner 只能依赖 `muse-module-events-api`,Events server 不得反向依赖 AI / Knowledge / Market / Member / Content server。 - 当前 coverage summary 仍是 `completedOperations=100`、`needsVerificationOperations=133`、`incompleteOperations=0`、`genericPersistenceOperations=0`、`ssePlaceholderOperations=0`。 - Events `streamEvents` 与 Market 32 operations 均仍为 `dedicated / needs_verification`。 ### Events 可复用合同 - `EventsPublishReqDTO` 已包含 `commandId`、`tenantId`、`ownerUserId`、`sourceOwner`、`sourceType`、`sourceId`、`sourceRevision`、`eventType`、`resourceType`、`resourceId`、`payloadSummary`、`emittedAt`。 - `EventsPublishServiceImpl` 先按 `commandId` 回放,再按 source tuple 回放。 - `EventsPublishServiceImpl` 已声明事件类型包含 `notification`。 - Events OpenAPI 的 `SSENotificationEvent.data.type` 已包含 `governance_action`。 - `UnifiedEventMapper.selectVisibleEventsForOwner` 只返回 `accepted`、同租户、同 owner、未删除、已到 visible time 的事件。 ### Market 当前事实模型 - `AdminMarketGovernanceServiceImpl.delistAsset` 在治理命令成功后写入 `muse_market_governance_action`,字段包括 `actionId`、`previewId`、`assetId`、`actorUserId`、`ownerUserId`、`actionType`、`commandId`、`requestHash`、`status`、`revision`、`actionSnapshot`、`reason`。 - `AdminMarketGovernanceServiceImpl.recallAsset` 在治理命令成功后写入 `muse_market_governance_action`,并额外写入 `muse_market_source_status_event`,其中 `sourceStatus` 固定为合同状态如 `recalled`,`needs_recheck` 只写入 `actionPolicy/recheckReasons`。 - `MuseMarketGovernanceActionMapper` 支持按 tenant + asset 查询最新或列表,但没有 publish claim / retry / dead_letter 语义。 - `MuseMarketSourceStatusEventMapper` 支持按 `sourceEventId` 或 tenant + asset 查询最新,但没有 publish claim / retry / dead_letter 语义。 - `MarketAccountProjectionOutboxServiceImpl` 只有 `recordSynced` / `recordBlocked`,职责是 Market -> Member Account projection 同步状态;当前没有领取 retryable outbox、重试成功、dead_letter 或 replay worker。 - `MarketAccountProjectionProvider` 明确把 Account 查询读模型写入 member 表,失败时记录 Market 侧 blocked outbox 后抛错,防止伪成功。 - `MarketHandoffServiceImpl` 保存 handoff token hash 和生命周期状态,但 token 明文只在创建响应中返回;后续 P1R-7d 第一切片不发布 handoff token 或授权详情。 ## 推断 - Market P1R-7d 第一切片应优先选择 governance/source status,而不是 purchase/install/handoff。原因是 governance/source status 已有明确 publisher owner、source fact、actionId/sourceEventId 和安全摘要空间,且用户需要实时知道资产被下架或召回。 - 直接复用 `muse_market_account_projection` 作为 Events publish outbox 会混淆职责:它记录 Account 投影同步,不记录 Events publish claim、lease、retry、published event 或 dead_letter。 - `notification/governance_action` 已在 Events 合同内,可避免本阶段修改 OpenAPI。 - recall 比 delist 更适合作为第一条 source status 证据,因为它同时写 governance action 和 source status blocked 事实;但执行版仍可把 delist 作为同一事件族的正向治理通知。 ## 假设 - P1R-7d 允许在 Market owner 内新增独立 Events publish outbox 表或等价状态表。 - P1R-7d 允许 `muse-module-market-server` 新增对 `muse-module-events-api` 的直接依赖,但不允许依赖 `muse-module-events-server`。 - Market publisher 是第一批通知的 `ownerUserId`;admin 操作者只作为 `actorUserId` 写入 Market fact / 安全摘要,不作为 SSE 可见 owner。 - `notification/governance_action` 的 payload 最小字段可以收敛为 `type`、`message`、可选 `resourceRef`、`timestamp`,不需要新增 OpenAPI 字段。 ## 目标 1. 证明 Market source owner 的真实治理事实可以通过 Market 本域 publish outbox / worker 发布到 Events。 2. 让发布者用户可以通过统一 SSE 看到自己的资产治理通知。 3. 建立 Market 与 Events 的单向依赖证据:Market server 依赖 events-api,不依赖 events-server;Events server 不反向依赖 Market server。 4. 为后续 Market source propagation / Account projection / handoff 事件扩展保留清晰边界。 ## 非目标 1. 不把 Market 32 operations 从 `needs_verification` 推进到 `completed`。 2. 不修改 Market / Events OpenAPI。 3. 不修改 coverage scanner 或 coverage report。 4. 不把 purchase、install、license、handoff token、authorization snapshot 或 Account projection 同步纳入第一切片。 5. 不把 `muse_market_account_projection` 复用为 Events publish outbox。 6. 不发布授权快照、安装详情、handoff token、目标 owner 私有事实、治理 preview 原始 riskSummary、管理员内部备注原文或完整 actionSnapshot。 7. 不让 Events server 查询 Market 表。 ## 推荐方案 ### 事件选择 第一批只允许: | Market source fact | 触发入口 | Events eventType | notification type | SSE owner | |---|---|---|---|---| | `muse_market_governance_action(actionType=delist)` | `adminDelistAsset` | `notification` | `governance_action` | asset.publisherId | | `muse_market_governance_action(actionType=recall)` | `adminRecallAsset` | `notification` | `governance_action` | asset.publisherId | | `muse_market_source_status_event(sourceStatus=recalled)` | `adminRecallAsset` | `notification` | `governance_action` | event.ownerUserId | 不把 `previewGovernanceImpact` 发布到 SSE。preview 只是管理员决策前的影响预览,不是用户必须实时感知的终态事实。 ### Payload allowlist 第一批 payload 只允许: ```json { "type": "governance_action", "message": "资产治理状态已更新", "resourceRef": { "resourceType": "market_asset", "resourceId": 1001 }, "timestamp": "2026-06-07T12:00:00" } ``` 执行版可以把 `message` 细化为“资产已下架”或“资产已召回”,但不能放入管理员备注原文、治理证据、授权快照、handoff token、target owner 内部细节或错误堆栈。 ### Outbox 边界 推荐新增独立 Market Events publish outbox: - source table:`muse_market_governance_action` / `muse_market_source_status_event`。 - source tuple:`sourceOwner=market`,`sourceType=market_governance_action` 或 `market_source_status`,`sourceId=actionId/sourceEventId`,`sourceRevision=revision`。 - commandId:稳定短幂等键,建议 `market_evt:`。 - 状态机:`queued/running/retryable/published/dead_letter`。 - claim:必须原子领取,具备 lease / retry / attempt ownership。 - 终态回写:必须防 stale worker 覆盖新 claim 终态。 - worker 默认关闭,配置启用后才 claim。 ### 依赖边界 - Market server 可以依赖 `muse-module-events-api`。 - Market server 不得依赖 `muse-module-events-server`。 - Events server 不得依赖 Market server。 - `muse-server` 作为装配层可以同时包含 Market server 与 Events server。 ## 关键取舍 - 选择 governance/source status,不选择 Account projection:前者是 Market source owner 的用户可见事实;后者是 Account 查询读模型同步状态,复用会制造职责混乱。 - 选择 publisher 可见,不选择 admin 可见:SSE app 侧事件面向资源 owner;管理员操作已由管理端审计和治理记录承载。 - 使用 `notification/governance_action`,不新增 event type:Events 合同已经声明该 subtype,足够承载第一批摘要。 - 第一切片不发布 handoff:handoff 涉及 token、授权摘要和目标 owner 消费,泄露面和跨 owner 行为更复杂,应后续单独设计。 ## 影响范围 - 直接影响: - Market governance/source status 事实发布链路。 - Market server 依赖 events-api。 - Market 本域 outbox / worker / migration / focused tests。 - Events unified event focused E2E。 - 间接影响: - P1R-7 completed approval 的证据链更完整。 - 不影响: - OpenAPI 合同。 - coverage scanner / coverage report。 - Market 32 operations completion status。 - Account projection 查询读模型。 - handoff token 和目标 owner 授权消费。 ## 风险与兼容性 - 事件重复风险:同一治理动作 replay 不能创建多条 unified event;必须用 commandId 与 source tuple 双重幂等。 - 并发风险:worker terminal update 必须绑定本次 claim attempt,避免 stale worker 覆盖新 claim。 - 敏感信息风险:`reason`、`actionSnapshot`、preview `riskSummary` 和 target owner 细节不能进入 payload。 - 职责混淆风险:Account projection outbox 只能继续服务 Account 读模型同步,不参与 Events publish。 - 可见性风险:ownerUserId 必须是 publisher / source fact owner,不能使用 adminUserId,否则发布者无法通过 app SSE 看到通知。 - 兼容性:新增 outbox 与 worker 默认关闭,不改变现有 Market API 同步返回语义。 ## 验收标准 审阅版通过后,执行版必须包含: 1. Market outbox DO / Mapper / Service / Worker / 配置 / V19 migration 的文件边界。 2. `AdminMarketGovernanceServiceImpl` 创建 governance/source status fact 后创建 outbox 的事务边界。 3. payload allowlist 与敏感字段拒绝策略。 4. worker success / duplicate / retryable / rejected / blocked / dead_letter / stale claim tests。 5. Market governance source fact -> Market outbox -> worker -> EventsPublishApi -> `muse_unified_event` -> SSE visible focused E2E。 6. dependency gate:Market server 有 events-api、无 events-server;Events server 无 Market server。 7. V19 SQL 静态 gate 与 Flyway `_test`。 8. P1R mixed gates 继续确认 Events / AI / Knowledge 已有 source owner gates 不回退,Market 仍为 `dedicated / needs_verification`。 9. coverage scanner 只在 `/tmp` 隔离副本运行,真实 worktree protected diff 为空。 ## 待确认项 1. P1R-7d 第一切片是否确认只做 governance/source status,而不做 purchase/install/handoff。 2. `notification/governance_action` 的 message 是否允许按动作区分为“资产已下架 / 资产已召回”,还是统一为“资产治理状态已更新”。 3. 如果 recall 同时产生 governance action 和 source status event,执行版是否只发布一条 governance action 通知,还是允许两条不同 sourceType 的通知。