418 lines
19 KiB
Markdown
418 lines
19 KiB
Markdown
# P1R Content Admin RiskAction Completed Approval 审阅版
|
||
|
||
日期:2026-06-13
|
||
|
||
## 结论
|
||
|
||
推荐 Content 下一批只推进 1 个管理后台治理写命令的 operation-level completed approval:
|
||
|
||
```text
|
||
content:adminRiskAction
|
||
```
|
||
|
||
不推荐把 Content 剩余 38 个 operation 一次性推进 completed,也不推荐把 `content` 加入 domain-level completed allowlist。
|
||
|
||
`adminRiskAction` 与刚收口的 Admin Read 3 个只读 operation 相邻,但它不是读模型。它是治理写路径,必须证明 `commandId`、`expectedVersion`、行锁、目标归属、治理事实写入、command/audit envelope、幂等 replay、冲突拒绝、RBAC、API version、tenant 隔离和事务回滚全部闭合后,才允许进入 completed approval。
|
||
|
||
本审阅版只冻结候选范围、证据标准、风险边界和后续执行版要求。不修改 OpenAPI,不修改 scanner,不修改 coverage report,不修改业务实现,不推进任何 operation completed。
|
||
|
||
```mermaid
|
||
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"]
|
||
```
|
||
|
||
## 当前事实状态
|
||
|
||
工作区:
|
||
|
||
```text
|
||
/Users/qingse/.config/superpowers/worktrees/oh-my-muse/dev-1.0.0
|
||
```
|
||
|
||
当前分支状态:
|
||
|
||
```text
|
||
dev/1.0.0...origin/dev/1.0.0
|
||
```
|
||
|
||
当前 HEAD:
|
||
|
||
```text
|
||
b0b4b2c test(p1r): 收口 Content Admin Read completed approval 门禁
|
||
```
|
||
|
||
当前 coverage summary:
|
||
|
||
```text
|
||
total=233
|
||
completed=144
|
||
needsVerification=89
|
||
incomplete=0
|
||
genericPersistence=0
|
||
ssePlaceholder=0
|
||
```
|
||
|
||
当前 domain 状态:
|
||
|
||
```text
|
||
account completed=10 needsVerification=23
|
||
content completed=13 needsVerification=38
|
||
market completed=4 needsVerification=28
|
||
meta completed=16 needsVerification=0
|
||
```
|
||
|
||
目标 operation 当前状态:
|
||
|
||
```text
|
||
adminRiskAction dedicated needs_verification requiresCommandId=true POST /admin-api/muse/content/works/{workId}/risk-actions
|
||
```
|
||
|
||
本审阅版推荐的下一批目标值只是后续获批后的执行目标:
|
||
|
||
```text
|
||
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`:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
POST /admin-api/muse/content/works/{workId}/risk-actions
|
||
operationId: adminRiskAction
|
||
security: adminBearerAuth
|
||
```
|
||
|
||
请求体 `RiskActionRequest`:
|
||
|
||
```text
|
||
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`:
|
||
|
||
```text
|
||
actionId
|
||
status: executed | pending_notification
|
||
affectedScope
|
||
auditLogId
|
||
```
|
||
|
||
本切片不需要修改 `docs/api-contracts/content/openapi.yaml`。
|
||
|
||
### Controller 入口
|
||
|
||
`AdminContentController.adminRiskAction`:
|
||
|
||
```text
|
||
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 当前定义:
|
||
|
||
```text
|
||
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_ids`、`tenant_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=adminRiskAction`、`side=admin`、`targetType=work`、`targetId=workId`、`status=succeeded`、`reason`、`requestSummary.action`、`requestSummary.targetScope`、`requestSummary.targetIds`、`requestSummary.expectedVersion` 和 `result.actionId/status/affectedScope/auditLogId`。
|
||
|
||
### 成功写路径
|
||
|
||
- work 粒度 happy path:写入 1 条 governance action,写入 1 条 command/audit envelope,返回 actionId/status/affectedScope/auditLogId。
|
||
- chapter 粒度 happy path:targetIds 必须属于 URL work,返回 chapter affected scope。
|
||
- block 粒度 happy path:targetIds 必须沿 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
|
||
|
||
错误路径必须对以下表做全表快照或关键字段快照,不只比较行数:
|
||
|
||
```text
|
||
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 work;block 必须同时满足 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:运行 `ContentAdminServiceTest`、`AdminContentControllerTest` 并做 XML 防空跑。
|
||
9. P1R mixed gates:同步所有读取全局 summary、Content 状态或非目标 domain 防回退的 gate。
|
||
10. scanner/report 更新策略:只在用户批准后追加 1 个 `content:adminRiskAction` operation-level allowlist。
|
||
11. protected diff:OpenAPI、Content main/java、SQL migration 默认不得修改。
|
||
12. `.agent` 和 `docs/memorys` 留痕。
|
||
13. rollback:撤回 scanner operation key、coverage report、P1R gate 目标值和新增 `_test`,恢复 `adminRiskAction` 为 `needs_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。
|