Docmost 第二批实施计划 — 不可变快照、评审与版本发布
日期：2026-10-18；承接已批准 D03–D06，保留第一批 bf18b5a。
Goal：正文 A 提交评审后，即使草稿变成 B，批准与发布仍只引用 A。
Architecture：新增 document-review 模块；从数据库已保存的页面复制快照，使用内容摘要确认保存，评审与发布不修改草稿。复用现有身份和权限，不修改协作引擎，不依赖企业模块。
Tech Stack：现有 React/Mantine、NestJS、Kysely/PostgreSQL、Yjs/Hocuspocus；Node26/pnpm11。

范围与保存契约
客户端读取当前协作Y.Doc正文（不能直接用含默认attrs的editor.getJSON）和标题；服务端revision接口读取已持久化正文/标题，返回contentHash及revision。只有与客户端期望一致且协作连接正常时允许提交；等待最多60秒，未保存/仍在变化明确提示，不自动提交旧正文。
revision使用稳定排序JSON的SHA256，绑定标题及正文。submit事务锁定pages行后重新计算并比对expectedRevision，成功才复制JSON、纯文本、标题、hash、sourceUpdatedAt及来源空间。浏览器不提交快照正文。
页面自动历史有延迟，不参与本批快照生成。快照内容数据库禁止UPDATE；后续决定/发布引用snapshotId，禁止复制当前草稿替换它。
静态快照预览仅渲染固化JSON/文字，不执行富文本中的动态嵌入或页面转引，不开放附件匿名访问。

HTTP契约（均POST，前缀/api/document-reviews，复用JwtAuthGuard）
revision {pageId} -> {pageId,title,contentHash,revision,sourceUpdatedAt}
reviewers {pageId} -> {items:[{id,name}]}，仅返回当前有写权限、同工作区、非本人候选人。
list {pageId} -> {items:[ReviewSummary],releases:[ReleaseSummary],canSubmit,canPublish}
info {reviewId} -> {review,snapshot,decision,release,permissions:{canDecide,canPublish}}
submit {pageId,reviewerId,expectedRevision,requestId(UUID)} -> {review,snapshot,replayed}
decision {reviewId,decision:approved|rejected,comment,expectedVersion,requestId} -> {review,decision,replayed}
publish {reviewId,expectedVersion,requestId} -> {release,snapshot,replayed}
参数严格白名单，未知/跨工作区/删除/迁移空间资源返回404，权限不足403，版本/请求冲突409，输入错误400。

数据与不变量
review_snapshots保存不可变正文；document_reviews绑定唯一snapshot及submitter/reviewer，status pending→approved/rejected、version递增；review_decisions每申请仅一条；document_releases每申请最多一条；review_action_requests按工作区/用户/操作/请求UUID唯一并保存输入digest。
权限每次重新查询，使用第一批fresh/trx选项。提交人不能自批；仅指定的当前有写权限评审人决定；有页面写权限者可发布已批准快照。重复操作先检查当前访问权再返回原结果；同key不同输入409。并发决定行锁/CAS仅一胜；发布不替换草稿。
软删除/来源空间变更后不能读取或操作历史快照；硬删除按外键清理业务内容，幂等请求留下墓碑，防止误重建。成员撤权后不能继续审批或借重试读快照。

任务与文件边界
1. 服务端：apps/server/src/core/document-review/*、database/migrations/20261018T120000-document-reviews.ts、database/types/document-review.types.ts，注册core.module/db.interface。
   先写hash/DTO/流程失败测试；测试原行为不满足后实现。覆盖A→B、同key重放/冲突、CAS、自己审批、撤权、移动删除、快照防更新。读取权限扩展也要fresh/trx，避免连接池耗尽。
2. 客户端：apps/client/src/features/document-review/*；page-editor/atoms暴露当前页面协作正文读函数；page-header-menu新增“评审与发布”。
   先测等待保存/页面切换/断线阻断、失败重试保持requestId、冲突重新加载和只读快照；实现选择评审人、提交、查看、通过/退回理由、发布已批准版及当前草稿差异。
3. 真实验收：work/docmost02/runtime-check.mjs，独立合成夹具、实际HTTP/DB/双WS。验证提交快照A→草稿B→批准→发布A；拒绝B替换；并发决定/发布、重试、立即撤权、跨空间/删除；应用重启后快照/状态保持。验证第一批46项真实流程无回归。
4. 独立规格及质量审查；修复后构建和相关测试，原四个上游DI脚手架失败独立记录，不称全库全绿。
5. 本地浏览器双角色操作提交→退回→修改→再提交→批准→发布；截图、报告、对应源码归档；原目录文件hash和Git状态保持。

执行顺序：契约确认→测试先行的前后端实现→真实验收→审查修复→构建/回归→本地提交归档。既有批准足以执行，本批不改原维修/门户/ERP，不公网发布。
