test(p1r): 收口 Content 第一批 completed approval 门禁
This commit is contained in:
parent
00327749a2
commit
3ced553cb2
@ -163,3 +163,23 @@ P1R Market Completed Approval 修订执行版最终 fresh review 已双 PASS:B
|
||||
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 pass;XML 防空跑覆盖 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 server,memory 已将措辞收窄为 MockMvc HTTP 入口证据;其提出的 status CHECK / FK 属于新增迁移范围,当前执行版明确禁止改迁移,作为后续 Market schema hardening 风险保留,不作为本轮 approved diff;Dirac 复审提出 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 已双 PASS:Raman 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 hardening;raw 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 审阅版首轮 review:Feynman spec/scope review PASS,但 Herschel quality/feasibility review FAIL;Feynman 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 审阅版第二轮 review:Bernoulli spec/scope review PASS,但 Wegener quality/feasibility/testing review FAIL;Bernoulli 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 审阅版第三轮 review:Leibniz quality/feasibility/testing review FAIL,P1 有效并已只读验证:`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 已双 PASS:Fermat 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 已双 PASS:Rawls 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 allowlist;3) 执行版 allowed diff;4) 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 key;coverage 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-write;fresh testing review(Hooke)指出 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=0,XML 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 已双 PASS:Mendel correctness review PASS,无 findings、无 residual risks;Anscombe 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 key,OpenAPI、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 仍需用户明确指令。
|
||||
|
||||
450
docs/agent-specs/2026-06-11-P1RContentCompletedApproval审阅版.md
Normal file
450
docs/agent-specs/2026-06-11-P1RContentCompletedApproval审阅版.md
Normal 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` 默认 unavailable,Parse 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 owner,Content 当前只能 fail-closed。
|
||||
- `getExportTask` / admin export read 可在后续通过 DB seed 单独审批,但不应与创建/下载闭环混为一批。
|
||||
|
||||
### Meta projection 与动态字段
|
||||
|
||||
以下 operation 暂不推荐第一批:
|
||||
|
||||
- `listMetaProjections`
|
||||
- `getMetaProjection`
|
||||
- `validateDynamicFields`
|
||||
|
||||
主要缺口:
|
||||
|
||||
- `ContentMetaFacade` 默认 unavailable。
|
||||
- MetaSchema 解释、字段校验和写入路由建议归 Meta owner,Content 服务明确不复制 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 数量大于 0,failures/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。
|
||||
857
docs/agent-specs/2026-06-12-P1RContentCompletedApproval执行版.md
Normal file
857
docs/agent-specs/2026-06-12-P1RContentCompletedApproval执行版.md
Normal 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、跨 tenant,DTO 字段完整 |
|
||||
| `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,详情含 blocks,missing、wrong work、跨 owner/tenant |
|
||||
| `content:listBlocks` | `GET` | `/app-api/muse/works/{workId}/chapters/{chapterId}/blocks` | `dedicated / completed` | chapter 属于 path work,Block 排序,有数据、空列表、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 completed,43 个继续 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 1:TDD 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 2:TDD 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 3:Content 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 4:Content 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 6:Content 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 7:P1R 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 9:memory 与 `.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 10:fresh implementation review
|
||||
|
||||
实现、验证和文档留痕后,必须派发两类 fresh reviewer:
|
||||
|
||||
- spec/correctness reviewer:检查 8 个 operation 是否严格匹配本执行版,43 个 operation 是否仍 needs_verification,Content 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。
|
||||
|
||||
实现完成条件必须等用户批准后另行满足,不由本执行版写入自动成立。
|
||||
378
docs/memorys/2026-06-12-P1RContent状态推进.md
Normal file
378
docs/memorys/2026-06-12-P1RContent状态推进.md
Normal 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 review(Hooke)指出 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=0,XML 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=0,XML 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 已双 PASS:Mendel 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 仍需用户明确指令。
|
||||
@ -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",
|
||||
|
||||
@ -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 |
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 SSE、Meta、Account 与 Market operation,不连带推进其它 owner domain。
|
||||
* 验证 completed approval 只推进已批准的 Events SSE、Meta、Account、Market 与 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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 在不同模块目录执行时路径失效。
|
||||
*/
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user