oh-my-muse/docs/agent-specs/2026-06-13-P1RContentAdminRiskActionCompletedApproval审阅版.md

19 KiB
Raw Blame History

P1R Content Admin RiskAction Completed Approval 审阅版

日期2026-06-13

结论

推荐 Content 下一批只推进 1 个管理后台治理写命令的 operation-level completed approval

content:adminRiskAction

不推荐把 Content 剩余 38 个 operation 一次性推进 completed也不推荐把 content 加入 domain-level completed allowlist。

adminRiskAction 与刚收口的 Admin Read 3 个只读 operation 相邻,但它不是读模型。它是治理写路径,必须证明 commandIdexpectedVersion、行锁、目标归属、治理事实写入、command/audit envelope、幂等 replay、冲突拒绝、RBAC、API version、tenant 隔离和事务回滚全部闭合后,才允许进入 completed approval。

本审阅版只冻结候选范围、证据标准、风险边界和后续执行版要求。不修改 OpenAPI不修改 scanner不修改 coverage report不修改业务实现不推进任何 operation completed。

flowchart TB
    Current["当前 Content<br/>13 completed / 38 needs_verification"] --> Candidate["下一包候选<br/>adminRiskAction"]
    Candidate --> Kind{"路径类型"}
    Kind -->|"POST 写命令"| Evidence["必须补 completed-grade write evidence"]
    Evidence --> E1["RBAC + API version"]
    Evidence --> E2["commandId + replay + conflict"]
    Evidence --> E3["expectedVersion + row lock"]
    Evidence --> E4["targetScope / targetIds 归属"]
    Evidence --> E5["governance fact + audit snapshot"]
    Evidence --> E6["错误路径 no-write / rollback"]
    Evidence --> Review["fresh spec/scope review<br/>fresh quality/testing review"]
    Review --> Exec["双 PASS 后写执行版"]
    Exec --> Approval{"用户明确批准后才实现"}
    Approval -->|"否"| Stay["保持 233/144/89"]
    Approval -->|"是"| Target["目标 233/145/88<br/>Content 14 completed / 37 needs_verification"]

当前事实状态

工作区:

/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0

当前分支状态:

dev/1.0.0...origin/dev/1.0.0

当前 HEAD

b0b4b2c test(p1r): 收口 Content Admin Read completed approval 门禁

当前 coverage summary

total=233
completed=144
needsVerification=89
incomplete=0
genericPersistence=0
ssePlaceholder=0

当前 domain 状态:

account  completed=10  needsVerification=23
content  completed=13  needsVerification=38
market   completed=4   needsVerification=28
meta     completed=16  needsVerification=0

目标 operation 当前状态:

adminRiskAction  dedicated  needs_verification  requiresCommandId=true  POST  /admin-api/muse/content/works/{workId}/risk-actions

本审阅版推荐的下一批目标值只是后续获批后的执行目标:

total=233
completed=145
needsVerification=88
incomplete=0
genericPersistence=0
ssePlaceholder=0

content total=51
completed=14
needsVerification=37

推荐候选

operation key Method Path 推荐原因 必补 completed-grade evidence
content:adminRiskAction POST /admin-api/muse/content/works/{workId}/risk-actions admin 治理写命令,当前实现已有 command reserve、expectedVersion、行锁、治理事实和 audit envelope但仍缺 HTTP + 真实 DB completed approval 证据 MockMvc HTTP + 真实 PostgreSQL _test,覆盖 RBAC、API version、commandId、expectedVersion、replay、command conflict、revision conflict、targetScope / targetIds 归属、治理事实、audit snapshot、tenant 隔离和 no-write / rollback

推荐理由:

  • 当前 coverage report 已识别 adminRiskAction.requiresCommandId=true,说明它天然属于写命令切片,不能和 admin read 混在一起。
  • Controller 使用 @PreAuthorize("@ss.hasPermission('muse:content:risk-action')"),权限点与 Admin Read 的 muse:content:query 不同,必须单独证明。
  • Service 使用 @Transactional(rollbackFor = Exception.class),写路径应以事务内证据证明成功写入和失败回滚。
  • Service 先 reserveCommand,再 selectByIdForUpdate 锁定作品行并校验 expectedVersion,适合单独验证幂等、冲突和并发边界。
  • OpenAPI 已声明 RiskActionRequest.required=[commandId, action, reason, expectedVersion],当前不需要为了本切片修改合同。

不纳入本批的 operation

以下 37 个 Content operation 必须继续 dedicated / needs_verification

content:adminListExportTasks
content:adminListImportTasks
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:listPlanningCandidates
content:createPlanningCandidate
content:getPlanningCandidate
content:confirmPlanningCandidate
content:discardPlanningCandidate
content:createStyleCheck
content:getStyleCheckResult

排除原因:

  • adminListImportTasks / adminListExportTasks 是任务读模型,证据应覆盖导入导出任务状态、产物和 work 关联,不应夹入治理写命令。
  • 创建、更新、删除、重排、拆分、合并等 Content 写命令有各自的 revision、source snapshot、outbox 和结构一致性边界,应独立切片。
  • 导入、导出、解析、下载仍缺 FileService、任务产物和真实下载字节闭环 evidence。
  • Meta projection、AI planning candidate、style check、suggestion 等能力依赖 Meta / AI / Knowledge 外部 owner closure 和运行时证据。

已有实现证据

OpenAPI 合同

OpenAPI 当前 operation

POST /admin-api/muse/content/works/{workId}/risk-actions
operationId: adminRiskAction
security: adminBearerAuth

请求体 RiskActionRequest

required: commandId, action, reason, expectedVersion
action enum: warn, restrict, block_content, force_archive, notify_owner
targetScope enum: work, chapter, block
targetIds: chapter 或 block 粒度必填
expectedVersion: 作品当前版本号,用于乐观锁
duration: restrict 动作限制时长

响应体 RiskActionResult

actionId
status: executed | pending_notification
affectedScope
auditLogId

本切片不需要修改 docs/api-contracts/content/openapi.yaml

Controller 入口

AdminContentController.adminRiskAction

POST /muse/content/works/{workId}/risk-actions
@PreAuthorize("@ss.hasPermission('muse:content:risk-action')")
requireApiVersion()
contentAdminService.adminRiskAction(getLoginUserId(), workId, reqVO)

说明:

  • Controller-local mapping 是 /muse/content/...;对外 OpenAPI 路径仍以 /admin-api/muse/... 为准。
  • 执行版必须用当前项目既有 admin MockMvc / Web 测试上下文证明最终 admin API 路径可访问。
  • 权限证据必须来自启用 method security 的 HTTP 请求,不能只靠反射检查注解。

Service 写链路

ContentAdminServiceImpl.adminRiskAction 当前链路:

  1. 校验 expectedVersion 非空。
  2. 用请求体和 workId 构造 scoped request hash。
  3. ContentCommandService.reserveCommand(commandId, "adminRiskAction", operatorUserId, "work", workId, requestHash)
  4. 如果 command 已存在且 hash / target 匹配,返回 resultSnapshot replay。
  5. WorkMapper.selectByIdForUpdate(workId, tenantId) 锁定作品行。
  6. 校验 work.revision == expectedVersion
  7. 校验 action、targetScope、targetIds。
  8. 对 chapter / block target 沿 targetIds -> chapter/block -> work 校验从属关系。
  9. 插入 muse_content_governance_action
  10. 构造 RiskActionResultRespVO
  11. ContentCommandAuditService.recordSucceededWithAudit(...) 记录 command/audit/result snapshot。

关键语义:

  • notify_owner 结果状态为 pending_notification
  • 其它当前支持 action 结果状态为 executed
  • affectedScope 对 work 粒度为 work:{workId}chapter/block 粒度为 {targetScope}:{targetIds}
  • 成功审计 target 仍绑定业务目标 work不能改写成治理动作 id否则 command replay 会被判为跨目标冲突。

Schema 证据来源

V9 当前定义:

muse_content_governance_action
tenant_id
work_id
operator_user_id
action
reason
target_scope default work
target_ids jsonb
expected_version not null
status
audit_log_id
command_id not null
uk_muse_content_governance_action_command unique (tenant_id, command_id)
idx_muse_content_governance_action_work
trg_muse_content_governance_action_updated_at

执行版不需要新增 migration但必须 fresh 证明 _test 库从 V1 到当前版本 clean migrate。当前 Content 已有 V21 event publish outbox因此本切片的真实 schema gate 应至少证明 V1-V21 clean migrate 成功并断言治理动作表、唯一约束、work 索引、JSONB target_idstenant_id / expected_version / command_id 关键列、updated_at trigger 真实存在。

当前测试证据与缺口

已有 focused tests 可作为辅助证据:

  • AdminContentControllerTest.should_requireRbacAnnotationsForAdminContentApis
  • AdminContentControllerTest.should_rejectRiskActionMissingReason
  • AdminContentControllerTest.should_rejectRiskActionMissingExpectedVersionAtValidationLayer
  • AdminContentControllerTest.should_createRiskActionWithCommonResult
  • ContentAdminServiceTest.should_rejectRiskAction_when_expectedVersionMismatch
  • ContentAdminServiceTest.should_rejectRiskAction_when_workMissing
  • ContentAdminServiceTest.should_createRiskActionWithAudit
  • ContentAdminServiceTest.should_rejectRiskAction_when_actionUnsupported
  • ContentAdminServiceTest.should_rejectRiskAction_when_targetScopeUnsupported
  • ContentAdminServiceTest.should_rejectRiskAction_when_chapterTargetIdsBelongToOtherWork
  • ContentAdminServiceTest.should_rejectRiskAction_when_blockTargetIdsBelongToOtherWork
  • ContentAdminServiceTest.should_replayRiskActionCommand

这些测试不能直接等同 completed。缺口包括

  • 没有 HTTP + 真实 PostgreSQL _test 的端到端证据。
  • 缺 method security 真实拒绝路径证据。
  • 缺真实 tenant interceptor 下的 cross tenant 不可见 / no-write 证据。
  • 缺 command table、governance action、Content outbox、work/chapter/block 的全表快照或关键字段 no-write / rollback 证据。
  • 缺 V1-V21 clean migrate 后 muse_content_governance_action / muse_content_command_log schema、唯一键、JSONB、trigger、tenant 关键列证据与 XML 防空跑。
  • 缺 command replay / command conflict 的真实 DB 证据,尤其同 commandId 不同 action、reason、targetScope、targetIds、workId、expectedVersion。
  • 缺并发/行锁语义的可接受代理证据,例如 FOR UPDATE SQL 与真实事务 revision stale / conflict 场景。
  • 缺 P1R scanner/report/gate 在获批后从 233/144/89 推进到 233/145/88 的 RED/GREEN 证据。

必补证据标准

后续执行版必须至少覆盖以下矩阵。

HTTP / 权限 / 版本

  • X-API-Version 的成功请求。
  • 缺或不支持 X-API-Version 的统一错误响应。
  • muse:content:risk-action 权限时允许进入 service。
  • muse:content:risk-action 权限时 403且不写 command、governance action、outbox 或业务表。
  • 登录 operator user id 必须进入 operator_user_id 和 command/audit 事实。
  • command/audit snapshot 必须显式断言 operationId=adminRiskActionside=admintargetType=worktargetId=workIdstatus=succeededreasonrequestSummary.actionrequestSummary.targetScoperequestSummary.targetIdsrequestSummary.expectedVersionresult.actionId/status/affectedScope/auditLogId

成功写路径

  • work 粒度 happy path写入 1 条 governance action写入 1 条 command/audit envelope返回 actionId/status/affectedScope/auditLogId。
  • chapter 粒度 happy pathtargetIds 必须属于 URL work返回 chapter affected scope。
  • block 粒度 happy pathtargetIds 必须沿 block -> chapter -> work 归属到 URL work。
  • notify_owner 返回 pending_notification
  • block_content / warn / restrict / force_archive 返回 executed
  • 结果 snapshot 可被 replay 解析,重复同 commandId / 同请求返回同一结果,不重复插入治理事实。

command / revision / target 负路径

  • commandId validation 失败,不写任何事实。
  • expectedVersion validation 失败,不写任何事实。
  • expectedVersion 与 work revision 不一致,返回 revision conflict不写治理事实和 succeeded audit。
  • 同 commandId 不同 action、reason、targetScope、targetIds、workId 或 expectedVersion 返回 command conflict不写第二条治理事实。
  • 同 commandId 不同 workId 返回 command conflict 或目标冲突,不泄露目标数据。
  • unsupported action 返回统一错误并 no-write。
  • unsupported targetScope 返回统一错误并 no-write。
  • chapter targetIds 为空、含 null、missing、属于其它 work、属于其它 tenant均拒绝并 no-write。
  • block targetIds 为空、含 null、missing、属于其它 work、章节链路不匹配、属于其它 tenant均拒绝并 no-write。
  • missing work / cross tenant work 返回不可见或 not found 语义,不写任何事实。

no-write / rollback

错误路径必须对以下表做全表快照或关键字段快照,不只比较行数:

muse_content_work
muse_content_chapter
muse_content_block
muse_content_governance_action
muse_content_command_log
muse_content_event_publish_outbox

说明:

  • 成功写路径允许 governance action 与 command/audit 变化。
  • 错误路径必须证明上述表没有 INSERT / DELETE / UPDATE 污染。
  • 如果测试自身需要 seed 或清理,必须在 no-write baseline 采样之前完成。

Flyway / schema

执行版必须 fresh 证明:

  • _test 库只使用测试库名,禁止连接非 _test
  • 密码只从环境变量读取,禁止通过 JVM system property 或 JDBC query 传入。
  • V1-V21 clean migrate 成功。
  • muse_content_governance_action 表、关键列、JSONB target_ids、unique command 约束、work 索引和 updated_at trigger 存在。
  • duplicate (tenant_id, command_id) 被数据库唯一约束拒绝。

风险和取舍

  1. adminRiskAction 不是 Admin Read 的延伸证据。

    Admin Read 已证明治理事实可读它不能证明治理事实写入、command replay、expectedVersion、行锁和 audit snapshot。

  2. selectByIdForUpdate 是本切片的核心边界。

    执行版必须证明 expectedVersion 校验发生在同一事务的锁定作品行之后;否则 completed 证据不足以支撑并发写路径。

  3. targetIds 归属不能只查目标 id 存在。

    chapter 必须属于 URL workblock 必须同时满足 block.workId 与 block.chapterId 对应 chapter.workId 都属于 URL work。

  4. command replay 不能只证明不报错。

    必须证明 replay 返回原 resultSnapshot且没有重复插入治理事实、command/audit 或 outbox。

  5. 不应新增 Content audit 表。

    当前 Content audit snapshot 保存在 muse_content_command_log.result_snapshot / command audit envelope 中。执行版不得为了测试方便新增表或修改 schema。

  6. 不把生成 DTO 当成唯一合同证据。

    OpenAPI 已声明 expectedVersion required服务端 RiskActionReqVO 也有 @NotNull validation。后续执行版如果引用生成物或客户端 DTO必须说明其新鲜度和同步关系completed 证据应以 OpenAPI、服务端 VO validation、HTTP 请求和真实 DB 结果闭合。

后续执行版要求

后续执行版必须包含:

  1. 精确目标 operation 清单:只包括 content:adminRiskAction
  2. operation-level approval不把 content 加入 domain allowlist。
  3. 实施前 RED旧 report 下 P1R coverage gate 必须因 adminRiskAction 尚未 completed 而失败。
  4. HTTP + real PostgreSQL _test gate新增独立 P1rContentAdminRiskActionCompletedApprovalIT 或等价命名。
  5. Web/MyBatis/DataSource 测试上下文:必须启用真实 tenant interceptor、真实 mapper、真实 service、真实 controller 和 method security。
  6. RBAC / API version / CommonResult 错误响应证据。
  7. happy / replay / command conflict / revision conflict / validation / missing / cross tenant / target mismatch / no-write matrix。
  8. focused tests运行 ContentAdminServiceTestAdminContentControllerTest 并做 XML 防空跑。
  9. P1R mixed gates同步所有读取全局 summary、Content 状态或非目标 domain 防回退的 gate。
  10. scanner/report 更新策略:只在用户批准后追加 1 个 content:adminRiskAction operation-level allowlist。
  11. protected diffOpenAPI、Content main/java、SQL migration 默认不得修改。
  12. .agentdocs/memorys 留痕。
  13. rollback撤回 scanner operation key、coverage report、P1R gate 目标值和新增 _test,恢复 adminRiskActionneeds_verification

审批前置条件

进入执行版前需要 fresh review 双 PASS

  • fresh spec/scope review确认只覆盖 content:adminRiskAction,不夹带 admin import/export task、Content CRUD 写命令、Content domain-level completed 或其它 domain。
  • fresh quality/feasibility/testing review确认 RBAC、tenant 拦截、command replay、expectedVersion、targetScope / targetIds、no-write / rollback、schema gate、XML 防空跑和 mixed gate 同步可落地。

执行版双 PASS 后,仍必须由用户明确批准以下事项后才允许实现:

  1. 只审批 content:adminRiskAction
  2. 继续 operation-level approval不把 content 加入 domain allowlist。
  3. 允许按执行版修改 scanner、coverage report、P1R gate tests、新增 HTTP+DB _test 和 memory。
  4. 允许同步 P1R mixed gate 的 summary / Content / 非目标域防回退断言。

本审阅版验收标准

  • 候选 operation 和目标值可由当前 report 机械推导。
  • 非目标 37 个 Content operation 明确保持 needs_verification
  • adminRiskAction 与 Admin Read、admin import/export task、Content CRUD 写命令的边界清晰。
  • evidence 标准包含 RBAC、tenant 隔离、commandId、expectedVersion、行锁、target 归属、governance action、audit snapshot、replay、conflict、no-write / rollback 和 XML 防空跑。
  • 未修改 OpenAPI、scanner、coverage report、业务实现、SQL migration 或 P1R gate。