205 lines
13 KiB
Markdown
205 lines
13 KiB
Markdown
# 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<br/>delist / recall"] --> Fact["Market source fact<br/>governance_action / source_status_event"]
|
||
Fact --> Outbox["Market events publish outbox<br/>queued / running / retryable / published / dead_letter"]
|
||
Outbox --> Worker["Market worker<br/>claim + retry + audit log"]
|
||
Worker --> Api["EventsPublishApi<br/>events-api only"]
|
||
Api --> Unified[("muse_unified_event<br/>notification / governance_action")]
|
||
Unified --> SSE["/app-api/muse/events<br/>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:<sha256-32>`。
|
||
- 状态机:`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 的通知。
|