test(p1r): 收口 Content 第一批 completed approval 门禁

This commit is contained in:
zizi 2026-06-12 13:04:42 +08:00
parent 00327749a2
commit 3ced553cb2
17 changed files with 3334 additions and 52 deletions

View File

@ -163,3 +163,23 @@ P1R Market Completed Approval 修订执行版最终 fresh review 已双 PASSB
P1R Market 第一批 operation-level completed approval implementation 当前状态:用户已批准执行版四项前置条件,本轮只把 4 个 Market operation 加入 operation-level completed approval未把 `market` 加入 domain-level allowlist未修改 Market 业务实现或数据库迁移。4 个 completed operation 为 `getMarketplaceAsset`、`listMarketplaceCategories`、`favoriteAsset`、`unfavoriteAsset``listMarketplaceAssets`、`listMarketplaceRecommendations` 以及 purchase/install/handoff/governance/publish/review/appeal/projection 相关 28 个 Market operation 继续 `dedicated/needs_verification`。OpenAPI 仅在 `docs/api-contracts/market/openapi.yaml` 为 `favoriteAsset` / `unfavoriteAsset` 增加 required `X-Command-Id` header修正已有实现强校验与合同不一致scanner 仍保持 `APPROVED_COMPLETED_DOMAINS={"ai","knowledge"}`,只追加上述 4 个 `market:*` operation key。TDD RED 已验证旧 report 下 focused P1R gates 因 expected completed=131 but was 127、Market expected 4 completed but was 0 等断言失败GREEN 后 scanner 输出 summary `233/131/102/0/0/0`Market 为 `completed=4`、`needs_verification=28`。验证 evidence`python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check` 通过Market focused verification 为 Market module 26/26 pass 与 muse-server 25/25 pass新增 `P1rMarketDiscoveryFavoriteCompletedApprovalIT` 使用 MockMvc HTTP 入口与真实 PostgreSQL `_test` 库 `muse_p1r_market_discovery_favorite_completed_approval_test` 为 9/9 pass覆盖 detail/categories/favorite/unfavorite happy/error/replay/inactive/no-write增强 `P1rMarketFlywayMigrationIT` 使用真实 PostgreSQL `_test` 库 `muse_p1r_market_completed_approval_test` 为 4/4 pass输出 V1-V15 clean migrate 并验证 favorite 表、索引、约束、trigger、duplicate tenant/user/asset 拒绝、duplicate tenant/command_id 拒绝和 inactive update trigger 可执行P1R mixed gates `P1rApiCoverageReportTest,P1rMarketRealApiGateTest,P1rEventsRealApiGateTest,P1rAiRealApiGateTest,P1rKnowledgeRealApiGateTest` 为 33/33 passXML 防空跑覆盖 13 个目标 XML全部 failures/errors/skipped=0`git diff --check` 通过OpenAPI diff 仅限 Market favorite/unfavorite `X-Command-Id` header。fresh correctness review 指出新增 Discovery IT 脱敏 `p1r.flyway.url/user` 后未恢复会污染同 JVM 后续 Flyway IT该问题已修复为 Discovery IT 与 Flyway IT 均保存原始 system property 并在 `@AfterAll` 恢复,组合验证使用真实 PostgreSQL `_test` 库 `muse_p1r_market_combined_completed_approval_test`,普通顺序与 `-Dsurefire.runOrder=reversealphabetical` 强制反向顺序均通过,均为 13/13 pass。fresh data/testing review 提到“真实 HTTP”若理解为 socket 级 live server则当前证据不足按执行版原文本轮要求是 WebApplicationContext/MockMvc + real mapper + `_test` DB不是 live socket servermemory 已将措辞收窄为 MockMvc HTTP 入口证据;其提出的 status CHECK / FK 属于新增迁移范围,当前执行版明确禁止改迁移,作为后续 Market schema hardening 风险保留,不作为本轮 approved diffDirac 复审提出 raw Surefire XML 会记录原始 host/user 但未泄露 password当前作为 P3 artifact 边界记录raw XML 不应直接外发,外发前需清洗 host/user 或后续改造 URL/user 传递方式。新增 memory`docs/memorys/2026-06-11-P1RMarket状态推进.md`。当前尚未提交、未 push下一步必须重新派发 fresh implementation spec/correctness review + fresh implementation quality/data-integrity/testing review双 PASS 前不得提交、push 或宣称 Market 32/32、Content、Account remaining 或总 P1R completed。
P1R Market 第一批 operation-level completed approval fresh implementation review 已双 PASSRaman spec/correctness review PASS、Hubble data-integrity/testing review PASS均无 P0/P1/P2/P3 阻塞项。Raman 已验证正确 worktree、scanner 未加入 `market` domain allowlist、只追加 4 个 `market:*` operation key、coverage summary 为 `233/131/102/0/0/0`、Market 为 `4 completed / 28 needs_verification`、`listMarketplaceAssets` 与 `listMarketplaceRecommendations` 继续 `dedicated/needs_verification`、OpenAPI diff 仅限 favorite/unfavorite required `X-Command-Id`、Market main/java 与 SQL diff 为空、文档未宣称 Market 32/32 或总 P1R completed。Hubble 已验证 Discovery IT 使用 WebApplicationContext + MockMvc + real mapper/service + PostgreSQL `_test`Flyway IT 覆盖 favorite unique/index/trigger/duplicate/no-write 证据,双向组合顺序 13/13 pass 关闭同 JVM system property 污染风险13 个目标 XML 合计 85 tests 且 failures/errors/skipped 均为 0。非阻塞注意事项favorite `status` CHECK 与 FK 属于后续 schema hardeningraw Surefire XML 暴露 DB host/user 但未泄露 password外发前需清洗。该双 PASS 只代表本轮 Market 第一批 4 个 operation-level completed approval 具备收口条件;不代表 Market 32/32、Content、Account remaining 或总 P1R completed。当前尚未提交、未 push提交或 push 仍需用户明确指令。
P1R Content Completed Approval 已按用户确认启动审阅版:`docs/agent-specs/2026-06-11-P1RContentCompletedApproval审阅版.md`。本审阅版只冻结 Content 51 个 `dedicated/needs_verification` operation 的 completed approval 证据标准和推荐路线,不修改 OpenAPI、scanner、coverage report 或 Content 业务实现,也不推进 Content/Account remaining/Market remaining/总 P1R completed。只读盘点事实当前 coverage summary 为 `233/131/102/0/0/0`Content 51 个 operation 全部仍为 `dedicated/needs_verification`P1R-7f 已给 `saveBlock -> source attribution active -> Content outbox -> Events -> SSE owner visible` 提供 needs-verification evidence但仍不等同 `saveBlock` completed。审阅版推荐不要做 Content 51/51 domain-level completed approval第一批推荐只做 8 个 operation`listWorks`、`getWork`、`listChapters`、`getChapter`、`listBlocks`、`getBlock`、`saveBlock`、`getBlockSourceAttribution`。只读子代理提出 15 个用户端 CRUD operation 也具备实现候选性,但当前 `createWork` / `updateWork` / `createChapter` 等写命令存在 OpenAPI 或 coverage `requiresCommandId` 口径与服务端 VO 不一致风险,因此审阅版默认不把创建/更新/删除合同修正夹带进第一批。导入/导出/解析/Meta projection/AI planning candidate/style/suggestion 均因 FileService、AI、Knowledge、Meta 等外部 owner closure 缺口暂缓admin read 和正式 planning section 可作为后续独立切片。下一步必须对该 Content 审阅版做 fresh spec/scope review + fresh quality/feasibility review双 PASS 前不得写执行版、实现、提交、push 或修改 scanner/report/gate。
P1R Content Completed Approval 审阅版首轮 reviewFeynman spec/scope review PASS但 Herschel quality/feasibility review FAILFeynman PASS 因后续文档修订失效,不能计入最终 PASS gate。Herschel 的 3 个 P1 和 1 个 P2 已只读验证并修订:第一,`saveBlock` completed approval 依赖 P1R-7f source/event 证据,执行版必须把 `P1rContentEventsPublishMigrationSqlTest`、`P1rContentEventsPublishDependencyTest`、`P1rContentEventsPublishEndToEndTest`、`P1rContentEventsPublishFlywayMigrationIT` 纳入必跑和 XML 防空跑第二HTTP/DB IT 必须覆盖 tenant 与 owner 双隔离,至少 seed 两个 tenant验证同 owner 不同 tenant 不可见、跨 tenant `saveBlock` 不写 block/source/outbox、写入行 `tenant_id` 正确第三Content completed IT 与 Content events Flyway IT 必须保存并在 `@AfterAll` 恢复 `p1r.flyway.url/user`做普通顺序与反序组合验证raw Surefire XML 只能作为本地证据,外发前清洗 DB host/user第四执行版必须明确 Flyway current=V21、成功 SQL migration 数量 21、V21 outbox 表/索引/约束/trigger 与 invalid insert 拒绝,且 V9 planning schema 不属于本第一批业务验收范围。由于审阅版已在 review 后修订,下一步必须重新派发 fresh spec/scope review + fresh quality/feasibility review双 PASS 前不得写执行版、实现、提交、push 或推进 Content completed。
P1R Content Completed Approval 审阅版第二轮 reviewBernoulli spec/scope review PASS但 Wegener quality/feasibility/testing review FAILBernoulli PASS 因后续文档修订失效,不能计入最终 PASS gate。Wegener 的 2 个 P1 已只读验证并修订:第一,执行版必须把 `P1rAccountRealApiGateTest`、`P1rMetaRealApiGateTest` 纳入必跑、allowed diff 审批项和 XML 防空跑边界,防止 Content 状态推进时漏掉 Account remaining 23 与 Meta completed 防回退断言第二8 个候选 operation 的 runtime evidence 必须包含 shadow-path matrix覆盖列表空页、资源不存在、path mismatch、跨 owner / 跨 tenant 拒绝,以及 `saveBlock` 缺 `commandId`、缺 `expectedRevision`、缺 `sourceSnapshot`、revision conflict 等 no-write 负路径。由于审阅版已在 review 后修订,下一步必须重新派发 fresh spec/scope review + fresh quality/feasibility review双 PASS 前不得写执行版、实现、提交、push 或推进 Content completed。
P1R Content Completed Approval 审阅版第三轮 reviewLeibniz quality/feasibility/testing review FAILP1 有效并已只读验证:`saveBlock` 与 `getBlockSourceAttribution` 都通过 `requireOwnedBlock(userId, workId, blockId)` 绑定 path workId 与 block.workId因此执行版必须覆盖“同 owner 但 block 属于另一个 work”的 path mismatch`saveBlock` 要断言 block/source/outbox/command no-write 或事务回滚后行数不变,`getBlockSourceAttribution` 要断言错误响应且不泄露 attribution。由于审阅版已在 review 后修订,本轮任何已返回或未返回的 PASS 都不能计入最终 PASS gate下一步必须重新派发 fresh spec/scope review + fresh quality/feasibility review。
P1R Content Completed Approval 审阅版第四轮 fresh review 已双 PASSFermat spec/scope review PASS、Faraday quality/feasibility/testing review PASS均无 P0/P1/P2/P3 blocker。Fermat 已验证审阅版仍停留在 review/scope 决策层,第一批 8 个 operation、`N=8` 目标值、非首批 scope 排除、`P1rAccountRealApiGateTest` / `P1rMetaRealApiGateTest` 非目标防回退、P1 修订点和 `.agent` 失效链路均自洽。Faraday 已验证后续执行版硬条件覆盖 MockMvc HTTP 入口、真实 PostgreSQL `_test`、真实 mapper/service、Flyway V1-V21、V21 outbox schema/trigger/invalid insert、fresh XML 防空跑、system property restore 与普通/反序组合验证8 ops runtime matrix 已覆盖 happy/empty/missing/path mismatch/same-owner wrong workId/cross owner/cross tenant/写命令缺字段/revision conflict/no-write/outbox no-duplicate。该双 PASS 只代表 Content 审阅版可进入执行版编写;不代表 Content completed也不允许未获批准前修改 scanner/report/OpenAPI/业务实现/gate tests。
P1R Content Completed Approval 执行版已新增:`docs/agent-specs/2026-06-12-P1RContentCompletedApproval执行版.md`。执行版只锁定第一批 8 个 operation-level approval`listWorks`、`getWork`、`listChapters`、`getChapter`、`listBlocks`、`getBlock`、`saveBlock`、`getBlockSourceAttribution`;不把 `content` 加入 domain-level completed allowlist。目标 summary 仅作为获批后执行目标:`233/139/94/0/0/0`Content 目标为 `completed=8`、`needsVerification=43`。执行版明确要求用户再次批准 4 项后才可实施8 ops 清单、operation-level approval、allowed diff、6 个 mixed gate 的 summary / Content / 非目标域防回退断言同步。当前只写执行方案和 `.agent`,未修改 OpenAPI、scanner、coverage report、Content 业务实现、SQL migration 或 gate test。下一步必须 fresh execution spec/scope review + fresh execution quality/feasibility review双 PASS 前不得实现、提交、push 或推进 Content completed。
P1R Content Completed Approval 执行版 fresh review 已双 PASSRawls execution spec/scope review PASS、Planck execution quality/feasibility/testing review PASS均无 P0/P1/P2/P3 blocker。Rawls 已验证正确 worktree、HEAD `0032774`、执行版严格锁定第一批 8 个 operation、不做 Content 51/51、不加入 `content` domain-level allowlist、目标值为获批后 `233/139/94` 与 Content `8 completed / 43 needs_verification`、43 个保留 `needs_verification` operation 与 coverage report 机械比对无缺失或额外项、allowed/prohibited diff 自洽且保护面无 diff。Planck 已验证 TDD RED/GREEN、Content HTTP+DB `_test` gate、`saveBlock` 依赖的 source/event/Flyway V21 mandatory gate、system property restore、普通/反序组合验证、XML 防空跑和 diff gate 均可执行;其只读审阅未运行 Maven/Flyway后续 implementation 仍必须按执行版实际跑完验证。该双 PASS 只代表 Content 执行版可进入用户批准点;不代表 Content completed也不允许未获批准前修改 scanner/report/OpenAPI/业务实现/SQL migration/gate tests。下一步必须由用户明确批准四项1) 8 ops 清单2) operation-level approval 且不加 `content` domain allowlist3) 执行版 allowed diff4) 6 个 mixed gate 的 summary / Content / 非目标域防回退断言同步。
P1R Content 第一批 operation-level completed approval implementation 当前状态:用户已批准执行版四项前置条件,本轮只把 8 个 Content operation 加入 operation-level completed approval未把 `content` 加入 domain-level allowlist未修改 OpenAPI、Content 业务实现或 SQL migration。8 个 completed operation 为 `listWorks`、`getWork`、`listChapters`、`getChapter`、`listBlocks`、`getBlock`、`saveBlock`、`getBlockSourceAttribution`;其余 43 个 Content operation 继续 `dedicated/needs_verification`。scanner 仍保持 `APPROVED_COMPLETED_DOMAINS={"ai","knowledge"}`,只追加 8 个 `content:*` operation keycoverage summary 已推进为 `233/139/94/0/0/0`Content 为 `8 completed / 43 needs_verification`。TDD RED 已验证旧 report 下 focused P1R gates 因目标 summary 与 Content 8 个 operation 尚未 completed 而失败GREEN 后 `python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check` 通过并重生成 report。新增 `P1rContentCoreCompletedApprovalIT` 使用 MockMvc HTTP 入口、真实 mapper/service 与 PostgreSQL `_test` 库 `muse_p1r_content_core_completed_approval_test`,当前目标为 12/12 pass覆盖 8 个 operation 的 happy/empty/missing/path mismatch/same-owner wrong work/cross owner/cross tenant、`saveBlock` command/revision/source/outbox/command replay/conflict/no-write、`getBlockSourceAttribution` active/current/empty/no-writefresh testing reviewHooke指出 chapter/block missing 负路径缺显式 HTTP 断言,已补 `missing work chapter list`、`missing chapter detail`、`missing chapter block list`、`missing block detail` 和 no-write outbox 断言。修复后已重跑 Core IT`P1rContentCoreCompletedApprovalIT` tests=12、failures=0、errors=0、skipped=0XML mtime 在本次运行窗口内。增强 `P1rContentEventsPublishFlywayMigrationIT` 保存并恢复 `p1r.flyway.url/user`V1-V21 clean migrate 4/4 pass验证 V21 outbox 表、索引、约束、trigger、`attempt_count<0` 与 `max_attempt<=0` invalid insert 拒绝。Hooke 修复后已重跑组合验证:普通顺序与 `-Dsurefire.runOrder=reversealphabetical` 均通过,实际 Surefire 顺序两次均为 Flyway IT 先、Core IT 后,合计 16/16 pass证明 Flyway 脱敏/恢复未污染后续 Core IT该参数未改变实际顺序留痕不得写成已证明 Core->Flyway 反向顺序。Content focused suite 已 fresh XML 防空跑P1R mixed gates 已重跑并 XML 防空49/49 pass。scanner `--check` 已重跑summary 仍为 `233/139/94/0/0/0`Content 仍为 `8 completed / 43 needs_verification`,代表性非目标 operation 仍为 `dedicated/needs_verification`。protected OpenAPI diff 为空,`git diff --check` 通过。新增 memory`docs/memorys/2026-06-12-P1RContent状态推进.md`。当前还需要重新派发 fresh implementation spec/correctness review + quality/data-integrity/testing review双 PASS 前不得提交、push 或宣称 Content 51/51、Account remaining、Market remaining 或总 P1R completed。
P1R Content 第一批 implementation fresh review 第一轮结果Bohr correctness review FAIL、Euclid testing/data-integrity review FAIL同源 P1 有效:`P1rContentCoreCompletedApprovalIT` 未覆盖执行版 mandatory 的 `saveBlock` 缺 `expectedRevision` 与缺 `sourceSnapshot` 两个 HTTP+DB no-write 负路径。已按 `superpowers:receiving-code-review` 验证反馈有效:执行版第 513 行明确要求这两项,`BlockSaveReqVO` 对两字段均有 required 语义。已最小修复:在 `should_rejectSaveBlockValidationRevisionConflictAndCommandConflictWithoutDirtyWrite` 中补 `p1r-content-missing-expected-revision` 与 `p1r-content-missing-source-snapshot` 两个请求,并复用 `assertSnapshotUnchanged` 断言 block/source/outbox/command 未写入。修复后已重跑 `P1rContentCoreCompletedApprovalIT` tests=12、failures=0、errors=0、skipped=0已重跑 Core+Flyway 组合验证tests=16、failures=0、errors=0、skipped=0两个目标 XML 均为新产物。当前仍需重新派发 fresh implementation spec/correctness review + quality/data-integrity/testing review双 PASS 前不得提交、push 或宣称 Content 51/51、Account remaining、Market remaining 或总 P1R completed。
P1R Content 第一批 operation-level completed approval fresh implementation re-review 已双 PASSMendel correctness review PASS无 findings、无 residual risksAnscombe testing/data-integrity review PASS无 findings、无 testing gaps。Mendel 已验证 Bohr/Euclid P1 已关闭,`P1rContentCoreCompletedApprovalIT` 覆盖缺 `expectedRevision` 与缺 `sourceSnapshot` 两个 `saveBlock` HTTP 负路径并复用 no-write 断言coverage summary 为 `233/139/94/0/0/0`Content 精确为 `8 completed / 43 needs_verification`scanner 未加入 `content` domain allowlist只追加 8 个 `content:*` operation keyOpenAPI、Content main/java 与 SQL migration diff 均为空Core 12/12、Core+Flyway 16/16、P1R mixed gates 49/49 通过。Anscombe 已验证 testing/data-integrity 口径,无新增 blocker唯一残余风险是 raw Surefire XML 仍可能包含本地 DB host/user但未发现 password 泄露,外发前需清洗。该双 PASS 只代表 Content 第一批 8 个 operation-level completed approval 具备提交条件;不代表 Content 51/51、Account remaining、Market remaining 或总 P1R completed。提交或 push 仍需用户明确指令。

View File

@ -0,0 +1,450 @@
# P1R Content Completed Approval 审阅版
日期2026-06-11
## 结论
不建议把 Content 51 个 operation 一次性整域推进 `completed`
推荐把 Content Completed Approval 拆成 operation-level 或证据切片推进。第一候选是用户端核心作品/章节/Block 读链路,加上已经在 P1R-7f 形成 source owner propagation evidence 的 `saveBlock``getBlockSourceAttribution`。第一批建议限定为 8 个 operation
```text
listWorks
getWork
listChapters
getChapter
listBlocks
getBlock
saveBlock
getBlockSourceAttribution
```
本审阅版只冻结 Content completed approval 的范围判断、证据缺口、推荐审批粒度和后续执行版要求。不修改 OpenAPI不修改 scanner不修改 coverage report不修改业务实现也不把 Content 或其它 domain 推进 `completed`
```mermaid
flowchart TB
Start["当前 coverage<br/>Content 51 dedicated / needs_verification"] --> Review["Content completed approval 审阅版<br/>冻结证据标准"]
Review --> Split{"是否整域 51/51 completed?"}
Split -->|否,推荐| OpLevel["按证据切片 operation-level approval"]
Split -->|是,不推荐| Domain["domain-level approval<br/>需 CRUD / source / admin / import / export / parse / planning / meta 全闭环"]
OpLevel --> First["第一候选 8 ops<br/>work/chapter/block read + saveBlock + attribution read"]
First --> Contract{"是否触碰创建/更新/删除合同?"}
Contract -->|否| Exec["执行版<br/>只写 operation allowlist + real HTTP/DB gate"]
Contract -->|是| ContractFix["先单独审批 OpenAPI/report commandId 合同修正"]
Exec --> FreshReview["fresh spec/scope review<br/>fresh quality/feasibility review"]
FreshReview --> UserApproval{"用户明确批准<br/>Content 状态推进?"}
UserApproval -->|否| Stay["保持 Content 51 needs_verification"]
UserApproval -->|是| Change["最小修改 scanner/report/gates<br/>运行 focused + P1R + _test + XML 防空跑"]
```
## 当前事实状态
工作区:
```text
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
```
当前分支与远端同步HEAD 为:
```text
0032774 test(p1r): 收口 Market 第一批 completed approval 门禁
```
当前 coverage summary
```text
total=233
completed=131
needsVerification=102
incomplete=0
genericPersistence=0
ssePlaceholder=0
```
当前按 domain 聚合:
```text
account total=33 completed=10 needsVerification=23
ai total=41 completed=41 needsVerification=0
content total=51 completed=0 needsVerification=51
events total=1 completed=1 needsVerification=0
knowledge total=59 completed=59 needsVerification=0
market total=32 completed=4 needsVerification=28
meta total=16 completed=16 needsVerification=0
```
Content 51 个 operation 当前全部为 `dedicated / needs_verification`
| operationId | Method | Path | 当前状态 |
|---|---|---|---|
| `adminListExportTasks` | `GET` | `/admin-api/muse/content/export-tasks` | `dedicated / needs_verification` |
| `adminListImportTasks` | `GET` | `/admin-api/muse/content/import-tasks` | `dedicated / needs_verification` |
| `adminListWorks` | `GET` | `/admin-api/muse/content/works` | `dedicated / needs_verification` |
| `adminGetWork` | `GET` | `/admin-api/muse/content/works/{workId}` | `dedicated / needs_verification` |
| `adminListChapters` | `GET` | `/admin-api/muse/content/works/{workId}/chapters` | `dedicated / needs_verification` |
| `adminRiskAction` | `POST` | `/admin-api/muse/content/works/{workId}/risk-actions` | `dedicated / needs_verification` |
| `confirmChapterParseResult` | `POST` | `/app-api/muse/chapter-parse-results/{resultId}/confirm` | `dedicated / needs_verification` |
| `rejectChapterParseResult` | `POST` | `/app-api/muse/chapter-parse-results/{resultId}/reject` | `dedicated / needs_verification` |
| `downloadExportPackage` | `GET` | `/app-api/muse/downloads/{credentialId}` | `dedicated / needs_verification` |
| `getExportTask` | `GET` | `/app-api/muse/export-tasks/{taskId}` | `dedicated / needs_verification` |
| `getImportTask` | `GET` | `/app-api/muse/import-tasks/{taskId}` | `dedicated / needs_verification` |
| `getParseJob` | `GET` | `/app-api/muse/parse-jobs/{jobId}` | `dedicated / needs_verification` |
| `listParseJobChapters` | `GET` | `/app-api/muse/parse-jobs/{jobId}/chapters` | `dedicated / needs_verification` |
| `batchConfirmChapters` | `POST` | `/app-api/muse/parse-jobs/{jobId}/chapters/batch-confirm` | `dedicated / needs_verification` |
| `retryParseJob` | `POST` | `/app-api/muse/parse-jobs/{jobId}/retry` | `dedicated / needs_verification` |
| `listWorks` | `GET` | `/app-api/muse/works` | `dedicated / needs_verification` |
| `createWork` | `POST` | `/app-api/muse/works` | `dedicated / needs_verification` |
| `deleteWork` | `DELETE` | `/app-api/muse/works/{workId}` | `dedicated / needs_verification` |
| `getWork` | `GET` | `/app-api/muse/works/{workId}` | `dedicated / needs_verification` |
| `updateWork` | `PUT` | `/app-api/muse/works/{workId}` | `dedicated / needs_verification` |
| `deleteBlock` | `DELETE` | `/app-api/muse/works/{workId}/blocks/{blockId}` | `dedicated / needs_verification` |
| `getBlock` | `GET` | `/app-api/muse/works/{workId}/blocks/{blockId}` | `dedicated / needs_verification` |
| `saveBlock` | `PUT` | `/app-api/muse/works/{workId}/blocks/{blockId}` | `dedicated / needs_verification` |
| `mergeBlocks` | `POST` | `/app-api/muse/works/{workId}/blocks/{blockId}/merge` | `dedicated / needs_verification` |
| `getBlockSourceAttribution` | `GET` | `/app-api/muse/works/{workId}/blocks/{blockId}/source-attribution` | `dedicated / needs_verification` |
| `splitBlock` | `POST` | `/app-api/muse/works/{workId}/blocks/{blockId}/split` | `dedicated / needs_verification` |
| `mergeBlockSuggestion` | `POST` | `/app-api/muse/works/{workId}/blocks/{blockId}/suggestion-merges` | `dedicated / needs_verification` |
| `listChapters` | `GET` | `/app-api/muse/works/{workId}/chapters` | `dedicated / needs_verification` |
| `createChapter` | `POST` | `/app-api/muse/works/{workId}/chapters` | `dedicated / needs_verification` |
| `deleteChapter` | `DELETE` | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `dedicated / needs_verification` |
| `getChapter` | `GET` | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `dedicated / needs_verification` |
| `updateChapter` | `PUT` | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `dedicated / needs_verification` |
| `listBlocks` | `GET` | `/app-api/muse/works/{workId}/chapters/{chapterId}/blocks` | `dedicated / needs_verification` |
| `createBlock` | `POST` | `/app-api/muse/works/{workId}/chapters/{chapterId}/blocks` | `dedicated / needs_verification` |
| `reorderChapters` | `PUT` | `/app-api/muse/works/{workId}/chapters/{chapterId}/reorder` | `dedicated / needs_verification` |
| `validateDynamicFields` | `POST` | `/app-api/muse/works/{workId}/dynamic-fields/validate` | `dedicated / needs_verification` |
| `exportWork` | `POST` | `/app-api/muse/works/{workId}/export` | `dedicated / needs_verification` |
| `createExportTask` | `POST` | `/app-api/muse/works/{workId}/export-tasks` | `dedicated / needs_verification` |
| `createImportTask` | `POST` | `/app-api/muse/works/{workId}/import-tasks` | `dedicated / needs_verification` |
| `listMetaProjections` | `GET` | `/app-api/muse/works/{workId}/meta-projections` | `dedicated / needs_verification` |
| `getMetaProjection` | `GET` | `/app-api/muse/works/{workId}/meta-projections/{projectionKey}` | `dedicated / needs_verification` |
| `createParseJob` | `POST` | `/app-api/muse/works/{workId}/parse-jobs` | `dedicated / needs_verification` |
| `getPlanning` | `GET` | `/app-api/muse/works/{workId}/planning` | `dedicated / needs_verification` |
| `listPlanningCandidates` | `GET` | `/app-api/muse/works/{workId}/planning/candidates` | `dedicated / needs_verification` |
| `createPlanningCandidate` | `POST` | `/app-api/muse/works/{workId}/planning/candidates` | `dedicated / needs_verification` |
| `getPlanningCandidate` | `GET` | `/app-api/muse/works/{workId}/planning/candidates/{candidateId}` | `dedicated / needs_verification` |
| `confirmPlanningCandidate` | `POST` | `/app-api/muse/works/{workId}/planning/candidates/{candidateId}/confirm` | `dedicated / needs_verification` |
| `discardPlanningCandidate` | `POST` | `/app-api/muse/works/{workId}/planning/candidates/{candidateId}/discard` | `dedicated / needs_verification` |
| `createStyleCheck` | `POST` | `/app-api/muse/works/{workId}/planning/style-checks` | `dedicated / needs_verification` |
| `getStyleCheckResult` | `GET` | `/app-api/muse/works/{workId}/planning/style-checks/{jobId}` | `dedicated / needs_verification` |
| `savePlanningItem` | `PUT` | `/app-api/muse/works/{workId}/planning/{sectionKey}` | `dedicated / needs_verification` |
## 已验证实现证据
### 子代理只读盘点结论
本轮并行只读复核得到两类结论:
- 核心 Content explorer 支持先做 owned work / chapter / block / source 小批量,并从实现证据角度把 `createWork``updateWork``deleteWork``createChapter``updateChapter``deleteChapter``createBlock` 等也列为强候选。
- 外部依赖 explorer 明确导入、导出、解析确认、Meta projection、AI planning candidate / style check / suggestion merge 都不应进入第一批admin read、正式 planning section 可作为后续早批候选,但应独立切片。
本审阅版没有采纳 15-operation 首批方案。原因不是这些实现一定不可用,而是当前只读盘点已发现若干创建/更新/删除写命令存在 OpenAPI / coverage `requiresCommandId` 口径与服务端 VO 不一致的风险。第一批若纳入这些 operation就会把合同修正夹带进 completed approval。更稳的路线是先审批 8 个不需要修改 OpenAPI 的核心读 + `saveBlock` + source attribution 闭环,把写命令合同修正放到后续单独审阅。
### Content owner 与入口
Content owner 位于 `muse-module-content/muse-module-content-server`
现有 dedicated Controller 覆盖 Content 51 个 operation
- `AppContentController`
- `AppContentStructureController`
- `AppContentSourceController`
- `AppContentExportController`
- `AppContentImportParseController`
- `AppContentPlanningController`
- `AppContentMetaProjectionController`
- `AdminContentController`
`P1rContentRealApiGateTest` 当前明确要求 Content 51 个 operation 全部保持 `dedicated / needs_verification`。这证明 P1R-1 已退出 catch-all / generic persistence / SSE placeholder不证明任何 Content operation 已达到 completed 口径。
### Content 本域核心服务
用户端核心作品/章节/Block 链路由 `ContentAppServiceImpl` 承载:
- `listWorks` / `getWork`:读取 owner 下作品。
- `listChapters` / `getChapter`:先校验 work owner再读取章节和 Block 摘要。
- `listBlocks` / `getBlock`:先校验 work / chapter / block 归属,再读取 Block。
- `saveBlock`:校验 commandId、owner、expectedRevision更新 Block`muse_content_block_source_attribution(source_status=active)`,创建 Content event publish outbox并记录审计。
来源归因读链路由 `ContentSourceServiceImpl.getBlockSourceAttribution` 承载,基于当前 Block revision 查询 `muse_content_block_source_attribution` 并返回来源摘要。
### P1R-7f saveBlock 证据
P1R-7f 已形成以下 `needs_verification` evidence
```text
ContentAppServiceImpl.saveBlock
-> muse_content_block_source_attribution(source_status=active)
-> muse_content_event_publish_outbox
-> ContentEventPublishWorker
-> EventsPublishApi
-> muse_unified_event
-> /app-api/muse/events 对 owner 可见
```
该证据覆盖 saveBlock 首次成功路径、command replay 不重复创建 outbox、revision conflict 不更新 Block / 不写 source attribution / 不创建 outbox、payload allowlist、worker retry/dead_letter、V21 Flyway `_test` 和 dependency tree gate。
但它仍只是 source owner propagation evidence不等同于 `saveBlock` API operation completed。Content completed approval 仍必须补 fresh HTTP/API + 真实 PostgreSQL `_test` + scanner/report/gate 同步证据。
## 推荐第一候选
推荐第一批候选限定为 8 个 operation
| operationId | 推荐原因 | 必补 completed-grade 证据 |
|---|---|---|
| `listWorks` | 本域读侧,依赖 owner 过滤和分页,不需要外部 owner | MockMvc HTTP + 真实 DB seed验证 owner 可见/不可见、分页和响应 DTO |
| `getWork` | 本域读侧owner guard 明确 | HTTP + DB 验证存在、跨 owner forbidden/not found、DTO 字段 |
| `listChapters` | 本域章节读侧,先校验 work owner | HTTP + DB 验证章节排序、blockCount/wordCount、跨 owner 拒绝 |
| `getChapter` | 本域章节详情,返回 blocks | HTTP + DB 验证章节详情、Block 列表、章节不属 work 拒绝 |
| `listBlocks` | 本域 Block 列表,先校验 chapter 从属 | HTTP + DB 验证列表、排序、跨 work/chapter 拒绝 |
| `getBlock` | 本域 Block 详情owner/work 归属明确 | HTTP + DB 验证 DTO、跨 owner 拒绝 |
| `saveBlock` | 本域核心写路径,已有 P1R-7f source/event evidence | HTTP + DB 验证 commandId、revision CAS、source attribution、outbox、replay、conflict no-write |
| `getBlockSourceAttribution` | 与 saveBlock 形成读后验证闭环 | HTTP + DB 验证当前 revision 来源、空来源、跨 owner 拒绝 |
推荐理由:
- 范围只覆盖 Content 本域核心 owned work / chapter / block不进入 AI / Knowledge / Meta / FileService 等外部 owner。
- 读链路可以用测试种子建立作品、章节、Block避免把 `createWork` / `createChapter` / `createBlock` 的合同缺口带入第一批。
- `saveBlock` 是 Content 当前最强证据链路,已有 source attribution 和 Events outbox 真实链路基础,但仍需补 API operation completed 级别的 fresh HTTP/DB gate。
- `getBlockSourceAttribution``saveBlock` 组成可验证闭环:写入当前 revision 来源事实,再读取同一 revision 的来源摘要。
替代方案是扩大到 15 个用户端 work / chapter / block CRUD operation。该方案只有在用户明确批准“先修 Content 写命令 OpenAPI / coverage commandId 合同,再做 completed approval”时才应进入执行版默认不采用。
如果执行版按 8 个 operation 推进,获批后的目标值只能是:
```text
total=233
completed=139
needsVerification=94
content completed=8
content needsVerification=43
```
该目标值只是后续获批后的执行目标,不是本审阅版当前状态。
## 暂不推荐第一批的切片
### create/update/delete 与结构编辑写命令
以下 operation 暂不纳入第一批:
- `createWork`
- `updateWork`
- `deleteWork`
- `createChapter`
- `updateChapter`
- `deleteChapter`
- `createBlock`
- `reorderChapters`
- `splitBlock`
- `mergeBlocks`
- `deleteBlock`
主要原因:
- 只读盘点已发现部分写接口的 OpenAPI / coverage `requiresCommandId` 口径与服务端 VO 不一致。例如 `WorkCreateReqVO``WorkUpdateReqVO``ChapterCreateReqVO` 服务端要求 `commandId`,但当前 coverage 对 `createWork``updateWork``createChapter` 仍显示 `requiresCommandId=false`OpenAPI 对 `createWork` / `updateWork` / `createChapter` 的 request body 也没有把 commandId 和 expectedRevision 等写命令字段完整列入 required。
- `deleteWork` 当前 OpenAPI 路径缺写命令 request body而服务端 controller 使用 `RevisionCommandReqVO`
- 结构编辑写命令会影响 work/chapter/block revision、排序、sourceSnapshot、审计和幂等 replay适合在合同修正审阅后单独切片。
执行版不得为了把这些 operation completed 而顺手改 OpenAPI 或 scanner。若用户希望第二批处理写命令必须先做 Content 写命令合同一致性审阅版。
### AI suggestion 合并
`mergeBlockSuggestion` 暂不推荐第一批。
它依赖 `ContentAiSuggestionFacade` 查询 AI owner 投影,当前默认 `UnavailableContentAiSuggestionFacade` 明确返回 unavailable。服务端要求 suggestion status、revision、quality result、source status、authorization snapshot 和 license restriction 全部可用,不能在 AI owner 未闭合时伪造成功。
### 导入 / 解析 / Knowledge 草稿
以下 operation 暂不推荐第一批:
- `createImportTask`
- `getImportTask`
- `createParseJob`
- `getParseJob`
- `retryParseJob`
- `listParseJobChapters`
- `confirmChapterParseResult`
- `rejectChapterParseResult`
- `batchConfirmChapters`
- `adminListImportTasks`
主要缺口:
- `ContentFileFacade` 默认 unavailable导入文件扫描、存储引用和失败阶段归 FileService owner。
- `ContentParseJobFacade` 默认 unavailableParse Job、章节解析结果和重试事实归 AI owner。
- `ContentKnowledgeDraftFacade` 默认 unavailable确认解析结果后创建知识草稿归 Knowledge owner。
- 确认/批量确认链路需要 Knowledge draft 创建与 AI parse decision record 双 owner 成功,不能只凭 Content 编排服务 completed。
### 导出 / 下载
以下 operation 暂不推荐第一批:
- `exportWork`
- `createExportTask`
- `getExportTask`
- `downloadExportPackage`
- `adminListExportTasks`
主要缺口:
- `ContentFileFacade.createExportPackage``downloadExportPackage` 默认 unavailable。
- 导出包、下载凭证、packageRef、字节内容和过期校验归 FileService ownerContent 当前只能 fail-closed。
- `getExportTask` / admin export read 可在后续通过 DB seed 单独审批,但不应与创建/下载闭环混为一批。
### Meta projection 与动态字段
以下 operation 暂不推荐第一批:
- `listMetaProjections`
- `getMetaProjection`
- `validateDynamicFields`
主要缺口:
- `ContentMetaFacade` 默认 unavailable。
- MetaSchema 解释、字段校验和写入路由建议归 Meta ownerContent 服务明确不复制 Meta 规则、不写 Content 事实表。
### Planning candidate / style check
以下 operation 暂不推荐第一批:
- `createPlanningCandidate`
- `listPlanningCandidates`
- `getPlanningCandidate`
- `confirmPlanningCandidate`
- `discardPlanningCandidate`
- `createStyleCheck`
- `getStyleCheckResult`
主要缺口:
- Candidate 生成、候选投影、丢弃决策和 style check 运行事实归 AI owner。
- `ContentPlanningCandidateFacade``ContentStyleCheckFacade` 默认 unavailable。
- `confirmPlanningCandidate` 会把 AI candidate 写入正式 planning必须验证 candidate source、quality result、authorization snapshot 和 revision不适合混入核心 Block 第一批。
### Admin governance
以下 operation 可作为后续候选,但不推荐第一批:
- `adminListWorks`
- `adminGetWork`
- `adminListChapters`
- `adminRiskAction`
理由:
- admin read 是本域读侧,但包含 RBAC、治理摘要、risk flags、word count 汇总和 admin DTO不应与用户端核心编辑首批混在一起。
- `adminRiskAction` 是治理写路径,涉及 `expectedVersion`、行锁、targetScope、targetIds 归属、幂等 envelope 和治理动作状态,适合单独做 Admin governance completed approval。
### Planning 正式 section
以下 operation 可作为后续本域候选:
- `getPlanning`
- `savePlanningItem`
理由:
- 它们主要依赖 Content 本域 `muse_content_planning_section`,不是外部 AI candidate runtime。
- 但 planning section 有 schemaVersion、projectionVersion、sourceSnapshot、upsert CAS 与 command replay 语义,建议作为第二批本域 planning 切片,不混入 Block 第一批。
## 后续执行版硬条件
如用户批准进入执行版,执行版必须先通过 fresh spec/scope review 与 fresh quality/feasibility review且至少满足以下硬条件。
### 允许的最小 diff
默认允许:
- 新增/修改 Content completed approval 执行版文档。
- 更新 `docs/agent-specs/.agent` 的阶段状态。
- 新增 Content 第一批 completed approval 的真实 `_test` gate。
- 在 scanner 中只追加用户批准的 `content:<operationId>` operation-level allowlist不加入 `content` domain-level allowlist。
- 重新生成 coverage report。
- 同步 P1R gate tests 中的 summary / Content 状态断言,以及 Account / Market / Meta 等非目标域防回退断言。
- 增强既有 Content events publish gate 仅限测试证据安全边界,例如 `P1rContentEventsPublishFlywayMigrationIT``p1r.flyway.url/user` 保存恢复与组合顺序验证;不得改变业务断言口径来掩盖回退。
- 新增 `docs/memorys/YYYY-MM-DD-P1RContent状态推进.md`
默认禁止:
- 修改 Content OpenAPI。
- 修改 Content 业务实现来掩盖 coverage 缺口。
- 修改 SQL migration。
- 把 `content` 加入 domain-level completed allowlist。
- 把 create/update/delete/import/export/parse/planning/meta/admin operation 顺手推进 completed。
如果执行中发现必须修改 OpenAPI、业务实现或 SQL必须停下说明原因并取得用户明确批准。
### 必须同步的 gate 文件
执行版必须枚举所有读取全局 summary、Content 状态,或作为 `saveBlock` source/event 前置证据的 P1R gate。当前至少包括
- `P1rApiCoverageReportTest`
- `P1rContentRealApiGateTest`
- `P1rEventsRealApiGateTest`
- `P1rAiRealApiGateTest`
- `P1rKnowledgeRealApiGateTest`
- `P1rMarketRealApiGateTest`
- `P1rAccountRealApiGateTest`
- `P1rMetaRealApiGateTest`
- `P1rContentEventsPublishMigrationSqlTest`
- `P1rContentEventsPublishDependencyTest`
- `P1rContentEventsPublishEndToEndTest`
- `P1rContentEventsPublishFlywayMigrationIT`
目标值必须公式化为:
```text
completed = 131 + N
needsVerification = 102 - N
Content completed = N
Content needsVerification = 51 - N
```
其中 `N` 只能来自用户明确批准的 Content operation。按本审阅版推荐第一批`N=8`
### 必须新增或增强的 runtime evidence
执行版必须包含类似 `P1rContentCoreCompletedApprovalIT` 的真实运行证据:
- 使用 WebApplicationContext / MockMvc 走 HTTP controller 入口。
- 使用真实 PostgreSQL `_test` 数据库,执行 Flyway V1-V21。
- 明确断言 Flyway current=V21、成功 SQL migration 数量为 21、V21 Content event outbox 表/索引/约束/trigger 存在,并验证 invalid outbox insert 被拒绝。
- V9 planning schema 不属于本第一批 8 个 operation 的业务验收范围;执行版不得为了本批去扩 V9 planning 业务 schema 断言。
- 使用真实 mapper / service 写读 `muse_content_work``muse_content_chapter``muse_content_block``muse_content_block_source_attribution``muse_content_event_publish_outbox`
- 覆盖 tenant 与 owner 双隔离:至少 seed 两个 tenant验证同 owner 不同 tenant 不可见、跨 tenant `saveBlock` 不写 block/source/outbox并验证本批写入行的 `tenant_id` 正确。
- 覆盖 `saveBlock` 首次成功、command replay、revision conflict、source attribution、outbox no-duplicate / conflict no-write。
- 覆盖 `getBlockSourceAttribution` 当前 revision 读模型。
- 为 8 个 operation 建立 shadow-path matrix不能只测 happy path
- `listWorks`:有数据分页、空作品页、跨 tenant 同 owner 不可见。
- `getWork`存在、ID 不存在、跨 owner / 跨 tenant 拒绝。
- `listChapters`有章节、空章节列表、work 不存在或跨 owner / 跨 tenant 拒绝。
- `getChapter`:存在并返回 blocks、chapter 不存在、chapter 不属于 path work、跨 owner / 跨 tenant 拒绝。
- `listBlocks`:有 Block、空 Block 列表、chapter 不属于 path work、跨 owner / 跨 tenant 拒绝。
- `getBlock`存在、block 不存在、block 不属于 path work、跨 owner / 跨 tenant 拒绝。
- `saveBlock`happy、command replay、revision conflict、缺 `commandId`、缺 `expectedRevision`、缺 `sourceSnapshot`、block 属于同 owner 的另一个 work、跨 owner / 跨 tenant 拒绝;所有失败路径均断言 block/source/outbox/command no-write 或事务回滚后的行数不变。
- `getBlockSourceAttribution`:当前 revision 有来源、当前 revision 空来源、block 不存在、block 属于同 owner 的另一个 work、跨 owner / 跨 tenant 拒绝,且 path mismatch 不泄露 attribution。
- 每个路径必须断言 HTTP 状态、CommonResult 体、关键 DTO 字段和 DB 行数。
- 把 `P1rContentEventsPublishMigrationSqlTest``P1rContentEventsPublishDependencyTest``P1rContentEventsPublishEndToEndTest``P1rContentEventsPublishFlywayMigrationIT` 纳入必跑和 XML 防空跑,作为 `saveBlock` completed approval 的 source/event 前置证据。
- Content completed IT 与 Content events Flyway IT 如果读取并脱敏 `p1r.flyway.url/user`,必须保存原始 system property 并在 `@AfterAll` 恢复;执行版必须包含普通顺序与 `-Dsurefire.runOrder=reversealphabetical` 或等价反序组合验证,防止同一 Surefire JVM 内污染后续 Flyway IT。
- 读取并校验 fresh Surefire XML要求 tests 数量大于 0failures/errors/skipped 均为 0且 XML mtime 在本轮运行窗口内。
- raw Surefire XML 只作为本地验证产物;若需要外发测试报告,必须先清洗 DB host/user且密码只能来自环境变量不能通过 JVM system property 或 JDBC URL query 传入。
### 必须保留的非目标断言
执行版和实现阶段必须继续证明:
- Account remaining 23 个 operation 仍保持 `dedicated / needs_verification`
- Market remaining 28 个 operation 仍保持 `dedicated / needs_verification`
- Content 未获批准的 43 个 operation 仍保持 `dedicated / needs_verification`
- AI / Knowledge / Events / Meta 已有 completed 口径不回退。
- OpenAPI protected diff 为空,除非用户单独批准合同修正。
## 待确认项
1. 是否接受 Content 第一批只审批上述 8 个 operation不做 Content 51/51。
2. 是否接受第一批不修改 OpenAPI创建/更新/删除写命令的 commandId 合同缺口放到后续单独审阅。
3. 是否接受后续执行版目标值按 `N=8` 计算为 `completed=139``needsVerification=94`
4. 是否允许执行版把 `P1rApiCoverageReportTest``P1rContentRealApiGateTest``P1rEventsRealApiGateTest``P1rAiRealApiGateTest``P1rKnowledgeRealApiGateTest``P1rMarketRealApiGateTest``P1rAccountRealApiGateTest``P1rMetaRealApiGateTest` 的 summary / Content / 非目标域状态防回退断言,以及 `P1rContentEventsPublish*` gate 的必跑与测试证据安全边界修正,列为后续获批 implementation 的 allowed diff。

View File

@ -0,0 +1,857 @@
# P1R Content Completed Approval 执行版
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` to implement this plan task-by-task after the explicit approval gate. Steps use checkbox syntax for tracking.
日期2026-06-12
## 结论
本执行版推荐第一轮只做 Content operation-level completed approval不做 Content domain-level 51/51 completed。
第一轮精确审批 8 个用户端核心作品 / 章节 / Block / 来源归因 operation
```text
content:listWorks
content:getWork
content:listChapters
content:getChapter
content:listBlocks
content:getBlock
content:saveBlock
content:getBlockSourceAttribution
```
审批后目标 coverage summary 为:
```text
total=233
completed=139
needsVerification=94
incomplete=0
genericPersistence=0
ssePlaceholder=0
```
Content 域目标状态为:
```text
content total=51
completed=8
needsVerification=43
```
本执行版只定义实现前置批准项、允许变更范围、TDD 顺序、验证命令、XML 防空跑和回滚策略。当前不得直接实现,不得修改 OpenAPI、scanner、coverage report、业务实现或 gate test。
```mermaid
flowchart TB
Current["当前 Content<br/>51 dedicated / needs_verification"] --> Exec["执行版<br/>锁定第一批 8 个 operation"]
Exec --> Review["fresh execution spec/scope review<br/>fresh execution quality/feasibility review"]
Review --> Approval{"用户明确批准<br/>8 ops / operation-level / allowed diff / legacy gates"}
Approval -->|否| Stay["保持当前 coverage<br/>233/131/102"]
Approval -->|是| Red["TDD RED<br/>先改 Content/Coverage/legacy gates<br/>旧 report 必须失败"]
Red --> Green["TDD GREEN<br/>scanner operation allowlist + regenerate report"]
Green --> Runtime["Content HTTP+DB _test<br/>V21 Flyway + source/event gates"]
Runtime --> Verify["focused + P1R mixed + XML 防空跑<br/>allowed/protected diff"]
Verify --> FreshReview["fresh implementation review"]
```
## 当前事实
正确 worktree
```text
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
```
当前 HEAD
```text
0032774 test(p1r): 收口 Market 第一批 completed approval 门禁
```
当前 coverage summary
```text
233 131 102 0 0 0
```
当前 Content 事实:
- Content 51 个 operation 全部为 `dedicated / needs_verification`
- `APPROVED_COMPLETED_DOMAINS` 当前只包含 `ai``knowledge`
- `APPROVED_COMPLETED_OPERATIONS` 当前包含 `events:streamEvents`、Meta 16 个 operation、Account 第一批 10 个 operation、Market 第一批 4 个 operation。
- `P1rContentRealApiGateTest` 当前明确禁止 Content operation 标记为 `completed`
- `P1rApiCoverageReportTest` 当前断言 summary `completed=131`,并断言 Content 51 个 operation 继续 `needs_verification`
- `P1rEventsRealApiGateTest``P1rAiRealApiGateTest``P1rKnowledgeRealApiGateTest``P1rMarketRealApiGateTest``P1rAccountRealApiGateTest``P1rMetaRealApiGateTest` 读取全局 summary 或非目标域状态;实现前必须把这些 mixed gate 同步列入单独批准项。
- P1R-7f 已证明 `saveBlock -> source attribution active -> Content outbox -> Events -> SSE owner visible``needs_verification` evidence但该 evidence 不等同于 `saveBlock` API operation completed。
## 执行边界
### 必须先获用户明确批准
实现前必须同时获得以下 4 项批准:
1. 批准第一轮 Content completed operation 精确清单为本执行版列出的 8 个 operation。
2. 批准只走 operation-level approval不把 `content` 加入 domain-level completed allowlist。
3. 批准按本执行版修改 scanner、coverage report、Content/Coverage gate、必要 legacy mixed gate、Content HTTP+DB `_test` IT、ContentEventsPublish gate 安全边界、memory 和 `.agent`
4. 批准同步 6 个 mixed gate 文件的 summary / Content / 非目标域状态防回退断言:
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rEventsRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rKnowledgeRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMetaRealApiGateTest.java`
未获 4 项批准前,不得实施本执行版。
### 本轮允许变更
获批后只允许修改以下路径:
- `muse-cloud/scripts/p1r-audit-api-coverage.py`
- `docs/superpowers/reports/p1r-api-coverage.json`
- `docs/superpowers/reports/p1r-api-coverage.md`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rEventsRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rKnowledgeRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMetaRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentCoreCompletedApprovalIT.java`,新增 Content 8 个 operation 的 MockMvc HTTP + 真实 PostgreSQL `_test` 证据。
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishFlywayMigrationIT.java`,仅限保存/恢复 `p1r.flyway.url/user` system property 与组合顺序验证所需的安全边界增强;不得弱化 V21 schema 断言。
- `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentAppServiceTest.java`
- `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentSourceServiceTest.java`
- `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishOutboxServiceTest.java`
- `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishWorkerTest.java`
- `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/controller/app/AppContentControllerTest.java`
- `muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/controller/app/AppContentSourceControllerTest.java`
- `docs/agent-specs/.agent`
- `docs/agent-specs/2026-06-11-P1RContentCompletedApproval审阅版.md`
- `docs/agent-specs/2026-06-12-P1RContentCompletedApproval执行版.md`
- `docs/memorys/2026-06-12-P1RContent状态推进.md`
如 implementation 发现必须修改 Content 业务实现、OpenAPI、SQL migration、AI/Knowledge/Meta/FileService owner 实现、非批准 gate 或其它文件,必须停下说明原因并重新取得用户批准。
### 本轮禁止变更
- 不修改 7 个 OpenAPI。
- 不修改 Content 业务实现来掩盖 coverage 缺口。
- 不新增或修改 SQL migration。
- 不新增或修改 Content domain-level completed allowlist。
- 不推进 create/update/delete/structure edit operation。
- 不推进 import/export/parse/FileService operation。
- 不推进 Meta projection / dynamic fields operation。
- 不推进 AI planning candidate / style check / suggestion merge operation。
- 不推进 admin governance / admin read operation。
- 不推进 planning section operation。
- 不推进 Account remaining 23、Market remaining 28 或总 P1R completed。
## 第一轮审批清单
| operation key | Method | Path | 目标状态 | 完成证据边界 |
|---|---|---|---|---|
| `content:listWorks` | `GET` | `/app-api/muse/works` | `dedicated / completed` | App 当前 owner真实 DB 分页,有数据页、空页、同 owner 跨 tenant 不可见 |
| `content:getWork` | `GET` | `/app-api/muse/works/{workId}` | `dedicated / completed` | App 当前 owner存在、missing、跨 owner、跨 tenantDTO 字段完整 |
| `content:listChapters` | `GET` | `/app-api/muse/works/{workId}/chapters` | `dedicated / completed` | work owner guard章节排序有章节、空章节、work missing、跨 owner/tenant |
| `content:getChapter` | `GET` | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `dedicated / completed` | chapter 属于 path work详情含 blocksmissing、wrong work、跨 owner/tenant |
| `content:listBlocks` | `GET` | `/app-api/muse/works/{workId}/chapters/{chapterId}/blocks` | `dedicated / completed` | chapter 属于 path workBlock 排序有数据、空列表、wrong work/chapter、跨 owner/tenant |
| `content:getBlock` | `GET` | `/app-api/muse/works/{workId}/blocks/{blockId}` | `dedicated / completed` | block 属于 path work存在、missing、wrong work、跨 owner/tenant |
| `content:saveBlock` | `PUT` | `/app-api/muse/works/{workId}/blocks/{blockId}` | `dedicated / completed` | commandId、expectedRevision、sourceSnapshot、revision CAS、source attribution、Content outbox、replay、conflict/no-write、wrong work、跨 owner/tenant |
| `content:getBlockSourceAttribution` | `GET` | `/app-api/muse/works/{workId}/blocks/{blockId}/source-attribution` | `dedicated / completed` | 当前 Block revision 来源读模型有来源、空来源、missing、wrong work、跨 owner/tenant不泄露 attribution |
说明:本轮使用 MockMvc HTTP 入口 + real mapper/service + PostgreSQL `_test` 作为执行版要求的 HTTP 入口证据,不声明 socket 级 live servlet container 证据。
## 保持 needs_verification 的 Content operation
以下 43 个 operation 必须保持 `dedicated / needs_verification`
```text
content:adminListExportTasks
content:adminListImportTasks
content:adminListWorks
content:adminGetWork
content:adminListChapters
content:adminRiskAction
content:confirmChapterParseResult
content:rejectChapterParseResult
content:downloadExportPackage
content:getExportTask
content:getImportTask
content:getParseJob
content:listParseJobChapters
content:batchConfirmChapters
content:retryParseJob
content:createWork
content:deleteWork
content:updateWork
content:deleteBlock
content:mergeBlocks
content:splitBlock
content:mergeBlockSuggestion
content:createChapter
content:deleteChapter
content:updateChapter
content:createBlock
content:reorderChapters
content:validateDynamicFields
content:exportWork
content:createExportTask
content:createImportTask
content:listMetaProjections
content:getMetaProjection
content:createParseJob
content:getPlanning
content:listPlanningCandidates
content:createPlanningCandidate
content:getPlanningCandidate
content:confirmPlanningCandidate
content:discardPlanningCandidate
content:createStyleCheck
content:getStyleCheckResult
content:savePlanningItem
```
保留原因:
- 写命令存在 OpenAPI / coverage `requiresCommandId` 与服务端 VO 口径不一致风险,不能夹带合同修正。
- 导入、导出、下载、解析确认依赖 FileService、AI、Knowledge owner 闭环。
- Meta projection、dynamic fields 依赖 Meta owner 解释与校验。
- AI planning candidate、style check、suggestion merge 依赖 AI owner runtime / projection / quality facts。
- admin read / governance 需要单独覆盖 RBAC、治理摘要、risk flags 和 admin DTO。
- formal planning section 虽属 Content 本域,但有 schemaVersion、projectionVersion、upsert CAS、command replay应作为后续独立切片。
## 证据矩阵
| 证据类型 | 必跑或必查内容 | 目的 |
|---|---|---|
| Content focused tests | `ContentAppServiceTest``ContentSourceServiceTest``ContentEventPublishOutboxServiceTest``ContentEventPublishWorkerTest``AppContentControllerTest``AppContentSourceControllerTest` | 支撑本域读写、source attribution、outbox、controller contract 与错误码 |
| Content HTTP+DB `_test` | 新增 `P1rContentCoreCompletedApprovalIT` | 证明 8 个 operation 经 MockMvc HTTP 入口访问真实 PostgreSQL `_test` 数据,覆盖 shadow-path matrix |
| Content source/event gates | `P1rContentEventsPublishMigrationSqlTest``P1rContentEventsPublishDependencyTest``P1rContentEventsPublishEndToEndTest``P1rContentEventsPublishFlywayMigrationIT` | 证明 `saveBlock` 的 source attribution / Content outbox / Events 前置证据仍成立 |
| P1R Content gates | `P1rContentRealApiGateTest``P1rApiCoverageReportTest` | 证明 only 8 Content operation completed43 个继续 needs_verification |
| P1R mixed gates | Coverage、Content、Events、AI、Knowledge、Market、Account、Meta gates | 防止非目标 domain 被连带推进,并同步 legacy summary / Content 状态断言 |
| Flyway `_test` | `P1rContentEventsPublishFlywayMigrationIT` fresh rerun | 证明 V1-V21 clean migrate、V21 outbox 表/索引/约束/trigger、invalid insert 拒绝 |
| Coverage scanner | `python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check` | 证明 scanner/report 与 operation-level allowlist 口径一致 |
| XML 防空跑 | 逐个读取 Surefire XML检查 tests、mtime、failures、errors、skipped | 防止 Maven 未跑目标类或旧 XML 假绿 |
| Protected diff | 7 个 OpenAPI、Content 业务实现、SQL migration 必须无 diff | 证明没有通过改合同、实现或迁移掩盖缺口 |
| Allowed diff | unstaged、staged、untracked 均必须落在允许清单 | 证明没有顺手扩大 scope |
## TDD 执行步骤
### Task 0实现前现场确认
- [ ] 确认 worktree
```bash
pwd
git -c core.quotePath=false status --short --branch
git log --oneline -5
jq -r '.summary | [.totalOperations,.completedOperations,.needsVerificationOperations,.incompleteOperations,.genericPersistenceOperations,.ssePlaceholderOperations] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
```
预期:
```text
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
233 131 102 0 0 0
```
- [ ] 确认 Content 51 个 operation 仍全部是 `dedicated / needs_verification`
```bash
jq -r '.operations[] | select(.domain=="content") | [.operationId,.implementationStatus,.completionStatus,.requiresCommandId] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
```
- [ ] 确认实施前只有文档 diff没有 OpenAPI、scanner、coverage report、业务实现、SQL migration、P1R gate test diff
```bash
git -c core.quotePath=false status --short --branch
git -c core.quotePath=false diff -- docs/api-contracts
git -c core.quotePath=false diff -- muse-cloud/scripts/p1r-audit-api-coverage.py docs/superpowers/reports
git -c core.quotePath=false diff -- muse-cloud/muse-module-content/muse-module-content-server/src/main/java muse-cloud/sql/muse
git -c core.quotePath=false diff -- muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api
```
### XML 防空跑通用规则
除 TDD RED 失败验证外,所有 Maven test 验证命令都必须执行以下规则,不能只读取历史 Surefire XML
- 命令前记录 `export RUN_START_EPOCH=$(date +%s)`
- 命令前删除本任务要求检查的 `target/surefire-reports/TEST-*.xml`
- 命令后逐个确认 XML 存在、`mtime >= RUN_START_EPOCH``tests` 不低于本执行版最低值、`failures=0``errors=0``skipped=0`
- 任一 XML 缺失、mtime 早于运行开始时间、tests 低于最低值或存在失败/跳过,都必须视为验证失败。
TDD RED 失败验证必须同样删除目标 XML 并记录 `RUN_START_EPOCH`,但 RED 的预期是 Maven 失败;此时只校验失败发生在本次运行窗口内,并人工确认失败点来自旧 report mismatch不要求 `failures=0`
推荐校验脚本:
```bash
python3 - <<'PY'
import os
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
run_start = int(os.environ["RUN_START_EPOCH"])
checks = {
"muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rApiCoverageReportTest.xml": 6,
}
failed = False
for path_text, min_tests in checks.items():
path = Path(path_text)
if not path.exists():
print(f"MISSING_XML {path}")
failed = True
continue
if int(path.stat().st_mtime) < run_start:
print(f"STALE_XML {path}")
failed = True
continue
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"))
print(path, tests, failures, errors, skipped)
if tests < min_tests or failures or errors or skipped:
failed = True
if failed:
sys.exit(1)
PY
```
执行具体任务时必须把 `checks` 替换成该任务表格内列出的 XML 路径和最低 tests。
### Task 1TDD RED先改 gate 期望
获批后先修改:
- `P1rApiCoverageReportTest.java`
- `P1rContentRealApiGateTest.java`
- 必要的 6 个 mixed gate
- `P1rEventsRealApiGateTest.java`
- `P1rAiRealApiGateTest.java`
- `P1rKnowledgeRealApiGateTest.java`
- `P1rMarketRealApiGateTest.java`
- `P1rAccountRealApiGateTest.java`
- `P1rMetaRealApiGateTest.java`
RED 期望:
- summary 期望 `completed=139``needsVerification=94`,但旧 report 仍是 `131/102`,必须失败。
- Content 8 个 approved operation 期望 `completed`,但旧 report 仍是 `needs_verification`,必须失败。
- Content 43 个未批准 operation 继续期望 `needs_verification`,其中必须包含 `createWork``updateWork``createChapter``mergeBlockSuggestion``exportWork``getPlanning``adminRiskAction`
- Account 仍为 `10 completed / 23 needs_verification`Market 仍为 `4 completed / 28 needs_verification`Meta 仍为 `16 completed / 0 needs_verification`AI/Knowledge/Events 不回退。
运行:
```bash
cd muse-cloud
export RUN_START_EPOCH=$(date +%s)
rm -f \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rApiCoverageReportTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rEventsRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rKnowledgeRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMetaRealApiGateTest.xml
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
mvn -o test -pl muse-server -am \
-Dtest=P1rApiCoverageReportTest,P1rContentRealApiGateTest,P1rEventsRealApiGateTest,P1rAiRealApiGateTest,P1rKnowledgeRealApiGateTest,P1rMarketRealApiGateTest,P1rAccountRealApiGateTest,P1rMetaRealApiGateTest \
-Dsurefire.failIfNoSpecifiedTests=false
```
预期:至少一个失败点来自旧 report 的 `131/102` 或 Content 8 个 operation 尚未 completed。若直接通过必须停下排查假绿。
### Task 2TDD GREEN最小 scanner/report 状态推进
只修改 `muse-cloud/scripts/p1r-audit-api-coverage.py`
- 不修改 `APPROVED_COMPLETED_DOMAINS`
- 只向 `APPROVED_COMPLETED_OPERATIONS` 追加 8 个 `content:*` operation key。
- 保持 completed 必须仍是 `dedicated` 的校验。
生成 report
```bash
python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check
```
预期 summary
```text
total=233
completed=139
needsVerification=94
incomplete=0
genericPersistence=0
ssePlaceholder=0
```
预期 Content
```text
completed=8
needsVerification=43
```
预期 scanner 仍不包含 `content` domain-level allowlist
```bash
python3 - <<'PY'
from pathlib import Path
source = Path("muse-cloud/scripts/p1r-audit-api-coverage.py").read_text()
assert 'APPROVED_COMPLETED_DOMAINS = {"ai", "knowledge"}' in source
for key in [
"content:listWorks", "content:getWork", "content:listChapters", "content:getChapter",
"content:listBlocks", "content:getBlock", "content:saveBlock", "content:getBlockSourceAttribution",
]:
assert key in source
print("content_operation_level_allowlist=PASS")
PY
```
### Task 3Content focused verification
运行 Content 本域 focused tests
```bash
cd muse-cloud
export RUN_START_EPOCH=$(date +%s)
rm -f \
muse-module-content/muse-module-content-server/target/surefire-reports/TEST-cn.iocoder.muse.module.content.application.ContentAppServiceTest.xml \
muse-module-content/muse-module-content-server/target/surefire-reports/TEST-cn.iocoder.muse.module.content.application.ContentSourceServiceTest.xml \
muse-module-content/muse-module-content-server/target/surefire-reports/TEST-cn.iocoder.muse.module.content.application.ContentEventPublishOutboxServiceTest.xml \
muse-module-content/muse-module-content-server/target/surefire-reports/TEST-cn.iocoder.muse.module.content.application.ContentEventPublishWorkerTest.xml \
muse-module-content/muse-module-content-server/target/surefire-reports/TEST-cn.iocoder.muse.module.content.controller.app.AppContentControllerTest.xml \
muse-module-content/muse-module-content-server/target/surefire-reports/TEST-cn.iocoder.muse.module.content.controller.app.AppContentSourceControllerTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishMigrationSqlTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishDependencyTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishEndToEndTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rApiCoverageReportTest.xml
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
mvn -o test -pl muse-module-content/muse-module-content-server,muse-server -am \
-Dtest=ContentAppServiceTest,ContentSourceServiceTest,ContentEventPublishOutboxServiceTest,ContentEventPublishWorkerTest,AppContentControllerTest,AppContentSourceControllerTest,P1rContentEventsPublishMigrationSqlTest,P1rContentEventsPublishDependencyTest,P1rContentEventsPublishEndToEndTest,P1rContentRealApiGateTest,P1rApiCoverageReportTest \
-Dsurefire.failIfNoSpecifiedTests=false
```
必须读取以下 XML 并检查 `mtime >= RUN_START_EPOCH``failures=0``errors=0``skipped=0`
| Test class | 最低 tests |
|---|---:|
| `ContentAppServiceTest` | 13 |
| `ContentSourceServiceTest` | 23 |
| `ContentEventPublishOutboxServiceTest` | 6 |
| `ContentEventPublishWorkerTest` | 10 |
| `AppContentControllerTest` | 5 |
| `AppContentSourceControllerTest` | 5 |
| `P1rContentEventsPublishMigrationSqlTest` | 3 |
| `P1rContentEventsPublishDependencyTest` | 2 |
| `P1rContentEventsPublishEndToEndTest` | 5 |
| `P1rContentRealApiGateTest` | 5 |
| `P1rApiCoverageReportTest` | 6 |
如实现修改测试数量XML 检查的最低 tests 可高于上表,但不能低于上表。
### Task 4Content HTTP + 真实 PostgreSQL `_test` completed gate
新增 `P1rContentCoreCompletedApprovalIT`,使用独立 `_test` 数据库和真实 mapper/service 数据,不能使用生产库或非 `_test` 库:
```bash
source ~/.config/muse-repo/infra.env
export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD"
export P1R_CONTENT_COMPLETED_APPROVAL_TEST_DB=muse_p1r_content_core_completed_approval_test
PGPASSWORD="$MUSE_POSTGRES_PASSWORD" psql \
-h "$MUSE_POSTGRES_HOST" \
-p "$MUSE_POSTGRES_PORT" \
-U "$MUSE_POSTGRES_USERNAME" \
-d postgres \
-tc "SELECT 1 FROM pg_database WHERE datname = '$P1R_CONTENT_COMPLETED_APPROVAL_TEST_DB'" | grep -q 1 || \
PGPASSWORD="$MUSE_POSTGRES_PASSWORD" psql \
-h "$MUSE_POSTGRES_HOST" \
-p "$MUSE_POSTGRES_PORT" \
-U "$MUSE_POSTGRES_USERNAME" \
-d postgres \
-c "CREATE DATABASE $P1R_CONTENT_COMPLETED_APPROVAL_TEST_DB"
cd muse-cloud
export RUN_START_EPOCH=$(date +%s)
rm -f muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentCoreCompletedApprovalIT.xml
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
mvn -o test -pl muse-server -am \
-Dtest=P1rContentCoreCompletedApprovalIT \
-Dflyway.postgresql.transactional.lock=false \
-Dp1r.flyway.locations=filesystem:sql/muse \
-Dp1r.flyway.url="jdbc:postgresql://$MUSE_POSTGRES_HOST:$MUSE_POSTGRES_PORT/$P1R_CONTENT_COMPLETED_APPROVAL_TEST_DB" \
-Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
-Djava.net.useSystemProxies=false \
-DsocksProxyHost= -DsocksProxyPort= \
-Dhttp.proxyHost= -Dhttp.proxyPort= \
-Dhttps.proxyHost= -Dhttps.proxyPort= \
-Dsurefire.failIfNoSpecifiedTests=false
```
该 IT 必须至少覆盖以下 12 个测试XML 最低 tests 为 12
| Test class | 最低 tests |
|---|---:|
| `P1rContentCoreCompletedApprovalIT` | 12 |
必须证明:
- `GET /app-api/muse/works` 覆盖有数据分页、空作品页、同 owner 不同 tenant 不可见。
- `GET /app-api/muse/works/{workId}` 覆盖存在、ID 不存在、跨 owner、跨 tenant。
- `GET /app-api/muse/works/{workId}/chapters` 覆盖有章节、空章节、work missing、跨 owner、跨 tenant。
- `GET /app-api/muse/works/{workId}/chapters/{chapterId}` 覆盖存在并返回 blocks、chapter missing、chapter 属于同 owner 另一个 work、跨 owner、跨 tenant。
- `GET /app-api/muse/works/{workId}/chapters/{chapterId}/blocks` 覆盖有 Block、空列表、chapter 属于同 owner 另一个 work、跨 owner、跨 tenant。
- `GET /app-api/muse/works/{workId}/blocks/{blockId}` 覆盖存在、block missing、block 属于同 owner 另一个 work、跨 owner、跨 tenant。
- `PUT /app-api/muse/works/{workId}/blocks/{blockId}` 覆盖 happy、command replay、revision conflict、缺 `commandId`、缺 `expectedRevision`、缺 `sourceSnapshot`、block 属于同 owner 另一个 work、跨 owner、跨 tenant。
- `GET /app-api/muse/works/{workId}/blocks/{blockId}/source-attribution` 覆盖当前 revision 有来源、当前 revision 空来源、block missing、block 属于同 owner 另一个 work、跨 owner、跨 tenant且 path mismatch 不泄露 attribution。
- 每个路径必须断言 HTTP 状态、CommonResult 体、关键 DTO 字段和 DB 行数。
- 所有失败路径必须断言 block/source/outbox/command no-write 或事务回滚后的行数不变。
- 成功 `saveBlock` 必须断言 `muse_content_block_source_attribution(source_status=active)``muse_content_event_publish_outbox``tenant_id`、owner、blockRevision、sourceStatus、notificationType 和 commandId/outbox 唯一事实。
- 该 IT 必须在测试启动时校验库名以 `_test` 结尾;密码只能从 `P1R_CONTENT_COMPLETED_PASSWORD``P1R_FLYWAY_PASSWORD``MUSE_POSTGRES_PASSWORD` 环境变量读取,不能通过 JVM system property 传入;日志和断言不得输出明文数据库密码。
- 该 IT 如果读取并脱敏 `p1r.flyway.url/user`,必须保存原始 system property 并在 `@AfterAll` 恢复。
### Task 5真实 PostgreSQL `_test` Flyway gate
fresh rerun 并增强 `P1rContentEventsPublishFlywayMigrationIT`。使用独立 `_test` 库,不能使用生产库或非 `_test` 库:
```bash
source ~/.config/muse-repo/infra.env
export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD"
export P1R_CONTENT_EVENTS_TEST_DB=muse_p1r_content_completed_approval_test
PGPASSWORD="$MUSE_POSTGRES_PASSWORD" psql \
-h "$MUSE_POSTGRES_HOST" \
-p "$MUSE_POSTGRES_PORT" \
-U "$MUSE_POSTGRES_USERNAME" \
-d postgres \
-tc "SELECT 1 FROM pg_database WHERE datname = '$P1R_CONTENT_EVENTS_TEST_DB'" | grep -q 1 || \
PGPASSWORD="$MUSE_POSTGRES_PASSWORD" psql \
-h "$MUSE_POSTGRES_HOST" \
-p "$MUSE_POSTGRES_PORT" \
-U "$MUSE_POSTGRES_USERNAME" \
-d postgres \
-c "CREATE DATABASE $P1R_CONTENT_EVENTS_TEST_DB"
cd muse-cloud
export RUN_START_EPOCH=$(date +%s)
rm -f muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishFlywayMigrationIT.xml
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
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/$P1R_CONTENT_EVENTS_TEST_DB" \
-Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
-Djava.net.useSystemProxies=false \
-DsocksProxyHost= -DsocksProxyPort= \
-Dhttp.proxyHost= -Dhttp.proxyPort= \
-Dhttps.proxyHost= -Dhttps.proxyPort= \
-Dsurefire.failIfNoSpecifiedTests=false
```
必须检查 XML
| Test class | 最低 tests |
|---|---:|
| `P1rContentEventsPublishFlywayMigrationIT` | 4 |
必须保持或补齐以下 assertion
- V1-V21 clean migrate。
- `target_schema_version=21`
- `successful_migration_count=21`
- V21 `muse_content_event_publish_outbox` 表、索引、唯一约束、CHECK 约束、trigger 存在。
- invalid outbox insert 被 PostgreSQL 拒绝,至少覆盖非法 `event_type`、非法 `notification_type``source_status=deleted`、非法 `publish_status`、非法 owner/block/resource/attempt。
- 保存原始 `p1r.flyway.url/user`,脱敏后在 `@AfterAll` 恢复,避免同一 Surefire JVM 中污染其它 Flyway IT。
### Task 6Content completed IT 与 Content events Flyway IT 组合顺序验证
为证明 system property 脱敏不会污染同 JVM 后续测试,必须用同一个 `_test` 库做普通顺序与反序组合验证。
```bash
source ~/.config/muse-repo/infra.env
export P1R_FLYWAY_PASSWORD="$MUSE_POSTGRES_PASSWORD"
export P1R_CONTENT_COMBINED_TEST_DB=muse_p1r_content_combined_completed_approval_test
PGPASSWORD="$MUSE_POSTGRES_PASSWORD" psql \
-h "$MUSE_POSTGRES_HOST" \
-p "$MUSE_POSTGRES_PORT" \
-U "$MUSE_POSTGRES_USERNAME" \
-d postgres \
-tc "SELECT 1 FROM pg_database WHERE datname = '$P1R_CONTENT_COMBINED_TEST_DB'" | grep -q 1 || \
PGPASSWORD="$MUSE_POSTGRES_PASSWORD" psql \
-h "$MUSE_POSTGRES_HOST" \
-p "$MUSE_POSTGRES_PORT" \
-U "$MUSE_POSTGRES_USERNAME" \
-d postgres \
-c "CREATE DATABASE $P1R_CONTENT_COMBINED_TEST_DB"
cd muse-cloud
export RUN_START_EPOCH=$(date +%s)
rm -f \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentCoreCompletedApprovalIT.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishFlywayMigrationIT.xml
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
mvn -o test -pl muse-server -am \
-Dtest=P1rContentCoreCompletedApprovalIT,P1rContentEventsPublishFlywayMigrationIT \
-Dflyway.postgresql.transactional.lock=false \
-Dp1r.flyway.locations=filesystem:sql/muse \
-Dp1r.flyway.url="jdbc:postgresql://$MUSE_POSTGRES_HOST:$MUSE_POSTGRES_PORT/$P1R_CONTENT_COMBINED_TEST_DB" \
-Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
-Djava.net.useSystemProxies=false \
-DsocksProxyHost= -DsocksProxyPort= \
-Dhttp.proxyHost= -Dhttp.proxyPort= \
-Dhttps.proxyHost= -Dhttps.proxyPort= \
-Dsurefire.failIfNoSpecifiedTests=false
export RUN_START_EPOCH=$(date +%s)
rm -f \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentCoreCompletedApprovalIT.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentEventsPublishFlywayMigrationIT.xml
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
mvn -o test -pl muse-server -am \
-Dtest=P1rContentCoreCompletedApprovalIT,P1rContentEventsPublishFlywayMigrationIT \
-Dsurefire.runOrder=reversealphabetical \
-Dflyway.postgresql.transactional.lock=false \
-Dp1r.flyway.locations=filesystem:sql/muse \
-Dp1r.flyway.url="jdbc:postgresql://$MUSE_POSTGRES_HOST:$MUSE_POSTGRES_PORT/$P1R_CONTENT_COMBINED_TEST_DB" \
-Dp1r.flyway.user="$MUSE_POSTGRES_USERNAME" \
-Djava.net.useSystemProxies=false \
-DsocksProxyHost= -DsocksProxyPort= \
-Dhttp.proxyHost= -Dhttp.proxyPort= \
-Dhttps.proxyHost= -Dhttps.proxyPort= \
-Dsurefire.failIfNoSpecifiedTests=false
```
两次组合验证均必须满足:
| Test class | 最低 tests |
|---|---:|
| `P1rContentCoreCompletedApprovalIT` | 12 |
| `P1rContentEventsPublishFlywayMigrationIT` | 4 |
### Task 7P1R mixed gate
运行 mixed gates
```bash
cd muse-cloud
export RUN_START_EPOCH=$(date +%s)
rm -f \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rApiCoverageReportTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rContentRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rEventsRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAiRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rKnowledgeRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMarketRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rAccountRealApiGateTest.xml \
muse-server/target/surefire-reports/TEST-cn.iocoder.muse.server.framework.api.P1rMetaRealApiGateTest.xml
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
mvn -o test -pl muse-server -am \
-Dtest=P1rApiCoverageReportTest,P1rContentRealApiGateTest,P1rEventsRealApiGateTest,P1rAiRealApiGateTest,P1rKnowledgeRealApiGateTest,P1rMarketRealApiGateTest,P1rAccountRealApiGateTest,P1rMetaRealApiGateTest \
-Dsurefire.failIfNoSpecifiedTests=false
```
必须读取以下 XML
| Test class | 最低 tests |
|---|---:|
| `P1rApiCoverageReportTest` | 6 |
| `P1rContentRealApiGateTest` | 5 |
| `P1rEventsRealApiGateTest` | 6 |
| `P1rAiRealApiGateTest` | 7 |
| `P1rKnowledgeRealApiGateTest` | 8 |
| `P1rMarketRealApiGateTest` | 6 |
| `P1rAccountRealApiGateTest` | 4 |
| `P1rMetaRealApiGateTest` | 4 |
每个 XML 均必须 `mtime >= RUN_START_EPOCH``failures=0``errors=0``skipped=0`
这些 gate 必须共同证明:
- summary 只从 `131/102` 推进到 `139/94`
- Content 只从 `0/51` 推进到 `8/43`
- Account 仍保持 `10 completed / 23 needs_verification`
- Market 仍保持 `4 completed / 28 needs_verification`
- Meta 仍保持 `16 completed / 0 needs_verification`
- AI 41、Knowledge 59、Events 1 仍为 completed不回退。
### Task 8报告与 diff gate
检查 summary
```bash
jq -r '.summary | [.totalOperations,.completedOperations,.needsVerificationOperations,.incompleteOperations,.genericPersistenceOperations,.ssePlaceholderOperations] | @tsv' docs/superpowers/reports/p1r-api-coverage.json
```
预期:
```text
233 139 94 0 0 0
```
检查 Content 8/43
```bash
python3 - <<'PY'
import json
from collections import Counter
from pathlib import Path
approved = {
"listWorks", "getWork", "listChapters", "getChapter",
"listBlocks", "getBlock", "saveBlock", "getBlockSourceAttribution",
}
data = json.loads(Path("docs/superpowers/reports/p1r-api-coverage.json").read_text())
content_ops = [op for op in data["operations"] if op["domain"] == "content"]
counter = Counter(op["completionStatus"] for op in content_ops)
print(counter)
assert counter == Counter({"needs_verification": 43, "completed": 8})
for op in content_ops:
expected = "completed" if op["operationId"] in approved else "needs_verification"
assert op["implementationStatus"] == "dedicated", op["operationId"]
assert op["completionStatus"] == expected, op["operationId"]
print("content_8_43=PASS")
PY
```
检查 protected diff
```bash
git -c core.quotePath=false diff --name-only -- docs/api-contracts
git -c core.quotePath=false diff --cached --name-only -- docs/api-contracts
git -c core.quotePath=false diff --name-only -- muse-cloud/muse-module-content/muse-module-content-server/src/main/java muse-cloud/sql/muse
git -c core.quotePath=false diff --cached --name-only -- muse-cloud/muse-module-content/muse-module-content-server/src/main/java muse-cloud/sql/muse
```
输出必须为空。
检查 whitespace
```bash
git diff --check
```
检查 allowed diff必须覆盖 unstaged / staged / untracked
```bash
python3 - <<'PY'
import subprocess
import sys
allowed = {
"muse-cloud/scripts/p1r-audit-api-coverage.py",
"docs/superpowers/reports/p1r-api-coverage.json",
"docs/superpowers/reports/p1r-api-coverage.md",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentRealApiGateTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rEventsRealApiGateTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiRealApiGateTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rKnowledgeRealApiGateTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketRealApiGateTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountRealApiGateTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMetaRealApiGateTest.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentCoreCompletedApprovalIT.java",
"muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishFlywayMigrationIT.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentAppServiceTest.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentSourceServiceTest.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishOutboxServiceTest.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/application/ContentEventPublishWorkerTest.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/controller/app/AppContentControllerTest.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/test/java/cn/iocoder/muse/module/content/controller/app/AppContentSourceControllerTest.java",
"docs/agent-specs/.agent",
"docs/agent-specs/2026-06-11-P1RContentCompletedApproval审阅版.md",
"docs/agent-specs/2026-06-12-P1RContentCompletedApproval执行版.md",
"docs/memorys/2026-06-12-P1RContent状态推进.md",
}
commands = [
["git", "-c", "core.quotePath=false", "diff", "--name-only"],
["git", "-c", "core.quotePath=false", "diff", "--cached", "--name-only"],
["git", "-c", "core.quotePath=false", "ls-files", "--others", "--exclude-standard"],
]
actual = set()
for command in commands:
output = subprocess.check_output(command, text=True)
actual.update(line for line in output.splitlines() if line)
unexpected = sorted(actual - allowed)
if unexpected:
print("UNEXPECTED_DIFF")
print("\n".join(unexpected))
sys.exit(1)
print("allowed_diff_gate=PASS")
PY
```
输出只能包含本执行版允许路径。若出现 OpenAPI、业务实现、SQL migration 或非批准 gate 文件,必须停止。
### Task 9memory 与 `.agent`
实现通过后新增:
- `docs/memorys/2026-06-12-P1RContent状态推进.md`
内容必须记录:
- 批准的 8 个 Content operation。
- 保持 `needs_verification` 的 43 个 Content operation 及原因。
- scanner/report/gate 修改范围。
- Content HTTP+DB `_test` 的数据库名、验证命令、XML 计数和 shadow-path matrix 覆盖点。
- ContentEventsPublish gate、V21 Flyway `_test`、组合顺序验证的命令和结果。
- focused tests、P1R mixed gates、XML 防空跑、protected diff 和 allowed diff 结果。
- 明确说明不代表 Content 51/51 completed不代表 Account remaining / Market remaining / 总 P1R completed。
同时更新 `docs/agent-specs/.agent`,但只能记录事实,不写未验证结论。
### Task 10fresh implementation review
实现、验证和文档留痕后,必须派发两类 fresh reviewer
- spec/correctness reviewer检查 8 个 operation 是否严格匹配本执行版43 个 operation 是否仍 needs_verificationContent domain allowlist 是否未新增。
- quality/data-integrity/testing reviewer检查 scanner/report/gate、legacy mixed gate 同步、HTTP+DB `_test`、XML 防空跑、V21 Flyway `_test`、system property restore、allowed/protected diff、rollback 是否可靠。
双 PASS 前不得提交、push 或宣称 Content completed approval 已收口。
## 回滚策略
如实现后需要撤回:
1. 从 `APPROVED_COMPLETED_OPERATIONS` 移除 8 个 `content:*` key。
2. 重新运行:
```bash
python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check
```
3. 恢复 gate 期望:
```text
summary completed=131
summary needsVerification=102
content completed=0
content needsVerification=51
```
4. 删除或修订本轮 memory保留失败原因。
5. 重新运行 Content/P1R focused gates、`git diff --check`、protected diff 和 allowed diff。
## 验收标准
执行版本身可以进入实现前批准点的条件:
1. 本文件已写入 `docs/agent-specs/2026-06-12-P1RContentCompletedApproval执行版.md`
2. `.agent` 记录执行版当前状态。
3. `git diff --check` 通过。
4. OpenAPI、scanner、coverage report、业务实现、SQL migration、P1R gate test 当前无新增 diff。
5. fresh execution spec/scope review PASS。
6. fresh execution quality/feasibility review PASS。
实现完成条件必须等用户批准后另行满足,不由本执行版写入自动成立。

View File

@ -0,0 +1,378 @@
# P1R Content 状态推进
日期2026-06-12
## 结论
本轮只推进 Content 第一批 8 个 operation-level completed approval
- `content:listWorks`
- `content:getWork`
- `content:listChapters`
- `content:getChapter`
- `content:listBlocks`
- `content:getBlock`
- `content:saveBlock`
- `content:getBlockSourceAttribution`
Content 仍不是整域 completed。其余 43 个 Content operation 继续 `dedicated/needs_verification`
本轮未修改 OpenAPI未修改 Content 业务实现,未修改 SQL migration未把 `content` 加入 domain-level completed allowlist。
## 工作区与基线
工作区:
```text
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
```
基线 HEAD
```text
0032774 test(p1r): 收口 Market 第一批 completed approval 门禁
```
实施前 coverage summary
```text
233 131 102 0 0 0
```
实施后 coverage summary
```text
233 139 94 0 0 0
```
实施后 Content 状态:
```text
completed=8
needs_verification=43
```
## 继续 needs_verification 的 Content operation
```text
content:adminListExportTasks
content:adminListImportTasks
content:adminListWorks
content:adminGetWork
content:adminListChapters
content:adminRiskAction
content:confirmChapterParseResult
content:rejectChapterParseResult
content:downloadExportPackage
content:getExportTask
content:getImportTask
content:getParseJob
content:listParseJobChapters
content:batchConfirmChapters
content:retryParseJob
content:createWork
content:deleteWork
content:updateWork
content:deleteBlock
content:mergeBlocks
content:splitBlock
content:mergeBlockSuggestion
content:createChapter
content:deleteChapter
content:updateChapter
content:createBlock
content:reorderChapters
content:validateDynamicFields
content:exportWork
content:createExportTask
content:createImportTask
content:listMetaProjections
content:getMetaProjection
content:createParseJob
content:getPlanning
content:listPlanningCandidates
content:createPlanningCandidate
content:getPlanningCandidate
content:confirmPlanningCandidate
content:discardPlanningCandidate
content:createStyleCheck
content:getStyleCheckResult
content:savePlanningItem
```
主要原因:
- 创建、更新、删除、重排、拆分、合并等写命令仍存在 OpenAPI / coverage `requiresCommandId` 与服务端 VO 或实现口径不一致风险,本轮不夹带合同修正。
- 导入、导出、解析、下载仍缺 FileService、任务产物和真实下载字节闭环 evidence。
- Meta projection、AI planning candidate、style check、suggestion 等能力仍依赖 Meta / AI / Knowledge 外部 owner closure 和运行时证据。
- admin read / risk action 需要单独 admin guard、审计和跨 owner 边界 evidence。
- planning section 虽属 Content 本域,但有 schemaVersion、projectionVersion、CAS、command replay 等独立合同,后续应单独切片。
## 修改范围
本轮修改:
- `muse-cloud/scripts/p1r-audit-api-coverage.py`
- `docs/superpowers/reports/p1r-api-coverage.json`
- `docs/superpowers/reports/p1r-api-coverage.md`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rEventsRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAiRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rKnowledgeRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rAccountRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMetaRealApiGateTest.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishFlywayMigrationIT.java`
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentCoreCompletedApprovalIT.java`
- `docs/agent-specs/.agent`
- `docs/agent-specs/2026-06-11-P1RContentCompletedApproval审阅版.md`
- `docs/agent-specs/2026-06-12-P1RContentCompletedApproval执行版.md`
- `docs/memorys/2026-06-12-P1RContent状态推进.md`
未修改:
- 7 个 OpenAPI。
- Content 业务实现。
- SQL migration。
- Content domain-level completed allowlist。
## TDD evidence
RED 阶段先修改 P1R gate 期望值和 Content 8 个 operation 断言,在旧 scanner/report 下运行 focused P1R gates预期失败已出现
- 失败点来自旧 report 仍为 `completed=131 / needsVerification=102`
- 失败点来自 Content 8 个 operation 仍为 `needs_verification`
- 该失败证明 gate 会阻止未获 scanner/report 支撑的 completed 推进。
GREEN 阶段完成 scanner operation-level allowlist、report 重新生成、Content HTTP+DB `_test` evidence 和 focused gates 后,目标验证通过。
## MockMvc HTTP 入口 + DB 证据
新增:
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentCoreCompletedApprovalIT.java`
验证方式:
- 使用 Spring Mock web context + `MockMvc`
- 使用真实 mapper / service。
- 使用真实 PostgreSQL `_test` 数据库。
- 使用 Flyway clean/migrate V1-V21。
- 密码只从环境变量读取,禁止通过 JVM system property 传入。
说明:这里的 HTTP 入口证据是执行版要求的 WebApplicationContext + MockMvc 请求路径证据,不是启动真实 socket 端口的 live servlet container 证据。
验证数据库:
```text
muse_p1r_content_core_completed_approval_test
```
通过结果:
```text
P1rContentCoreCompletedApprovalIT: tests=12, failures=0, errors=0, skipped=0
```
review 修复:
- fresh testing reviewHooke指出 chapter/block missing 负路径缺显式 HTTP 断言。
- 已补 `missing work chapter list``missing chapter detail``missing chapter block list``missing block detail`
- 已补上述查询负路径的 Content outbox no-write 断言。
- 修复后已重跑 `P1rContentCoreCompletedApprovalIT`,结果仍为 tests=12、failures=0、errors=0、skipped=0XML mtime 在本次运行窗口内。
- fresh implementation review 第一轮中Bohr correctness review 与 Euclid testing/data-integrity review 均 FAIL指出 `saveBlock``expectedRevision`、缺 `sourceSnapshot` 两个执行版 mandatory 负路径。
- 已补 `p1r-content-missing-expected-revision``p1r-content-missing-source-snapshot` 两个 HTTP+DB no-write 断言,并复用 `assertSnapshotUnchanged` 覆盖 block/source/outbox/command 不写入。
- 修复后已重跑 `P1rContentCoreCompletedApprovalIT`,结果仍为 tests=12、failures=0、errors=0、skipped=0XML mtime 在本次运行窗口内。
覆盖内容:
- `listWorks` 分页、状态过滤、owner 隔离、tenant 隔离、空页。
- `getWork` happy、missing、cross owner、cross tenant。
- `listChapters` 的 happy、empty、missing work、cross owner、cross tenant。
- `getChapter` 的 happy、missing chapter、path mismatch、cross owner、cross tenant。
- `listBlocks` 的 happy、empty、missing chapter、path mismatch、cross owner、cross tenant。
- `getBlock` 的 happy、missing block、path mismatch、cross owner、cross tenant。
- `saveBlock` 写入 block revision、source attribution、Content outbox、command log。
- `saveBlock` command replay 不重复写。
- `saveBlock``commandId`、缺 `expectedRevision`、缺 `sourceSnapshot`、revision conflict、command conflict、path mismatch、cross owner、cross tenant 均断言 no-write 或事务回滚后行数不变。
- `getBlockSourceAttribution` active/current、empty、path mismatch、cross owner、cross tenant错误路径不写 DB。
## Flyway V21 证据
增强:
- `muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentEventsPublishFlywayMigrationIT.java`
验证数据库:
```text
muse_p1r_content_core_completed_approval_test
```
通过结果:
```text
P1rContentEventsPublishFlywayMigrationIT: tests=4, failures=0, errors=0, skipped=0
```
关键输出:
```text
flyway_success=true
flyway_url=jdbc:postgresql://<host>/muse_p1r_content_core_completed_approval_test
flyway_locations_effective=filesystem:/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0/muse-cloud/sql/muse
migrations_executed=21
successful_migration_count=21
target_schema_version=21
flyway_latest=21:extend content events publish outbox
v21_table=muse_content_event_publish_outbox
v21_triggers=trg_muse_content_event_publish_outbox_update_time
```
验证内容:
- V1-V21 clean migrate。
- V21 Content event publish outbox 表存在。
- claim、owner/status、block/revision 相关索引存在。
- event type、notification type、source status、publish status、owner、block、block revision、resource ref、attempt count、max attempt 等约束存在。
- update time trigger 存在。
- `attempt_count=-1` 被 PostgreSQL check 拒绝。
- `max_attempt=0` 被 PostgreSQL check 拒绝。
## system property 组合验证
`P1rContentCoreCompletedApprovalIT``P1rContentEventsPublishFlywayMigrationIT` 都会保存并在 `@AfterAll` 恢复 `p1r.flyway.url/user`,避免脱敏后的 system property 污染同一 Surefire JVM 后续测试。
组合验证使用真实 PostgreSQL `_test` 库:
```text
muse_p1r_content_core_completed_approval_test
```
通过结果:
```text
P1rContentCoreCompletedApprovalIT,P1rContentEventsPublishFlywayMigrationIT: tests=16, failures=0, errors=0, skipped=0
P1rContentCoreCompletedApprovalIT,P1rContentEventsPublishFlywayMigrationIT with -Dsurefire.runOrder=reversealphabetical: tests=16, failures=0, errors=0, skipped=0
```
Hooke 修复后已重跑上述两组组合验证,结果仍为 16/16 pass两个目标 XML 均为本次运行窗口内的新产物。
Bohr / Euclid P1 修复后已重跑普通组合验证,结果仍为 16/16 pass两个目标 XML 均为本次运行窗口内的新产物。
注意:两次组合验证的实际 Surefire 执行顺序均为 `P1rContentEventsPublishFlywayMigrationIT` 先、`P1rContentCoreCompletedApprovalIT` 后。`-Dsurefire.runOrder=reversealphabetical` 未改变实际顺序,因此本轮只能证明 Flyway IT 脱敏/恢复不会污染后续 Core IT不能写成已证明 Core IT -> Flyway IT 反向顺序。
## focused gates
Content focused verification 通过,并已做 XML 防空跑:
```text
ContentAppServiceTest: tests=13, failures=0, errors=0, skipped=0
ContentSourceServiceTest: tests=23, failures=0, errors=0, skipped=0
ContentEventPublishOutboxServiceTest: tests=6, failures=0, errors=0, skipped=0
ContentEventPublishWorkerTest: tests=10, failures=0, errors=0, skipped=0
AppContentControllerTest: tests=32, failures=0, errors=0, skipped=0
AppContentSourceControllerTest: tests=5, failures=0, errors=0, skipped=0
P1rContentEventsPublishMigrationSqlTest: tests=3, failures=0, errors=0, skipped=0
P1rContentEventsPublishDependencyTest: tests=2, failures=0, errors=0, skipped=0
P1rContentEventsPublishEndToEndTest: tests=5, failures=0, errors=0, skipped=0
P1rContentRealApiGateTest: tests=5, failures=0, errors=0, skipped=0
P1rApiCoverageReportTest: tests=6, failures=0, errors=0, skipped=0
```
P1R mixed gates 通过,并已做 XML 防空跑:
```text
P1rApiCoverageReportTest: tests=6, failures=0, errors=0, skipped=0
P1rContentRealApiGateTest: tests=5, failures=0, errors=0, skipped=0
P1rEventsRealApiGateTest: tests=7, failures=0, errors=0, skipped=0
P1rAiRealApiGateTest: tests=7, failures=0, errors=0, skipped=0
P1rKnowledgeRealApiGateTest: tests=8, failures=0, errors=0, skipped=0
P1rMarketRealApiGateTest: tests=6, failures=0, errors=0, skipped=0
P1rAccountRealApiGateTest: tests=5, failures=0, errors=0, skipped=0
P1rMetaRealApiGateTest: tests=5, failures=0, errors=0, skipped=0
```
合计:
```text
P1R mixed gates: 49/49 pass
```
## scanner 与 diff 证据
scanner
```bash
python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check
```
结果:
```text
Generated docs/superpowers/reports/p1r-api-coverage.json
Generated docs/superpowers/reports/p1r-api-coverage.md
233 139 94 0 0 0
```
Content operation 状态确认:
```text
completed=8
needs_verification=43
```
completed 清单:
```text
listWorks
getWork
listChapters
getChapter
listBlocks
getBlock
saveBlock
getBlockSourceAttribution
```
代表性非目标 operation 仍为 `needs_verification`
```text
adminRiskAction
createWork
updateWork
mergeBlockSuggestion
createChapter
exportWork
getPlanning
```
diff check
```text
git diff --check: pass
```
protected OpenAPI diff
```text
empty
```
allowed diff 检查结果:
```text
仅包含执行版允许的 docs/agent-specs、docs/memorys、coverage report、scanner、P1R gate test、Content Core IT 与 Content Flyway IT 文件。
```
## 当前边界
- fresh implementation re-review 已双 PASSMendel correctness review PASS确认 Bohr/Euclid P1 已关闭8 个 Content completed operation、scanner operation-level allowlist、coverage summary、protected diff 与 XML 证据均自洽Anscombe testing/data-integrity review PASS无 findings、无 testing gaps残余风险仅为 raw Surefire XML 可能包含本地 DB host/user未发现 password 泄露。
- 本轮只代表 Content 第一批 8 个 operation-level completed approval。
- 不代表 Content 51/51 completed。
- 不代表 Account remaining completed。
- 不代表 Market remaining completed。
- 不代表总 P1R completed。
- raw Surefire XML 可能包含 DB host/user未发现 password 泄露;外发前需清洗。
- 当前已具备提交条件;提交或 push 仍需用户明确指令。

View File

@ -1,5 +1,5 @@
{
"generatedAt": "2026-05-25T07:06:40+00:00",
"generatedAt": "2026-05-25T15:49:45+00:00",
"sourceContracts": [
"docs/api-contracts/account/openapi.yaml",
"docs/api-contracts/ai/openapi.yaml",
@ -11,9 +11,9 @@
],
"summary": {
"totalOperations": 233,
"completedOperations": 131,
"completedOperations": 139,
"incompleteOperations": 0,
"needsVerificationOperations": 102,
"needsVerificationOperations": 94,
"catchAllOperations": 0,
"genericPersistenceOperations": 0,
"ssePlaceholderOperations": 0,
@ -8308,7 +8308,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentController.java"
],
@ -8345,7 +8345,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",
@ -8458,7 +8458,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentController.java"
],
@ -8495,7 +8495,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",
@ -8608,7 +8608,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentController.java"
],
@ -8645,7 +8645,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",
@ -8658,7 +8658,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentController.java"
],
@ -8695,7 +8695,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",
@ -8758,7 +8758,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentSourceController.java"
],
@ -8795,7 +8795,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",
@ -8911,7 +8911,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentController.java"
],
@ -8948,7 +8948,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",
@ -9061,7 +9061,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentController.java"
],
@ -9098,7 +9098,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",
@ -9161,7 +9161,7 @@
"externalDependencies": [],
"targetStage": "P1R-1 Content Real API",
"implementationStatus": "dedicated",
"completionStatus": "needs_verification",
"completionStatus": "completed",
"controllerFiles": [
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/controller/app/AppContentController.java"
],
@ -9198,7 +9198,7 @@
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentPlanningCandidateFacade.java",
"muse-cloud/muse-module-content/muse-module-content-server/src/main/java/cn/iocoder/muse/module/content/application/facade/ContentStyleCheckFacade.java"
],
"notes": "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure."
"notes": "Dedicated entry exists; user-approved P1R acceptance evidence allows this operation to advance to completed coverage."
},
{
"domain": "content",

View File

@ -1,9 +1,9 @@
# P1R API Coverage Report
- Generated at: `2026-05-25T07:06:40+00:00`
- Generated at: `2026-05-25T15:49:45+00:00`
- Total operations: `233`
- Completed: `131`
- Needs verification: `102`
- Completed: `139`
- Needs verification: `94`
- Incomplete: `0`
- Catch-all: `0`
- Generic persistence: `0`
@ -103,24 +103,24 @@
| content | app | GET | `/app-api/muse/parse-jobs/{jobId}/chapters` | `listParseJobChapters` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/parse-jobs/{jobId}/chapters/batch-confirm` | `batchConfirmChapters` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/parse-jobs/{jobId}/retry` | `retryParseJob` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works` | `listWorks` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works` | `listWorks` | dedicated | completed | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/works` | `createWork` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | DELETE | `/app-api/muse/works/{workId}` | `deleteWork` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}` | `getWork` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}` | `getWork` | dedicated | completed | P1R-1 Content Real API |
| content | app | PUT | `/app-api/muse/works/{workId}` | `updateWork` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | DELETE | `/app-api/muse/works/{workId}/blocks/{blockId}` | `deleteBlock` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/blocks/{blockId}` | `getBlock` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | PUT | `/app-api/muse/works/{workId}/blocks/{blockId}` | `saveBlock` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/blocks/{blockId}` | `getBlock` | dedicated | completed | P1R-1 Content Real API |
| content | app | PUT | `/app-api/muse/works/{workId}/blocks/{blockId}` | `saveBlock` | dedicated | completed | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/works/{workId}/blocks/{blockId}/merge` | `mergeBlocks` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/blocks/{blockId}/source-attribution` | `getBlockSourceAttribution` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/blocks/{blockId}/source-attribution` | `getBlockSourceAttribution` | dedicated | completed | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/works/{workId}/blocks/{blockId}/split` | `splitBlock` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/works/{workId}/blocks/{blockId}/suggestion-merges` | `mergeBlockSuggestion` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/chapters` | `listChapters` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/chapters` | `listChapters` | dedicated | completed | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/works/{workId}/chapters` | `createChapter` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | DELETE | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `deleteChapter` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `getChapter` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `getChapter` | dedicated | completed | P1R-1 Content Real API |
| content | app | PUT | `/app-api/muse/works/{workId}/chapters/{chapterId}` | `updateChapter` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/chapters/{chapterId}/blocks` | `listBlocks` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | GET | `/app-api/muse/works/{workId}/chapters/{chapterId}/blocks` | `listBlocks` | dedicated | completed | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/works/{workId}/chapters/{chapterId}/blocks` | `createBlock` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | PUT | `/app-api/muse/works/{workId}/chapters/{chapterId}/reorder` | `reorderChapters` | dedicated | needs_verification | P1R-1 Content Real API |
| content | app | POST | `/app-api/muse/works/{workId}/dynamic-fields/validate` | `validateDynamicFields` | dedicated | needs_verification | P1R-1 Content Real API |

View File

@ -54,6 +54,18 @@ class P1rAccountRealApiGateTest {
"adminListQuotaAdjustments"
);
/** Content completed approval 第一批只允许推进的 8 个 operation。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
/** P1R-3 计划第 1 节冻结的 Account operation、方法和路径清单。 */
private static final Map<String, String> P1R3_ACCOUNT_OPERATION_SIGNATURES = Map.ofEntries(
entry("adminListAccountUsers", "GET /admin-api/muse/account/users"),
@ -175,11 +187,46 @@ class P1rAccountRealApiGateTest {
assertEquals(23, needsVerification, "Account 剩余 23 个 operation 必须继续 needs_verification");
}
/**
* 验证 Content 只推进第一批 8 operation防止 Account gate 漏掉 Content 未批准项回退检查
*
* @throws IOException 读取覆盖报告失败时抛出
*/
@Test
void should_keep_content_partial_approval_at_8_completed_operations() throws IOException {
JsonNode contentOperations = readContentOperations();
int completed = 0;
int needsVerification = 0;
assertEquals(51, contentOperations.size(), "Content operation 数量必须保持 51");
for (JsonNode operation : contentOperations) {
String operationId = operation.path("operationId").asText("<missing-operationId>");
assertEquals("dedicated", operation.path("implementationStatus").asText(),
"Content/" + operationId + " 必须继续保持 dedicated");
if (APPROVED_CONTENT_COMPLETED_OPERATIONS.contains(operationId)) {
completed++;
assertEquals("completed", operation.path("completionStatus").asText(),
"Content/" + operationId + " 已获本轮批准后必须 completed");
} else {
needsVerification++;
assertEquals("needs_verification", operation.path("completionStatus").asText(),
"Content/" + operationId + " 未获本轮批准,必须继续 needs_verification");
}
}
assertEquals(8, completed, "Content 第一批 completed approval 只能推进 8 个 operation");
assertEquals(43, needsVerification, "Content 剩余 43 个 operation 必须继续 needs_verification");
}
private JsonNode readAccountOperations() throws IOException {
JsonNode operations = objectMapper.readTree(findReportPath().toFile()).path("operations");
return filterAccountOperations(operations);
}
private JsonNode readContentOperations() throws IOException {
JsonNode operations = objectMapper.readTree(findReportPath().toFile()).path("operations");
return filterContentOperations(operations);
}
private JsonNode filterAccountOperations(JsonNode operations) {
ArrayNode accountOperations = objectMapper.createArrayNode();
for (JsonNode operation : operations) {
@ -191,6 +238,16 @@ class P1rAccountRealApiGateTest {
return accountOperations;
}
private JsonNode filterContentOperations(JsonNode operations) {
ArrayNode contentOperations = objectMapper.createArrayNode();
for (JsonNode operation : operations) {
if ("content".equals(operation.path("domain").asText())) {
contentOperations.add(operation);
}
}
return contentOperations;
}
private static Map<String, String> toOperationSignatures(JsonNode operations) {
Map<String, String> operationSignatures = new LinkedHashMap<>();
for (JsonNode operation : operations) {

View File

@ -85,6 +85,18 @@ class P1rAiRealApiGateTest {
"unfavoriteAsset"
);
/** Content completed approval 第一批只允许推进的 8 个 operation。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
@ -127,8 +139,8 @@ class P1rAiRealApiGateTest {
void should_count_ai_and_knowledge_operations_as_completed() throws IOException {
JsonNode report = readReport();
assertEquals(131, report.path("summary").path("completedOperations").asInt(),
"completedOperations 必须只来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4");
assertEquals(139, report.path("summary").path("completedOperations").asInt(),
"completedOperations 必须只来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4 + Content 8");
}
@Test
@ -154,12 +166,39 @@ class P1rAiRealApiGateTest {
@Test
void should_keep_content_meta_account_dedicated_needs_verification() throws IOException {
assertDomainStatusCount("content", 51);
assertContentStatusCount(8, 43);
assertMetaStatusCount(16, 0);
assertAccountStatusCount(10, 23);
assertMarketStatusCount(4, 28);
}
private void assertContentStatusCount(int expectedCompleted, int expectedNeedsVerification) throws IOException {
int completed = 0;
int needsVerification = 0;
int total = 0;
for (JsonNode operation : readOperations()) {
if (!"content".equals(operation.path("domain").asText())) {
continue;
}
total++;
String operationId = operation.path("operationId").asText("<missing-operationId>");
assertEquals("dedicated", operation.path("implementationStatus").asText(),
"content/" + operationId + " 必须保持 dedicated");
if (APPROVED_CONTENT_COMPLETED_OPERATIONS.contains(operationId)) {
completed++;
assertEquals("completed", operation.path("completionStatus").asText(),
"content/" + operationId + " 已获本轮批准后必须 completed");
} else {
needsVerification++;
assertEquals("needs_verification", operation.path("completionStatus").asText(),
"content/" + operationId + " 未获本轮批准,必须继续 needs_verification");
}
}
assertEquals(51, total, "Content operation 数量必须保持 51");
assertEquals(expectedCompleted, completed, "Content 第一批 completed approval 只能推进 8 个 operation");
assertEquals(expectedNeedsVerification, needsVerification, "Content 剩余 43 个 operation 必须继续 needs_verification");
}
private void assertAccountStatusCount(int expectedCompleted, int expectedNeedsVerification) throws IOException {
int completed = 0;
int needsVerification = 0;

View File

@ -84,7 +84,15 @@ class P1rApiCoverageReportTest {
"market:getMarketplaceAsset",
"market:listMarketplaceCategories",
"market:favoriteAsset",
"market:unfavoriteAsset"
"market:unfavoriteAsset",
"content:listWorks",
"content:getWork",
"content:listChapters",
"content:getChapter",
"content:listBlocks",
"content:getBlock",
"content:saveBlock",
"content:getBlockSourceAttribution"
);
/** 已批准的 MetaSchema 管理接口。 */
@ -133,6 +141,18 @@ class P1rApiCoverageReportTest {
"unfavoriteAsset"
);
/** 已批准的 Content 第一批用户端核心作品、章节、Block 与来源归因接口,仍保持 operation-level approval。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
private final ObjectMapper objectMapper = new ObjectMapper();
/**
@ -234,12 +254,12 @@ class P1rApiCoverageReportTest {
}
/**
* 验证 completed approval 只推进已批准的 Events SSEMetaAccount Market operation不连带推进其它 owner domain
* 验证 completed approval 只推进已批准的 Events SSEMetaAccountMarket Content operation不连带推进其它 owner domain
*
* @throws IOException 读取覆盖报告失败时抛出
*/
@Test
void should_only_promote_approved_events_meta_account_and_market_operations() throws IOException {
void should_only_promote_approved_events_meta_account_market_and_content_operations() throws IOException {
JsonNode operations = readOperations();
int completed = 0;
@ -248,7 +268,7 @@ class P1rApiCoverageReportTest {
completed++;
}
}
assertEquals(131, completed, "Market 第一批 4 个 operation-level approval 后 completed 总数只能从 127 增至 131");
assertEquals(139, completed, "Content 第一批 8 个 operation-level approval 后 completed 总数只能从 131 增至 139");
assertOperationStatus("events", "streamEvents", "dedicated", "completed");
for (String operationId : APPROVED_META_SCHEMA_COMPLETED_OPERATIONS) {
@ -263,13 +283,23 @@ class P1rApiCoverageReportTest {
for (String operationId : APPROVED_MARKET_COMPLETED_OPERATIONS) {
assertOperationStatus("market", operationId, "dedicated", "completed");
}
for (String operationId : APPROVED_CONTENT_COMPLETED_OPERATIONS) {
assertOperationStatus("content", operationId, "dedicated", "completed");
}
assertOperationStatus("market", "listMarketplaceAssets", "dedicated", "needs_verification");
assertOperationStatus("market", "listMarketplaceRecommendations", "dedicated", "needs_verification");
assertOperationStatus("market", "favoriteAsset", "dedicated", "completed", true);
assertOperationStatus("market", "unfavoriteAsset", "dedicated", "completed", true);
assertOperationStatus("content", "createWork", "dedicated", "needs_verification");
assertOperationStatus("content", "updateWork", "dedicated", "needs_verification");
assertOperationStatus("content", "createChapter", "dedicated", "needs_verification");
assertOperationStatus("content", "mergeBlockSuggestion", "dedicated", "needs_verification");
assertOperationStatus("content", "exportWork", "dedicated", "needs_verification");
assertOperationStatus("content", "getPlanning", "dedicated", "needs_verification");
assertOperationStatus("content", "adminRiskAction", "dedicated", "needs_verification");
assertDomainStatusCount("market", "dedicated", "needs_verification", 28);
assertDomainStatusCount("account", "dedicated", "needs_verification", 23);
assertDomainStatusCount("content", "dedicated", "needs_verification", 51);
assertDomainStatusCount("content", "dedicated", "needs_verification", 43);
}
/**

View File

@ -3,6 +3,7 @@ package cn.iocoder.muse.server.framework.api;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.output.MigrateResult;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
@ -67,6 +68,14 @@ class P1rContentEventsPublishFlywayMigrationIT {
"user", "username", "password", "pass", "pwd", "sslpassword", "ssl_password",
"token", "secret", "api_key", "apikey", "bearer", "access_token", "refresh_token"
);
private static volatile boolean originalFlywayPropertiesCaptured;
private static volatile String originalFlywayUrlSystemProperty;
private static volatile String originalFlywayUserSystemProperty;
@AfterAll
static void restoreFlywaySystemProperties() {
restoreOriginalFlywaySystemProperties();
}
@Test
void should_migrateV1ToV21OnRealPostgresqlAndVerifyContentPublishOutboxSchema() throws Exception {
@ -205,10 +214,38 @@ class P1rContentEventsPublishFlywayMigrationIT {
private static void redactFlywaySystemProperties(String url, String user) {
// 原因Surefire XML 会记录 JVM system property读取后立即脱敏避免报告文件残留真实连接信息
captureOriginalFlywaySystemProperties();
System.setProperty("p1r.flyway.url", maskedUrl(url));
System.setProperty("p1r.flyway.user", maskUser(user));
}
private static void captureOriginalFlywaySystemProperties() {
if (originalFlywayPropertiesCaptured) {
return;
}
// IT 会为日志脱敏 p1r.flyway.*先保存原值避免同一 Surefire JVM 中污染后续 Flyway IT
originalFlywayUrlSystemProperty = System.getProperty("p1r.flyway.url");
originalFlywayUserSystemProperty = System.getProperty("p1r.flyway.user");
originalFlywayPropertiesCaptured = true;
}
private static void restoreOriginalFlywaySystemProperties() {
if (!originalFlywayPropertiesCaptured) {
return;
}
// 恢复用户传入的原始连接属性让组合运行的 Flyway 验收测试继续读取真实 _test 库地址
restoreSystemProperty("p1r.flyway.url", originalFlywayUrlSystemProperty);
restoreSystemProperty("p1r.flyway.user", originalFlywayUserSystemProperty);
}
private static void restoreSystemProperty(String name, String value) {
if (value == null) {
System.clearProperty(name);
return;
}
System.setProperty(name, value);
}
private static void cleanSchema(Flyway flyway, String url, String user) {
try {
flyway.clean();
@ -488,6 +525,10 @@ class P1rContentEventsPublishFlywayMigrationIT {
"block_revision<=0 必须被 V21 check 拒绝");
assertInsertRejected(connection, rowWith("resource_ref_id", 0L),
"resource_ref_id<=0 必须被 V21 check 拒绝");
assertInsertRejected(connection, rowWith("attempt_count", -1),
"attempt_count<0 必须被 V21 check 拒绝");
assertInsertRejected(connection, rowWith("max_attempt", 0),
"max_attempt<=0 必须被 V21 check 拒绝");
connection.setAutoCommit(true);
}

View File

@ -93,6 +93,29 @@ class P1rContentRealApiGateTest {
"savePlanningItem"
);
/** 已批准的 Content 第一批用户端核心作品、章节、Block 与来源归因接口,仍保持 operation-level approval。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
/** 必须继续保持 needs_verification 的代表性未批准 Content operation防止 CRUD、导出、规划和 admin 误推进。 */
private static final Set<String> REPRESENTATIVE_UNAPPROVED_CONTENT_OPERATIONS = Set.of(
"createWork",
"updateWork",
"createChapter",
"mergeBlockSuggestion",
"exportWork",
"getPlanning",
"adminRiskAction"
);
private final ObjectMapper objectMapper = new ObjectMapper();
/**
@ -173,20 +196,34 @@ class P1rContentRealApiGateTest {
}
/**
* 验证当前 P1R-1 收口口径Content 全部进入 dedicated仍保持 needs_verification
* 验证当前 completed approval 只推进第一批 8 Content operation其余 43 仍保持 needs_verification
*
* @throws IOException 读取覆盖报告失败时抛出
*/
@Test
void should_keep_current_content_operations_dedicated_and_needs_verification() throws IOException {
void should_promote_only_first_batch_content_operations_to_completed() throws IOException {
JsonNode contentOperations = readContentOperations();
int completed = 0;
int needsVerification = 0;
for (JsonNode operation : contentOperations) {
String operationId = operation.path("operationId").asText("<missing-operationId>");
assertEquals("dedicated", operation.path("implementationStatus").asText(),
operationId + " 当前 P1R-1 目标必须是 dedicated");
assertEquals("needs_verification", operation.path("completionStatus").asText(),
operationId + " 当前 P1R-1 目标必须是 needs_verification不能提前宣称 completed");
if (APPROVED_CONTENT_COMPLETED_OPERATIONS.contains(operationId)) {
completed++;
assertEquals("completed", operation.path("completionStatus").asText(),
operationId + " 已获 Content 第一批批准后必须标记 completed");
} else {
needsVerification++;
assertEquals("needs_verification", operation.path("completionStatus").asText(),
operationId + " 未获 Content 第一批批准,必须继续保持 needs_verification");
}
}
assertEquals(8, completed, "Content 第一批 operation-level completed approval 只能推进 8 个 operation");
assertEquals(43, needsVerification, "Content 剩余 43 个 operation 必须继续 needs_verification");
for (String operationId : REPRESENTATIVE_UNAPPROVED_CONTENT_OPERATIONS) {
assertContentOperationStatus(operationId, "needs_verification");
}
}
@ -213,6 +250,18 @@ class P1rContentRealApiGateTest {
return operationIds;
}
private void assertContentOperationStatus(String operationId, String expectedCompletionStatus) throws IOException {
for (JsonNode operation : readContentOperations()) {
if (!operationId.equals(operation.path("operationId").asText())) {
continue;
}
assertEquals(expectedCompletionStatus, operation.path("completionStatus").asText(),
"content/" + operationId + " completionStatus 不符合本轮审批边界");
return;
}
throw new AssertionError("content/" + operationId + " 必须存在于 coverage report");
}
/**
* 从当前 Maven 执行目录逐级向上查找仓库根目录避免 surefire 在不同模块目录执行时路径失效
*/

View File

@ -45,6 +45,18 @@ class P1rEventsRealApiGateTest {
"unfavoriteAsset"
);
/** Content completed approval 第一批只允许推进的 8 个 operation。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
@ -88,10 +100,10 @@ class P1rEventsRealApiGateTest {
void should_keep_completed_approval_summary_at_approved_operation_boundary() throws IOException {
JsonNode summary = readReport().path("summary");
assertEquals(131, summary.path("completedOperations").asInt(),
"completedOperations 只能来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4");
assertEquals(102, summary.path("needsVerificationOperations").asInt(),
"needsVerificationOperations 必须扣除已批准的 Events streamEvents、Meta 16、Account 10 和 Market 4");
assertEquals(139, summary.path("completedOperations").asInt(),
"completedOperations 只能来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4 + Content 8");
assertEquals(94, summary.path("needsVerificationOperations").asInt(),
"needsVerificationOperations 必须扣除已批准的 Events streamEvents、Meta 16、Account 10、Market 4 和 Content 8");
assertEquals(0, summary.path("incompleteOperations").asInt(),
"Events 退出 placeholder 后不应再留下 incomplete operation");
assertEquals(0, summary.path("genericPersistenceOperations").asInt(),
@ -154,6 +166,31 @@ class P1rEventsRealApiGateTest {
assertEquals(23, needsVerification, "Account 剩余 23 个 operation 必须继续 needs_verification");
}
@Test
void should_keep_content_partial_approval_at_8_completed_operations() throws IOException {
JsonNode contentOperations = readContentOperations();
int completed = 0;
int needsVerification = 0;
assertEquals(51, contentOperations.size(), "Content operation 数量必须保持 51");
for (JsonNode operation : contentOperations) {
String operationId = operation.path("operationId").asText("<missing-operationId>");
assertEquals("dedicated", operation.path("implementationStatus").asText(),
"Content/" + operationId + " 必须继续保持 dedicated");
if (APPROVED_CONTENT_COMPLETED_OPERATIONS.contains(operationId)) {
completed++;
assertEquals("completed", operation.path("completionStatus").asText(),
"Content/" + operationId + " 已获本轮批准后必须 completed");
} else {
needsVerification++;
assertEquals("needs_verification", operation.path("completionStatus").asText(),
"Content/" + operationId + " 未获本轮批准,必须继续 needs_verification");
}
}
assertEquals(8, completed, "Content 第一批 completed approval 只能推进 8 个 operation");
assertEquals(43, needsVerification, "Content 剩余 43 个 operation 必须继续 needs_verification");
}
private JsonNode readSingleEventsOperation() throws IOException {
JsonNode eventsOperations = readEventsOperations();
assertEquals(EXPECTED_EVENTS_OPERATION_COUNT, eventsOperations.size(),
@ -173,6 +210,10 @@ class P1rEventsRealApiGateTest {
return readOperationsByDomain("account");
}
private JsonNode readContentOperations() throws IOException {
return readOperationsByDomain("content");
}
private void assertMarketOperationStatus(String operationId, String expectedCompletionStatus,
boolean expectedRequiresCommandId) throws IOException {
for (JsonNode operation : readMarketOperations()) {

View File

@ -107,6 +107,18 @@ class P1rKnowledgeRealApiGateTest {
"unfavoriteAsset"
);
/** Content completed approval 第一批只允许推进的 8 个 operation。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
@ -166,19 +178,46 @@ class P1rKnowledgeRealApiGateTest {
void should_count_ai_and_knowledge_operations_as_completed() throws IOException {
JsonNode report = readReport();
assertEquals(131, report.path("summary").path("completedOperations").asInt(),
"completedOperations 必须只来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4");
assertEquals(139, report.path("summary").path("completedOperations").asInt(),
"completedOperations 必须只来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4 + Content 8");
}
@Test
void should_keep_existing_dedicated_domains_unchanged() throws IOException {
assertDomainStatusCount("ai", 41, "completed");
assertDomainStatusCount("content", 51);
assertContentStatusCount(8, 43);
assertMetaStatusCount(16, 0);
assertAccountStatusCount(10, 23);
assertMarketStatusCount(4, 28);
}
private void assertContentStatusCount(int expectedCompleted, int expectedNeedsVerification) throws IOException {
int completed = 0;
int needsVerification = 0;
int total = 0;
for (JsonNode operation : readOperations()) {
if (!"content".equals(operation.path("domain").asText())) {
continue;
}
total++;
String operationId = operation.path("operationId").asText("<missing-operationId>");
assertEquals("dedicated", operation.path("implementationStatus").asText(),
"content/" + operationId + " 必须保持 dedicated");
if (APPROVED_CONTENT_COMPLETED_OPERATIONS.contains(operationId)) {
completed++;
assertEquals("completed", operation.path("completionStatus").asText(),
"content/" + operationId + " 已获本轮批准后必须 completed");
} else {
needsVerification++;
assertEquals("needs_verification", operation.path("completionStatus").asText(),
"content/" + operationId + " 未获本轮批准,必须继续 needs_verification");
}
}
assertEquals(51, total, "Content operation 数量必须保持 51");
assertEquals(expectedCompleted, completed, "Content 第一批 completed approval 只能推进 8 个 operation");
assertEquals(expectedNeedsVerification, needsVerification, "Content 剩余 43 个 operation 必须继续 needs_verification");
}
@Test
void should_promote_p1r7_events_stream_without_reintroducing_sse_placeholder() throws IOException {
int ssePlaceholderCount = 0;

View File

@ -78,6 +78,18 @@ class P1rMarketRealApiGateTest {
"unfavoriteAsset"
);
/** Content completed approval 第一批只允许推进的 8 个 operation。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
@ -133,19 +145,46 @@ class P1rMarketRealApiGateTest {
void should_keep_completion_summary_owned_by_approved_scopes_only() throws IOException {
JsonNode report = readReport();
assertEquals(131, report.path("summary").path("completedOperations").asInt(),
"completedOperations 必须只来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4");
assertEquals(139, report.path("summary").path("completedOperations").asInt(),
"completedOperations 必须只来自 AI 41 + Knowledge 59 + Events streamEvents 1 + Meta 16 + Account 10 + Market 4 + Content 8");
}
@Test
void should_keep_existing_domain_statuses_unchanged() throws IOException {
assertDomainStatusCount("ai", 41, "completed");
assertDomainStatusCount("knowledge", 59, "completed");
assertDomainStatusCount("content", 51, "needs_verification");
assertContentStatusCount(8, 43);
assertMetaStatusCount(16, 0);
assertAccountStatusCount(10, 23);
}
private void assertContentStatusCount(int expectedCompleted, int expectedNeedsVerification) throws IOException {
int completed = 0;
int needsVerification = 0;
int total = 0;
for (JsonNode operation : readOperations()) {
if (!"content".equals(operation.path("domain").asText())) {
continue;
}
total++;
String operationId = operation.path("operationId").asText("<missing-operationId>");
assertEquals("dedicated", operation.path("implementationStatus").asText(),
"content/" + operationId + " 必须保持 dedicated");
if (APPROVED_CONTENT_COMPLETED_OPERATIONS.contains(operationId)) {
completed++;
assertEquals("completed", operation.path("completionStatus").asText(),
"content/" + operationId + " 已获本轮批准后必须 completed");
} else {
needsVerification++;
assertEquals("needs_verification", operation.path("completionStatus").asText(),
"content/" + operationId + " 未获本轮批准,必须继续 needs_verification");
}
}
assertEquals(51, total, "Content operation 数量必须保持 51");
assertEquals(expectedCompleted, completed, "Content 第一批 completed approval 只能推进 8 个 operation");
assertEquals(expectedNeedsVerification, needsVerification, "Content 剩余 43 个 operation 必须继续 needs_verification");
}
@Test
void should_promote_p1r7_events_stream_without_reintroducing_sse_placeholder() throws IOException {
int ssePlaceholderCount = 0;

View File

@ -76,6 +76,18 @@ class P1rMetaRealApiGateTest {
"activateFunctionChainVersion"
);
/** Content completed approval 第一批只允许推进的 8 个 operation。 */
private static final Set<String> APPROVED_CONTENT_COMPLETED_OPERATIONS = Set.of(
"listWorks",
"getWork",
"listChapters",
"getChapter",
"listBlocks",
"getBlock",
"saveBlock",
"getBlockSourceAttribution"
);
private final ObjectMapper objectMapper = new ObjectMapper();
/**
@ -155,11 +167,46 @@ class P1rMetaRealApiGateTest {
assertEquals(0, needsVerification, "Meta operation-level approval 后不应再保留 needs_verification operation");
}
/**
* 验证 Content 只推进第一批 8 operation防止 Meta gate 漏掉 Content 未批准项回退检查
*
* @throws IOException 读取覆盖报告失败时抛出
*/
@Test
void should_keep_content_partial_approval_at_8_completed_operations() throws IOException {
JsonNode contentOperations = readContentOperations();
int completed = 0;
int needsVerification = 0;
assertEquals(51, contentOperations.size(), "Content operation 数量必须保持 51");
for (JsonNode operation : contentOperations) {
String operationId = operation.path("operationId").asText("<missing-operationId>");
assertEquals("dedicated", operation.path("implementationStatus").asText(),
"Content/" + operationId + " 必须继续保持 dedicated");
if (APPROVED_CONTENT_COMPLETED_OPERATIONS.contains(operationId)) {
completed++;
assertEquals("completed", operation.path("completionStatus").asText(),
"Content/" + operationId + " 已获本轮批准后必须 completed");
} else {
needsVerification++;
assertEquals("needs_verification", operation.path("completionStatus").asText(),
"Content/" + operationId + " 未获本轮批准,必须继续 needs_verification");
}
}
assertEquals(8, completed, "Content 第一批 completed approval 只能推进 8 个 operation");
assertEquals(43, needsVerification, "Content 剩余 43 个 operation 必须继续 needs_verification");
}
private JsonNode readMetaOperations() throws IOException {
JsonNode operations = objectMapper.readTree(findReportPath().toFile()).path("operations");
return filterMetaOperations(operations);
}
private JsonNode readContentOperations() throws IOException {
JsonNode operations = objectMapper.readTree(findReportPath().toFile()).path("operations");
return filterContentOperations(operations);
}
private JsonNode filterMetaOperations(JsonNode operations) {
ArrayNode metaOperations = objectMapper.createArrayNode();
for (JsonNode operation : operations) {
@ -170,6 +217,16 @@ class P1rMetaRealApiGateTest {
return metaOperations;
}
private JsonNode filterContentOperations(JsonNode operations) {
ArrayNode contentOperations = objectMapper.createArrayNode();
for (JsonNode operation : operations) {
if ("content".equals(operation.path("domain").asText())) {
contentOperations.add(operation);
}
}
return contentOperations;
}
private static Map<String, String> toOperationSignatures(JsonNode operations) {
Map<String, String> operationSignatures = new LinkedHashMap<>();
for (JsonNode operation : operations) {

View File

@ -79,6 +79,15 @@ APPROVED_COMPLETED_OPERATIONS = {
"market:listMarketplaceCategories",
"market:favoriteAsset",
"market:unfavoriteAsset",
# P1R Content completed approval 第一批仍使用 operation-level 清单,避免整域 Content 被误升 completed。
"content:listWorks",
"content:getWork",
"content:listChapters",
"content:getChapter",
"content:listBlocks",
"content:getBlock",
"content:saveBlock",
"content:getBlockSourceAttribution",
}