保留 P1R-7f 审阅版、执行版与 review gate 结论,明确本阶段只覆盖 saveBlock source attribution active 链路,不推进 completed。
823 lines
41 KiB
Markdown
823 lines
41 KiB
Markdown
# P1R7f Content Source Owner Propagation 执行版
|
||
|
||
## 结论
|
||
|
||
P1R-7f 只执行 Content source owner propagation 的第五 owner 切片:把 `ContentAppServiceImpl.saveBlock` 首次成功写入的 `muse_content_block_source_attribution(source_status=active)` 转成 Content 本域 Events publish outbox,再由 Content worker 调用 `EventsPublishApi` 写入 `muse_unified_event`,最终通过 `/app-api/muse/events` 对作品 owner SSE 可见。
|
||
|
||
本执行版不允许把 Events / P1R-7 / Content 推进到 `completed`,不允许修改 OpenAPI、coverage scanner 或 coverage report,不允许把 Content 保存正文伪造成 SSE `chunk`。
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Save["saveBlock<br/>commandId + expectedRevision"] --> Block["muse_content_block<br/>content_text + revision"]
|
||
Save --> Attr["muse_content_block_source_attribution<br/>source_status=active"]
|
||
Attr --> Outbox["muse_content_event_publish_outbox<br/>one row per block revision"]
|
||
Outbox --> Worker["Content publish worker<br/>claim/retry/dead_letter"]
|
||
Worker --> Api["EventsPublishApi<br/>events-api only"]
|
||
Api --> Unified["muse_unified_event<br/>notification/source_status_change"]
|
||
Unified --> Stream["/app-api/muse/events<br/>work owner visible SSE"]
|
||
```
|
||
|
||
## Review Gate
|
||
|
||
P1R-7f 审阅版已通过 fresh review:
|
||
|
||
- Harvey spec / scope review:PASS,无 P0/P1/P2;已验证本审阅版严格停留在 Content `saveBlock -> block source attribution -> Events notification` 第一切片,没有把 completed、OpenAPI、coverage 或全部 Content 写命令混入。
|
||
- Chandrasekhar quality / feasibility review:PASS,无 P0/P1/P2;已验证 `saveBlock -> muse_content_block_source_attribution(source_status=active) -> Content outbox -> EventsPublishApi -> muse_unified_event -> SSE` 路径可落地,并确认 Content 不应误用 `chunk`。
|
||
|
||
Halley quality reviewer 长时间超时未返回,已丢弃,不计入 review gate。
|
||
|
||
Chandrasekhar P3 已冻结为本执行版硬 gate:
|
||
|
||
- Content worker / outbox 只能发布 `eventType=notification`、`notificationType=source_status_change`。
|
||
- 必须有测试或静态 gate 防止 Content 误用 `chunk`。
|
||
|
||
P1R-7f 执行版首轮 quality / feasibility review(Hypatia)FAIL,两个 P1 已验证有效:
|
||
|
||
- `source_status=active` 合同只停留在 service 描述,V21 schema、Flyway `_test` 与 worker gate 没有兜住非 active outbox;由于 `deleteBlock` 真实会写 `sourceStatus=deleted`,该风险实际可达。
|
||
- E2E wording 允许 bare fake `EventsPublishApi` 返回 accepted,不能证明真实 `EventsPublishServiceImpl -> muse_unified_event -> visible query` 链路。
|
||
|
||
执行版已修订:V21 check 约束、SQL gate、Flyway `_test`、worker fail-closed 与 focused tests 均补 `source_status = 'active'`;E2E 成功路径收紧为必须使用真实 `EventsPublishServiceImpl`,只允许使用 in-memory / mocked `UnifiedEventMapper`,禁止 bare fake `EventsPublishApi` 作为成功证据。
|
||
|
||
旧 spec reviewer(Arendt)在首轮文档修订前长时间未返回,关闭动作被会话中断;该旧 review 视为 stale / discarded,即使后续异步返回也不计入 gate。
|
||
|
||
P1R-7f 执行版第二轮 fresh review 已通过:
|
||
|
||
- Huygens execution spec compliance review:PASS,无 P0/P1/P2/P3;已验证 scope、非目标、protected 边界、completed 边界、依赖边界、Chandrasekhar P3、Hypatia P1 修订、V21 / worker / E2E / Flyway / P1R mixed / `/tmp` scanner / protected diff gate 均已闭合。
|
||
- Raman execution quality / feasibility review:PASS,无 P0/P1/P2/P3;已验证 `saveBlock` 事务接入点、command replay 早退、`BlockSourceAttributionDO` 字段、Events `notification/source_status_change` 合同、`chunk` 不适配 Content save、V21 `source_status=active` 约束、payload exact allowlist、focused tests、真实 `EventsPublishServiceImpl` E2E 与防假绿门禁均可落地。
|
||
|
||
本轮双 PASS 只允许进入 fresh implementer 实现,不代表 P1R-7f 实现完成,也不代表 Events / P1R-7 / Content completed。实现后必须重新进行 fresh implementation spec / correctness review + fresh implementation quality / data-integrity / testing review,双 PASS 前不得提交、push 或推进 completed。
|
||
|
||
## 边界
|
||
|
||
### 本轮目标
|
||
|
||
- 在 Content owner 内新增独立 Events publish outbox。
|
||
- 只接入 `ContentAppServiceImpl.saveBlock` 首次成功路径。
|
||
- 只发布 `muse_content_block_source_attribution(source_status=active)` 形成的用户可见 source status 摘要。
|
||
- 通过 Content worker 调用 `EventsPublishApi`。
|
||
- 用 focused tests、P1R mixed gates、Flyway `_test`、dependency gate、dependency tree 和隔离 coverage scanner 证明链路进入 `needs_verification` 证据层。
|
||
|
||
### 本轮非目标
|
||
|
||
- 不覆盖 `createBlock`、`deleteBlock`、`splitBlock`、`mergeBlocks`、`mergeBlockSuggestion`。
|
||
- 不覆盖 import、parse、export、planning、meta projection、admin risk action。
|
||
- 不发布 Block 正文、请求正文、完整 `sourceSnapshot`、`sourceObjectId`、`authorizationSnapshotId`、`lineagePayload`、`licenseRestrictionSnapshot`、`auditReason`、`requestHash`、`resultSnapshot` 或错误堆栈。
|
||
- 不发布 `chunk`、`done`、`quality_check` 或 `error` 事件。
|
||
- 不新增 Events notification subtype,不新增 `block_saved`。
|
||
- 不抽取 AI / Knowledge / Market / Account / Content 共享 outbox 框架。
|
||
- 不为历史 block source attribution 做 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/account/openapi.yaml`
|
||
- `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`。
|
||
- `git pull --ff-only origin dev/1.0.0` 返回 `Already up to date.`。
|
||
- 当前 HEAD 为 `68cee5d test(p1r): 收口 Account 事件传播真实链路门禁`。
|
||
- P1R-7e 已提交并 push:`0f49802`、`0e10caf`、`68cee5d`。
|
||
- P1R-7f 审阅版路径为 `docs/agent-specs/2026-06-08-P1R7fContentSourceOwnerPropagation审阅版.md`。
|
||
- P1R-7f 审阅版 fresh review 已双 PASS:Harvey spec PASS,Chandrasekhar quality PASS。
|
||
- 当前 coverage 边界仍为 `completedOperations=100`、`needsVerificationOperations=133`、`incompleteOperations=0`、`genericPersistenceOperations=0`、`ssePlaceholderOperations=0`。
|
||
- Events `streamEvents` 当前是 `dedicated / needs_verification`。
|
||
- Content 51 operations 当前均为 `dedicated / needs_verification`。
|
||
- 当前受保护文件 staged / unstaged diff 为空。
|
||
- 当前最新 migration 是 `muse-cloud/sql/muse/V20__extend_account_events_publish_outbox.sql`;P1R-7f 下一 migration 编号应为 `V21`。若实现前 live 文件列表已出现更高版本,必须停止并修订本执行版的 migration 编号。
|
||
- `muse-module-content-server/pom.xml` 当前没有 `muse-module-events-api` 依赖,也没有 `muse-module-events-server` 依赖。
|
||
- `muse-module-events-server/pom.xml` 当前不依赖 Content server。
|
||
- `ContentAppServiceImpl.saveBlock` 当前在同一事务内完成 command 预占、owner guard、revision 校验、Block 更新、source attribution 插入和 command audit。
|
||
- `saveBlock` 命中 command replay 时直接回放 `ContentCommandDO.resultSnapshot`,不会更新 Block,也不会插入 `BlockSourceAttributionDO`。
|
||
- `BlockSourceAttributionDO` 包含 `id`、`workId`、`blockId`、`revision`、`sourceType`、`sourceObjectId`、`sourceVersion`、`lineagePayload`、`authorizationSnapshotId`、`sourceStatus`、`licenseRestrictionSnapshot`。
|
||
- `muse_content_block_source_attribution` 已有唯一约束 `(tenant_id, block_id, revision)`。
|
||
- `EventsPublishReqDTO` 必填 `commandId`、`tenantId`、`ownerUserId`、`sourceOwner`、`sourceType`、`sourceId`、`eventType`、`payloadSummary`、`emittedAt`。
|
||
- `EventsPublishServiceImpl` 当前先按 `commandId` 回放,再按 source tuple 回放。
|
||
- `EventsPublishServiceImpl` 当前已声明 `notification` event type。
|
||
- `EventsPublishServiceImpl` 当前已声明 `source_status_change` notification type。
|
||
- `EventsPublishServiceImpl` 当前把 `chunk` 识别为需要 `content/sequenceNo` 的 AI 文本片段语义;Content saveBlock 不能使用 `chunk`。
|
||
- `UnifiedEventMapper.selectVisibleEventsForOwner` 当前按 `tenantId`、`ownerUserId`、`publishStatus=accepted`、`deleted=false`、`visibleFrom<=now`、`sequenceNo>afterSequenceNo` 查询 SSE 可见事件。
|
||
|
||
## 推断
|
||
|
||
- P1R-7f 可复用 P1R-7b/P1R-7c/P1R-7d/P1R-7e 的 outbox 状态机、worker claim、stale claim guard、dependency tree gate、Surefire XML 防假绿和 Flyway `_test` 模式。
|
||
- `muse_content_block_source_attribution` 比 `muse_content_block` 更适合作为第一批 source fact,因为它表达“该 Block revision 的来源状态 active”,可脱敏映射为 `source_status_change`。
|
||
- Content outbox 与 block source attribution 必须保持两张表 / 两套状态机,否则 source fact 与 publish 补偿状态会互相污染。
|
||
- `source_status_change` 在 Content 中只能表达“Block 来源状态已更新”,不能扩展成正文保存、任务完成或万能通知。
|
||
|
||
## 假设
|
||
|
||
- P1R-7f 允许 Content server 新增 `muse-module-events-api` direct dependency。
|
||
- P1R-7f 允许新增 `muse_content_event_publish_outbox`。
|
||
- P1R-7f 第一批通知的 SSE owner 是作品 owner,即 `WorkDO.ownerUserId` / 当前 `saveBlock` userId。
|
||
- P1R-7f 第一批使用 Events 现有 `notification/source_status_change`,不新增 OpenAPI event type 或 notification type。
|
||
|
||
## 设计合同
|
||
|
||
### 事件合同
|
||
|
||
第一批只允许以下 Content source fact 进入 publish outbox:
|
||
|
||
| Content source fact | sourceStatus | Events eventType | notification type | SSE owner | message |
|
||
|---|---|---|---|---|---|
|
||
| `muse_content_block_source_attribution` | `active` | `notification` | `source_status_change` | `WorkDO.ownerUserId` | `内容来源状态已更新` |
|
||
|
||
强制约束:
|
||
|
||
- Content outbox service 必须二次校验 `sourceStatus=active`。
|
||
- Content worker 构造的 `EventsPublishReqDTO.eventType` 必须固定为 `notification`。
|
||
- Content worker 构造的 payload `type` 必须固定为 `source_status_change`。
|
||
- Content 相关生产代码和 P1R gate 不得把 Content saveBlock 发布为 `chunk`。
|
||
- V21 SQL、worker tests、E2E tests 至少各有一处证明 Content 只发布 `notification/source_status_change`,并负断言 `chunk`。
|
||
|
||
### 幂等合同
|
||
|
||
- 每个 `(tenantId, blockId, blockRevision, source_status_change)` 只生成一条 outbox。
|
||
- `commandId` 格式:`content_evt:` + SHA-256(`tenantId|blockId|blockRevision|source_status_change`) 前 32 位 hex。
|
||
- `outboxId` 格式:`content_out:` + SHA-256(`tenantId|blockId|blockRevision|source_status_change`) 前 32 位 hex。
|
||
- Events source tuple:
|
||
- `sourceOwner = content`
|
||
- `sourceType = content_block_source_attribution`
|
||
- `sourceId = blockId`
|
||
- `sourceRevision = blockRevision`
|
||
- `eventType = notification`
|
||
- 同一 Block revision 重放必须得到同一 Events `eventId/sequenceNo`。
|
||
- 同一 Block 的不同 revision 必须生成不同 commandId 和不同 source tuple。
|
||
- Command replay 策略:`saveBlock` 命中 Content command replay 时不补历史 outbox;只有首次成功写入 `BlockSourceAttributionDO(sourceStatus=active)` 的事务内创建 outbox。历史 backfill 若需要,必须另起任务。
|
||
|
||
### Payload 合同
|
||
|
||
Internal outbox 允许保存以下字段:
|
||
|
||
- `sourceAttributionId`
|
||
- `sourceCommandId`
|
||
- `ownerUserId`
|
||
- `workId`
|
||
- `blockId`
|
||
- `blockRevision`
|
||
- `sourceStatus`
|
||
- `eventType`
|
||
- `notificationType`
|
||
- `resourceRefType`
|
||
- `resourceRefId`
|
||
- `payloadSummary`
|
||
|
||
Events `payloadSummary` 只能发布:
|
||
|
||
```json
|
||
{
|
||
"type": "source_status_change",
|
||
"message": "内容来源状态已更新",
|
||
"resourceRef": {
|
||
"resourceType": "content_block",
|
||
"resourceId": 8001
|
||
},
|
||
"timestamp": "2026-06-08T12:00:00"
|
||
}
|
||
```
|
||
|
||
约束:
|
||
|
||
- `resourceRef.resourceId` 必须是数值型 Long / Integer 语义,值为 `blockId`。
|
||
- DTO `EventsPublishReqDTO.resourceId` 是 String,可以传 `String.valueOf(blockId)`;payload `resourceRef.resourceId` 必须保持 Number,不能是 String。
|
||
- `message` 第一切片只允许 `内容来源状态已更新`。
|
||
- `timestamp` 使用 source attribution `createTime`,缺失时才回退 outbox `createTime`。
|
||
- 不得发布 `contentText`、请求 `content`、`sourceSnapshot` 原文、`sourceObjectId`、`authorizationSnapshotId`、`lineagePayload`、`licenseRestrictionSnapshot`、`auditReason`、`requestHash`、`resultSnapshot`、错误堆栈。
|
||
- `payloadSummary` 必须是 exact allowlist:顶层只能包含 `type`、`message`、`resourceRef`、`timestamp`;`resourceRef` 只能包含 `resourceType`、`resourceId`。
|
||
|
||
### 状态机合同
|
||
|
||
Outbox 本域状态只允许:
|
||
|
||
- `queued`
|
||
- `running`
|
||
- `retryable`
|
||
- `published`
|
||
- `dead_letter`
|
||
|
||
状态流转:
|
||
|
||
```mermaid
|
||
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、blockId、blockRevision、attempt、targetStatus、errorCode。
|
||
- Worker 默认关闭:`muse.content.events.publish-worker.enabled=false`。
|
||
- 调度配置键:
|
||
- `muse.content.events.publish-worker.initial-delay-ms`
|
||
- `muse.content.events.publish-worker.fixed-delay-ms`
|
||
- `muse.content.events.publish-worker.max-attempt`
|
||
- `muse.content.events.publish-worker.claim-timeout-seconds`
|
||
|
||
### 失败路径合同
|
||
|
||
| 路径 | 结果 |
|
||
|---|---|
|
||
| command replay | 不补历史 outbox,不重复发布 |
|
||
| revision guard 失败 | 不更新 Block,不创建 source attribution,不创建 outbox |
|
||
| owner guard 失败 | 不更新 Block,不创建 source attribution,不创建 outbox |
|
||
| source attribution 缺失 | 不创建 outbox,记录安全日志 |
|
||
| sourceStatus 非 `active` | 不创建 outbox,记录安全日志 |
|
||
| ownerUserId null 或 <= 0 | 不创建 outbox,记录安全日志 |
|
||
| blockId null 或 <= 0 | 不创建 outbox,记录安全日志 |
|
||
| blockRevision null 或 <= 0 | 不创建 outbox,记录安全日志 |
|
||
| payload 构造缺 required 字段 | 创建 outbox 前拒绝并记录安全日志,不创建 outbox,不发布,错误码固定为 `CONTENT_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 |
|
||
| worker 构造出 `chunk` | 测试必须失败;生产实现不得出现该路径 |
|
||
|
||
### 依赖合同
|
||
|
||
允许:
|
||
|
||
- `muse-module-content-server` 依赖 `muse-module-events-api`。
|
||
- `muse-server` 同时装配 Content server 与 Events server。
|
||
|
||
禁止:
|
||
|
||
- `muse-module-content-server` 依赖 `muse-module-events-server`。
|
||
- `muse-module-events-server` 依赖 `muse-module-content-server`。
|
||
- Events server 依赖 AI / Knowledge / Market / Member / Content server。
|
||
- Events stream controller 查询 Content 业务表。
|
||
|
||
## 涉及文件
|
||
|
||
### 生产代码
|
||
|
||
- Modify: `muse-cloud/muse-module-content/muse-module-content-server/pom.xml`
|
||
- Modify: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/ContentAppServiceImpl.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/ContentEventPublishOutboxService.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/ContentEventPublishOutboxServiceImpl.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/ContentEventPublishWorker.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/ContentEventPayloads.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/dal/dataobject/ContentEventPublishOutboxDO.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/dal/mysql/ContentEventPublishOutboxMapper.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/framework/config/MuseContentEventsConfiguration.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/framework/config/MuseContentEventsProperties.java`
|
||
- Create: `muse-cloud/sql/muse/V21__extend_content_events_publish_outbox.sql`
|
||
|
||
### 测试代码
|
||
|
||
- Modify: `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentAppServiceTest.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishOutboxServiceTest.java`
|
||
- Create: `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishWorkerTest.java`
|
||
- Create: `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishDependencyTest.java`
|
||
- Create: `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishEndToEndTest.java`
|
||
- Create: `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishFlywayMigrationIT.java`
|
||
- Create: `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishMigrationSqlTest.java`
|
||
|
||
### 文档
|
||
|
||
- Modify: `docs/agent-specs/.agent`
|
||
- Create: `docs/memorys/YYYY-MM-DD-P1R7fContentSourceOwnerPropagation真实链路.md` only after implementation evidence is available
|
||
|
||
## 实施任务
|
||
|
||
### Task 0:fresh implementer preflight
|
||
|
||
在 worktree 根执行:
|
||
|
||
```bash
|
||
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
|
||
git status --short --branch
|
||
git log --oneline -5
|
||
git pull --ff-only origin dev/1.0.0
|
||
git diff --quiet -- docs/api-contracts/account/openapi.yaml 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
|
||
git diff --cached --quiet -- docs/api-contracts/account/openapi.yaml 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
|
||
ls muse-cloud/sql/muse/V*__*.sql | sort -V | tail
|
||
```
|
||
|
||
完成条件:
|
||
|
||
- 记录 dirty baseline。
|
||
- 确认 protected staged / unstaged diff 为空。
|
||
- 确认 V21 编号仍可用;若最新 migration 已不是 V20,停止并修订本执行版。
|
||
- 只读核对 Content server 当前没有 events-api dependency。
|
||
- 只读核对 Events server 仍不依赖 Content server。
|
||
|
||
### Task 1:依赖与配置骨架
|
||
|
||
目标:
|
||
|
||
- Content server 只新增 `muse-module-events-api` direct dependency。
|
||
- 新增 Content Events worker 配置,默认关闭。
|
||
|
||
文件:
|
||
|
||
- Modify: `muse-cloud/muse-module-content/muse-module-content-server/pom.xml`
|
||
- Create: `MuseContentEventsConfiguration.java`
|
||
- Create: `MuseContentEventsProperties.java`
|
||
|
||
要求:
|
||
|
||
- 不新增 `muse-module-events-server`。
|
||
- 配置 prefix:`muse.content.events`。
|
||
- 默认:
|
||
- `publish-worker.enabled=false`
|
||
- `publish-worker.max-attempt=5`
|
||
- `publish-worker.claim-timeout-seconds=60`
|
||
- 配置类必须可被 Spring 扫描。
|
||
- 关键配置、状态转换和 worker 禁用分支必须写中文注释,说明 WHY。
|
||
|
||
Focused tests:
|
||
|
||
- 配置默认值。
|
||
- worker disabled 时 `dispatchOnce()` 不 claim。
|
||
|
||
### Task 2:V21 outbox schema
|
||
|
||
目标:
|
||
|
||
- 新增 `muse_content_event_publish_outbox`,只服务 Content -> Events publish compensation。
|
||
|
||
文件:
|
||
|
||
- Create: `muse-cloud/sql/muse/V21__extend_content_events_publish_outbox.sql`
|
||
- Create: `ContentEventPublishOutboxDO.java`
|
||
- Create: `ContentEventPublishOutboxMapper.java`
|
||
- Create: `P1rContentEventsPublishMigrationSqlTest.java`
|
||
|
||
表字段必须包含:
|
||
|
||
- `id`
|
||
- `tenant_id`
|
||
- `outbox_id`
|
||
- `command_id`
|
||
- `source_command_id`
|
||
- `source_attribution_id`
|
||
- `owner_user_id`
|
||
- `work_id`
|
||
- `block_id`
|
||
- `block_revision`
|
||
- `source_status`
|
||
- `source_revision`
|
||
- `event_type`
|
||
- `notification_type`
|
||
- `resource_ref_type`
|
||
- `resource_ref_id`
|
||
- `payload_summary`
|
||
- `publish_status`
|
||
- `attempt_count`
|
||
- `max_attempt`
|
||
- `next_retry_at`
|
||
- `claimed_at`
|
||
- `claim_expires_at`
|
||
- `last_error_code`
|
||
- `last_error_message`
|
||
- `published_event_id`
|
||
- `published_sequence_no`
|
||
- `creator`
|
||
- `updater`
|
||
- `create_time`
|
||
- `update_time`
|
||
- `deleted`
|
||
|
||
唯一约束:
|
||
|
||
- `(tenant_id, outbox_id)`
|
||
- `(tenant_id, command_id)`
|
||
- `(tenant_id, block_id, block_revision, notification_type)`
|
||
|
||
Check 约束:
|
||
|
||
- `event_type = 'notification'`
|
||
- `notification_type = 'source_status_change'`
|
||
- `source_status = 'active'`
|
||
- `publish_status in ('queued','running','retryable','published','dead_letter')`
|
||
- `owner_user_id > 0`
|
||
- `block_id > 0`
|
||
- `block_revision > 0`
|
||
- `resource_ref_id > 0`
|
||
- `attempt_count >= 0`
|
||
- `max_attempt > 0`
|
||
|
||
索引:
|
||
|
||
- claim index:`publish_status, next_retry_at, claim_expires_at`
|
||
- owner status index:`owner_user_id, publish_status`
|
||
- block revision index:`block_id, block_revision`
|
||
|
||
Trigger:
|
||
|
||
- `update_time` trigger。
|
||
|
||
Mapper 方法:
|
||
|
||
- `insertIgnore`
|
||
- `selectByTenantIdAndOutboxId`
|
||
- `selectByTenantIdAndCommandId`
|
||
- `selectByTenantIdAndBlockIdAndBlockRevisionAndNotificationType`
|
||
- `claimNextPublishOutbox`
|
||
- `markPublished`
|
||
- `markRetryable`
|
||
- `markDeadLetter`
|
||
|
||
SQL gate:
|
||
|
||
- 断言字段、唯一约束、check 约束、索引、trigger。
|
||
- 断言 `event_type` check 是单值 `notification`,不能包含 `chunk` 或 OR 放宽。
|
||
- 断言 `notification_type` check 是单值 `source_status_change`。
|
||
- 断言 `source_status` check 是单值 `active`,不能包含 `deleted` 或 OR 放宽。
|
||
- 断言非法 `event_type='chunk'`、非法 `notification_type`、非法 `source_status='deleted'`、非法 `publish_status`、非法 `owner_user_id`、非法 `block_id`、非法 `block_revision`、非法 `resource_ref_id` 在 Flyway `_test` 中被 DB 拒绝。
|
||
|
||
### Task 3:Content outbox service
|
||
|
||
目标:
|
||
|
||
- 在 `saveBlock` 首次成功事务内,为 `BlockSourceAttributionDO(sourceStatus=active)` 创建一条 outbox。
|
||
|
||
文件:
|
||
|
||
- Create: `ContentEventPublishOutboxService.java`
|
||
- Create: `ContentEventPublishOutboxServiceImpl.java`
|
||
- Create: `ContentEventPayloads.java`
|
||
- Modify: `ContentAppServiceImpl.java`
|
||
- Test: `ContentEventPublishOutboxServiceTest.java`
|
||
- Test: `ContentAppServiceTest.java`
|
||
|
||
接入点:
|
||
|
||
- `ContentAppServiceImpl.saveBlock`
|
||
- 在 `sourceAttributionMapper.insert(...)` 后调用 outbox service。
|
||
- command replay 返回时不得创建 outbox。
|
||
- revision guard / owner guard 失败时不得创建 outbox。
|
||
|
||
Outbox service 输入:
|
||
|
||
- `userId`
|
||
- `workId`
|
||
- `blockId`
|
||
- `blockRevision`
|
||
- `sourceCommandId = reqVO.getCommandId()`
|
||
- `BlockSourceAttributionDO sourceAttribution`
|
||
|
||
Payload 构造:
|
||
|
||
```json
|
||
{
|
||
"type": "source_status_change",
|
||
"message": "内容来源状态已更新",
|
||
"resourceRef": {
|
||
"resourceType": "content_block",
|
||
"resourceId": 8001
|
||
},
|
||
"timestamp": "2026-06-08T12:00:00"
|
||
}
|
||
```
|
||
|
||
Payload validator:
|
||
|
||
- 顶层 exact keys:`type/message/resourceRef/timestamp`。
|
||
- `resourceRef` exact keys:`resourceType/resourceId`。
|
||
- `type=source_status_change`。
|
||
- `message` 只允许 `内容来源状态已更新`。
|
||
- `resourceType=content_block`。
|
||
- `resourceId` 必须是 Number。
|
||
- 不允许 `contentText`、`content`、`sourceSnapshot`、`sourceObjectId`、`authorizationSnapshotId`、`lineagePayload`、`licenseRestrictionSnapshot`、`auditReason`、`requestHash`、`resultSnapshot`、`stackTrace`。
|
||
|
||
错误码:
|
||
|
||
- payload invalid 固定为 `CONTENT_EVENTS_PAYLOAD_INVALID`。
|
||
|
||
Focused tests:
|
||
|
||
- successful saveBlock 首次执行在 source attribution 插入后创建 outbox。
|
||
- command replay 不创建 outbox。
|
||
- revision conflict 不创建 outbox。
|
||
- owner mismatch 不创建 outbox。
|
||
- duplicate `(blockId, revision, source_status_change)` 不创建第二条 outbox。
|
||
- invalid owner 不创建 outbox 并记录安全日志。
|
||
- sourceStatus 非 active 不创建 outbox。
|
||
- payload exact allowlist,敏感字段负断言。
|
||
- `eventType` 固定 `notification`,`notificationType` 固定 `source_status_change`。
|
||
- 负断言 Content outbox service 不生成 `chunk`。
|
||
- configured maxAttempt 生效。
|
||
- timestamp 使用 source attribution createTime,缺失时回退 outbox createTime。
|
||
|
||
### Task 4:Content publish worker
|
||
|
||
目标:
|
||
|
||
- Claim Content outbox,构造 `EventsPublishReqDTO`,调用 `EventsPublishApi`,按结果更新 outbox 状态。
|
||
|
||
文件:
|
||
|
||
- Create: `ContentEventPublishWorker.java`
|
||
- Test: `ContentEventPublishWorkerTest.java`
|
||
|
||
Events request:
|
||
|
||
- `commandId = outbox.commandId`
|
||
- `tenantId = outbox.tenantId`
|
||
- `ownerUserId = outbox.ownerUserId`
|
||
- `sourceOwner = content`
|
||
- `sourceType = content_block_source_attribution`
|
||
- `sourceId = String.valueOf(outbox.blockId)`
|
||
- `sourceRevision = String.valueOf(outbox.blockRevision)`
|
||
- `eventType = notification`
|
||
- `resourceType = outbox.resourceRefType`
|
||
- `resourceId = String.valueOf(outbox.resourceRefId)`
|
||
- `payloadSummary = parsed exact allowlist payload`
|
||
- `emittedAt = outbox.createTime`
|
||
|
||
状态处理:
|
||
|
||
- accepted -> `published`
|
||
- duplicate accepted -> `published`
|
||
- rejected / blocked -> `dead_letter`
|
||
- CommonResult error -> `retryable` 或耗尽后 `dead_letter`
|
||
- exception -> `retryable` 或耗尽后 `dead_letter`
|
||
- payload invalid -> `dead_letter`,不调用 Events
|
||
- unknown status -> `dead_letter`
|
||
|
||
Focused tests:
|
||
|
||
- disabled 不 claim。
|
||
- enabled claim 使用配置 claim timeout。
|
||
- accepted 回写 eventId / sequenceNo。
|
||
- duplicate accepted 回写既有 eventId / sequenceNo。
|
||
- rejected / blocked dead_letter。
|
||
- CommonResult error 非最后一次 retryable,最后一次 dead_letter。
|
||
- exception 非最后一次 retryable,最后一次 dead_letter。
|
||
- invalid payload dead_letter 且不调用 Events。
|
||
- owner invalid dead_letter 且不调用 Events。
|
||
- sourceStatus 非 active dead_letter 且不调用 Events。
|
||
- terminal update 使用 claimed attempt ownership。
|
||
- stale terminal update 返回 0 时记录诊断日志。
|
||
- worker 发出的 `EventsPublishReqDTO.eventType` 必须是 `notification`。
|
||
- worker payload `type` 必须是 `source_status_change`。
|
||
- 负断言 worker 不调用 `chunk`,且 payload 不包含正文。
|
||
|
||
### Task 5:P1R executable gates
|
||
|
||
目标:
|
||
|
||
- 用 server 侧 gate 证明依赖方向、focused E2E、V21 migration、anti-chunk 合同和既有 owner gate 不回退。
|
||
|
||
文件:
|
||
|
||
- Create: `P1rContentEventsPublishDependencyTest.java`
|
||
- Create: `P1rContentEventsPublishEndToEndTest.java`
|
||
- Create: `P1rContentEventsPublishFlywayMigrationIT.java`
|
||
- Create: `P1rContentEventsPublishMigrationSqlTest.java`
|
||
|
||
Dependency test:
|
||
|
||
- Content server 有 direct `muse-module-events-api`。
|
||
- Content server 无 `muse-module-events-server`。
|
||
- Events server 无 AI / Knowledge / Market / Member / Content server。
|
||
|
||
E2E focused test:
|
||
|
||
- 构造 `saveBlock` source fact。
|
||
- Content outbox service 创建 outbox。
|
||
- Worker 成功路径必须调用真实 `EventsPublishServiceImpl`;只允许使用 in-memory / mocked `UnifiedEventMapper` 承接 unified event 行,禁止 bare fake `EventsPublishApi` 直接返回 accepted 作为成功证据。
|
||
- `muse_unified_event` 中 owner 为作品 owner。
|
||
- SSE visible query 对作品 owner 可见,对其他 owner 不可见。
|
||
- unified event `eventType=notification`,payload `type=source_status_change`。
|
||
- 负断言没有 `chunk` event。
|
||
- payload 不含正文、sourceSnapshot、authorizationSnapshotId、lineage、license restriction、auditReason、requestHash、resultSnapshot、错误堆栈。
|
||
- 同一 block revision duplicate replay 返回同一 eventId / sequenceNo。
|
||
- 同一 block 不同 revision 产生不同 notification。
|
||
- rejected / blocked -> outbox dead_letter,visible query 为空。
|
||
- sourceStatus 非 active -> outbox dead_letter 或创建前拒绝,visible query 为空,且不调用 Events。
|
||
- stale claim terminal update 被忽略。
|
||
|
||
Flyway `_test`:
|
||
|
||
- 使用真实 PostgreSQL `_test` 数据库。
|
||
- 跑到 V21。
|
||
- 验证 table / indexes / constraints / trigger。
|
||
- 验证非法 insert 被 DB 拒绝。
|
||
- 验证 `event_type='chunk'` 被 DB 拒绝。
|
||
- 验证 `source_status='deleted'` 被 DB 拒绝。
|
||
|
||
### Task 6:最终验证、review 和留痕
|
||
|
||
最终验证顺序必须先 build,再 tests,避免 `clean` 清理 surefire XML。
|
||
|
||
#### 1. Full reactor build
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
期望:live reactor 全量 modules success。
|
||
|
||
#### 2. Content focused tests
|
||
|
||
先断言新增测试文件存在:
|
||
|
||
```bash
|
||
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
|
||
test -f muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishOutboxServiceTest.java && \
|
||
test -f muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishWorkerTest.java && \
|
||
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishMigrationSqlTest.java && \
|
||
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishDependencyTest.java && \
|
||
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishEndToEndTest.java && \
|
||
test -f muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishFlywayMigrationIT.java
|
||
```
|
||
|
||
运行:
|
||
|
||
```bash
|
||
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-content/muse-module-content-server -am \
|
||
-Dtest=ContentAppServiceTest,ContentEventPublishOutboxServiceTest,ContentEventPublishWorkerTest \
|
||
-Dsurefire.failIfNoSpecifiedTests=false
|
||
```
|
||
|
||
必须检查 surefire XML,目标测试类 `tests>0` 且 failures/errors/skipped 均为 0。
|
||
|
||
#### 3. P1R mixed executable gates
|
||
|
||
```bash
|
||
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=P1rContentEventsPublishMigrationSqlTest,P1rContentEventsPublishDependencyTest,P1rContentEventsPublishEndToEndTest,P1rContentRealApiGateTest,P1rAccountEventsPublishMigrationSqlTest,P1rAccountEventsPublishDependencyTest,P1rAccountEventsPublishEndToEndTest,P1rAccountRealApiGateTest,P1rAccountRouteOwnershipTest,P1rMarketEventsPublishMigrationSqlTest,P1rMarketEventsPublishDependencyTest,P1rMarketEventsPublishEndToEndTest,P1rKnowledgeEventsPublishMigrationSqlTest,P1rKnowledgeEventsPublishDependencyTest,P1rKnowledgeEventsPublishEndToEndTest,P1rAiEventsPublishMigrationSqlTest,P1rAiEventsPublishDependencyTest,P1rAiEventsPublishEndToEndTest,P1rApiCoverageReportTest,P1rEventsRealApiGateTest,P1rEventsRouteOwnershipTest,P1rAiRealApiGateTest,P1rAiRouteOwnershipTest \
|
||
-Dsurefire.failIfNoSpecifiedTests=false
|
||
```
|
||
|
||
必须检查 surefire XML,新增 Content 三个 P1R gate、既有 Content real API gate、Account/Market/Knowledge/AI/Events 目标测试类 `tests>0` 且 failures/errors/skipped 均为 0:
|
||
|
||
```bash
|
||
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-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishMigrationSqlTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishDependencyTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishEndToEndTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentRealApiGateTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountEventsPublishMigrationSqlTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountEventsPublishDependencyTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountEventsPublishEndToEndTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountRealApiGateTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountRouteOwnershipTest.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.P1rKnowledgeEventsPublishMigrationSqlTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rKnowledgeEventsPublishDependencyTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rKnowledgeEventsPublishEndToEndTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiEventsPublishMigrationSqlTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiEventsPublishDependencyTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiEventsPublishEndToEndTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rApiCoverageReportTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rEventsRealApiGateTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rEventsRouteOwnershipTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiRealApiGateTest.xml",
|
||
"muse-cloud/muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiRouteOwnershipTest.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
|
||
```
|
||
|
||
#### 4. Flyway `_test`
|
||
|
||
```bash
|
||
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud && \
|
||
. ~/.config/muse-repo/infra.env && \
|
||
export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD" && \
|
||
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o test -pl muse-server -am \
|
||
-Dtest=P1rContentEventsPublishFlywayMigrationIT \
|
||
-Dflyway.postgresql.transactional.lock=false \
|
||
-Dp1r.flyway.locations=filesystem:sql/muse \
|
||
-Dp1r.flyway.url="jdbc:postgresql://$MUSE_POSTGRES_HOST:$MUSE_POSTGRES_PORT/muse_p1r7f_content_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
|
||
```
|
||
|
||
期望:
|
||
|
||
- migrations_executed = 21。
|
||
- target_schema_version = 21。
|
||
- `muse_content_event_publish_outbox` 存在。
|
||
- `event_type='chunk'` insert 被拒绝。
|
||
- `source_status='deleted'` insert 被拒绝。
|
||
- 目标测试 XML tests > 0,failures/errors/skipped 均为 0。
|
||
|
||
#### 5. Dependency tree gate
|
||
|
||
```bash
|
||
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 -pl muse-module-content/muse-module-content-server -DskipTests dependency:tree > /tmp/p1r7f-content-server-dependency-tree.txt && \
|
||
JAVA_HOME=$(/usr/libexec/java_home -v 21) mvn -o -pl muse-module-events/muse-module-events-server -DskipTests dependency:tree > /tmp/p1r7f-events-server-dependency-tree.txt && \
|
||
rg "muse-module-events-api" /tmp/p1r7f-content-server-dependency-tree.txt && \
|
||
! rg "muse-module-events-server" /tmp/p1r7f-content-server-dependency-tree.txt && \
|
||
! rg "muse-module-(ai|knowledge|market|member|content)-server" /tmp/p1r7f-events-server-dependency-tree.txt
|
||
```
|
||
|
||
#### 6. Isolated coverage scanner
|
||
|
||
只允许在 `/tmp` 隔离副本运行 scanner:
|
||
|
||
```bash
|
||
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
|
||
tmpdir=$(mktemp -d /tmp/p1r7f-coverage-scan.XXXXXX) && \
|
||
rsync -a --delete --exclude .git --exclude 'muse-cloud/**/target' ./ "$tmpdir"/ && \
|
||
cd "$tmpdir" && \
|
||
python3 -m venv .venv && \
|
||
. .venv/bin/activate && \
|
||
python -m pip install --quiet PyYAML && \
|
||
python muse-cloud/scripts/p1r-audit-api-coverage.py --check
|
||
```
|
||
|
||
期望:
|
||
|
||
- scanner exit 0。
|
||
- completed=100。
|
||
- needsVerification=133。
|
||
- incomplete=0。
|
||
- genericPersistence=0。
|
||
- ssePlaceholder=0。
|
||
- Events `streamEvents=dedicated / needs_verification`。
|
||
- Content 51 operations 仍为 `dedicated / needs_verification`。
|
||
- AI / Knowledge / Market / Account source owner gates 不回退。
|
||
|
||
#### 7. Protected diff and whitespace
|
||
|
||
```bash
|
||
cd /Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0 && \
|
||
git diff --check && \
|
||
git diff --quiet -- docs/api-contracts/account/openapi.yaml 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 && \
|
||
git diff --cached --quiet -- docs/api-contracts/account/openapi.yaml 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
|
||
```
|
||
|
||
#### 8. Memory
|
||
|
||
实现 evidence 可用后新增:
|
||
|
||
- `docs/memorys/2026-06-08-P1R7fContentSourceOwnerPropagation真实链路.md`
|
||
|
||
必须记录:
|
||
|
||
- 目标和非目标。
|
||
- 关键实现文件。
|
||
- focused tests 数量。
|
||
- P1R mixed gates 数量。
|
||
- Flyway `_test` 结果。
|
||
- dependency tree gate。
|
||
- isolated coverage scanner 目录和 summary。
|
||
- protected diff。
|
||
- fresh implementation review 结果。
|
||
- 明确说明仍不代表 Events / P1R-7 / Content completed。
|
||
|
||
## 回滚策略
|
||
|
||
- 配置层回滚:保持或恢复 `muse.content.events.publish-worker.enabled=false`,worker 不再 claim 新 outbox。
|
||
- 代码层回滚:回退 Content outbox service 注入点与 worker/config/pom 改动;不触碰 OpenAPI/scanner/coverage report。
|
||
- 数据层回滚:V21 只新增 outbox 表、索引、约束和 trigger;若未发布生产数据,可按迁移策略 drop 新表;若已有数据,先停 worker,再导出 outbox 审计数据后按 DBA 审批处理。
|
||
- 状态层回滚:不得把 rollback 描述成 completed 撤销;P1R coverage 状态仍保持 `needs_verification`。
|