# 方案计划文档管理系统功能契约

> 契约版本：1.6
> 状态：已确认，LibreOffice 离线文档转换与 PDF 预览基线
> 适用范围：前端、后端、数据库、联调、测试与验收

## 1. 契约效力

本文件是第一阶段业务功能的唯一执行契约。Codex 会话开始工作前必须完整读取本文件，不得只读取摘要。

约束优先级如下：

1. 用户在统领会话中最新、明确确认的变更；
2. 本功能契约；
3. `DMS_API_CONTRACT.md` 中与接口表达有关的约定；
4. `FUNCTION_AND_API_SPECIFICATION.md`；
5. `CODEX_BACKEND_PERSISTENCE_GUIDE.md`；
6. 当前代码、界面模拟数据和历史对话。

低优先级材料与本契约冲突时，以本契约为准。执行会话不得自行选择旧版本规则，不得未经确认扩大范围。发现本文件与接口契约互相冲突时必须停止相关实现并提交统领会话裁决。

`FUNCTION_AND_API_SPECIFICATION.md` 和 `CODEX_BACKEND_PERSISTENCE_GUIDE.md` 仍可作为背景、字段设计和实施建议的参考，但不能单独作为最终验收依据。

## 2. 第一阶段目标

将当前前端模拟界面建设为可持久化、可鉴权、可审计的方案计划文档管理系统，形成以下最小业务闭环：

1. 用户登录并恢复登录状态；
2. 查询组织、人员和方案分类；
3. 浏览、搜索和管理主案、子方案；
4. 管理全员共享附件库；
5. 将共享附件挂载到一个或多个主案；
6. 配置主案访问权限，子方案动态继承；
7. 上传、预览或下载真实文件；
8. 逻辑删除业务数据；
9. 查询真实审计日志和统计数据；
10. MySQL 保存结构化数据，`backend/dms-storage/` 保存文件。
11. 管理员查询文档回收站并恢复单条主案、子方案或共享附件。

## 3. 明确不在第一阶段实施

- 前端视觉风格重构；
- Milvus、向量化和语义检索；
- 现有 AI 抽取、标注、修复模块改造；
- 电子签章；
- 审批工作流；
- 在线 Office 编辑；
- Office 转 PDF；
- 国产数据库和国产操作系统适配；
- 动态角色与菜单配置平台；
- 复杂文档历史版本；
- 匿名访问；
- 强制删除已挂载附件；
- 子方案独立权限配置；
- 物理删除和自动清理逻辑删除文件。
- 分类、用户或组织恢复；
- 批量恢复；
- ACL或挂载关系的独立恢复接口；
- 将逻辑删除文件移动到`recycle`目录；
- 恢复时强制覆盖冲突数据。

## 4. 角色与访问入口

| 角色 | 代码 | 功能边界 |
|---|---|---|
| 普通用户 | `USER` | 浏览有权访问的已发布方案；查看、下载共享附件 |
| 管理员 | `ADMIN` | 浏览文档、进入后台管理及日志审计；管理分类、方案、附件、挂载和权限 |

登录成功后，后端返回用户可访问模块 `allowedModules`。前端不得仅根据本地角色字符串猜测导航入口。

固定模块代码：

- `DOCUMENT_BROWSER`
- `BACKEND_MANAGEMENT`
- `AUDIT_LOG`

## 5. 核心业务对象

### 5.1 主案

- 文档类型为 `MAIN`；
- 是顶层方案文档；
- 可以拥有多个直接子方案；
- 可以挂载多个共享附件；
- 必须属于一个有效方案分类；
- 可以配置可见范围和组织、人员允许权限。

### 5.2 子方案

- 文档类型为 `SUB_PLAN`；
- 必须且只能直接属于一个主案；
- 第一阶段不允许继续嵌套；
- 必须属于一个有效方案分类；
- 动态继承所属主案的可见范围和 ACL；
- 自身密级仍独立参与密级判断；
- 不允许独立配置权限。

### 5.3 共享附件

- 文档类型为 `ATTACHMENT`；
- 独立于主案存在；
- 文件和元数据只保存一份；
- 可以同时挂载到多个主案；
- 不进入方案分类，`categoryId` 必须为空；
- 不配置组织或人员 ACL；
- 可见范围固定为 `ALL_AUTHENTICATED`；
- 密级固定为 `PUBLIC`，前端不得修改；
- 所有状态正常的已登录用户均可查看和下载；
- 只有管理员可以上传、编辑、删除和管理挂载。

### 5.4 挂载关系

- 挂载只建立引用关系，不复制文件；
- 同一主案不能重复挂载同一附件；
- 解除挂载只逻辑删除关系，不删除附件；
- 删除主案不能删除共享附件；
- 已挂载附件不能直接删除。

## 6. 文档状态与展示规则

固定状态：

- `DRAFT`：草稿；
- `PUBLISHED`：已发布；
- `ARCHIVED`：已归档。

规则：

1. 普通用户只可查询和读取 `PUBLISHED` 文档；
2. 管理员后台可以查询全部状态；
3. 普通用户不因登录而自动获得未授权方案正文访问权；
4. 逻辑删除数据不出现在正常查询中；
5. 前端按钮由后端返回的 `allowedActions` 决定；
6. 前端隐藏按钮不能替代后端鉴权。

## 7. 密级规则

| 代码 | 名称 | 比较值 |
|---|---|---:|
| `PUBLIC` | 公开 | 10 |
| `INTERNAL` | 内部 | 20 |
| `SECRET` | 秘密 | 30 |
| `CONFIDENTIAL` | 机密 | 40 |
| `TOP_SECRET` | 绝密 | 50 |

访问方案时，用户密级比较值必须大于或等于文档密级比较值。判断顺序为：

```text
登录状态
→ 用户有效状态
→ 文档有效状态和发布状态
→ 密级
→ 管理员业务特权
→ 可见范围与 ACL
→ 具体操作权限
```

共享附件固定为 `PUBLIC`，因此对全部有效登录用户共享。

## 8. 可见范围与 ACL

固定可见范围：

- `ALL_AUTHENTICATED`：通过前置检查的所有登录用户；
- `ORGANIZATION`：权限集合只能包含组织主体；
- `CUSTOM`：权限集合可以包含组织和人员主体。

第一阶段 ACL 只表达“允许”，不表达显式拒绝。

组织权限覆盖该组织及其有效下级组织。人员允许记录与组织允许记录取并集，不存在“人员拒绝覆盖组织允许”的规则。

主案权限包含五个独立动作：

- `VIEW`
- `DOWNLOAD`
- `EDIT`
- `CONFIG_PERMISSION`
- `DELETE`

子方案动态使用主案 ACL，但仍按子方案自身密级和状态执行前置检查。

## 9. 前端页面功能契约

### 9.1 登录页

- 输入用户名、密码；
- “记住密码”改为“保持登录”语义；
- 不得保存明文密码；
- 保持登录时将 Token 放入 `localStorage`，否则放入 `sessionStorage`；
- 登录失败展示后端业务错误；
- Token 失效时清理会话并返回登录页。

### 9.2 文档浏览页

左侧：

- 展示方案分类树；
- 支持展开和收起；
- 分类搜索只搜索分类名称；
- 点击分类按 `categoryId` 筛选文档。

右侧页签：

- 主案列表：只显示 `MAIN`；
- 全部方案：显示 `MAIN` 和 `SUB_PLAN`，不显示附件；
- 最近更新：显示按更新时间倒序排列的主案和子方案。

主搜索框：

- 搜索文档名称、概述、标签和普通文本检索字段；
- 使用 MySQL 普通检索，不调用向量服务；
- 前端可高亮已返回文本中的关键词，但不得伪造命中内容。

文档操作：

- 查看详情；
- 预览；
- 下载；
- 操作是否显示由 `allowedActions` 决定。

选中主案后，下方加载：

- 直属子方案；
- 已挂载共享附件。

选中子方案时，下方不加载主案关联管理区。

### 9.3 后台管理页

统计卡片固定口径：

- 方案文档：有效主案数加有效子方案数；
- 主案数；
- 子方案数；
- 共享附件数；
- 今日查看人数：当天产生成功查看行为的去重用户数。

分类管理：

- 新增顶级或子级分类；
- 编辑名称、类型和排序；
- 存在有效子分类或有效方案文档时拒绝删除；
- 不递归删除，不自动迁移文档。

方案管理：

- 上传主案或子方案；
- 单文件上传在同一界面选择“主案”或“子案”；选择子案后必须选择所属主案，分类由该主案自动继承；
- 有效主案名称全局唯一；有效子案名称在同一所属主案内唯一；不同主案允许存在同名子案；
- 上传发生名称冲突时先返回冲突信息，由用户明确确认后覆盖；覆盖保留原文档ID、权限、挂载及父子关系，仅替换文件与本次填写的元数据；
- 编辑元数据；
- 查看、下载；
- 配置主案权限；
- 逻辑删除；
- 查询文档回收站并单条恢复；
- 批量导入按文件分别返回结果。

共享附件：

- 独立附件库；
- 上传单个附件；
- 拖入或选择多个文件批量导入；
- 编辑附件名称、类型、概述和标签；
- 查看挂载到哪些主案；
- 将一个或多个附件挂载到当前主案；
- 解除当前主案与附件的挂载；
- 已挂载附件删除时返回冲突，不自动解除。

### 9.4 日志审计页

- 仅 `ADMIN` 可进入，管理员具有 `AUDIT_LOG` 模块；
- 支持关键词、时间、用户、组织、动作、结果和目标筛选；
- 支持分页；
- 展示操作趋势、动作分布和活跃用户统计；
- 日志数据来自真实审计表，不使用前端模拟统计。

## 10. 上传、文件和批量处理

允许类型：

- `.doc`
- `.docx`
- `.pdf`
- `.xls`
- `.xlsx`

规则：

1. 同时校验扩展名、文件头或容器结构；
2. 后端限制单文件大小、批量文件数和请求总大小；
3. 原始文件名只用于展示，不参与磁盘路径拼接；
4. 磁盘文件名使用 UUID；
5. 计算并保存 SHA-256，但相同哈希不阻止作为不同业务文档上传；
6. 数据库只保存存储根目录下的相对路径；
7. 文件必须经过鉴权接口读取，存储目录不能作为静态目录暴露；
8. 批量导入每个文件使用独立业务事务；
9. 一个文件失败不回滚其他成功文件；
10. 返回逐文件结果。

预览规则：

- PDF 和 DOCX 支持通过同一预览接口原样返回源二进制；
- DOC、XLS、XLSX 返回 `415 PREVIEW_UNAVAILABLE`；
- 不调用 Office、WPS 或 LibreOffice，不生成预览衍生文件；
- 不得把原始 Office 文件伪装成其他媒体类型。

## 11. 删除规则

所有普通业务删除均为逻辑删除。

### 11.1 分类

存在有效子分类或有效主案、子方案时，返回冲突。

### 11.2 主案

- 存在有效子方案时，返回冲突；
- 删除主案时逻辑删除其有效权限和附件挂载关系；
- 不删除共享附件；
- 原始文件不立即物理删除。

### 11.3 子方案

- 只逻辑删除子方案及相关业务数据；
- 更新主案子方案计数；
- 不影响主案和共享附件。

### 11.4 共享附件

- 存在有效挂载时返回 `ATTACHMENT_IN_USE`；
- 不提供强制解除并删除；
- 未挂载时允许逻辑删除；
- 原始文件不立即物理删除。

### 11.5 文档回收站与恢复

仅管理员可以查询回收站及执行恢复。正式路径为：

```text
GET  /api/v1/recycle-bin/documents
POST /api/v1/recycle-bin/documents/{id}/restore
```

旧路径 `/api/v1/documents/{id}/restore` 永久废止，不得实现重定向、别名或兼容调用。

恢复范围只包含 `MAIN`、`SUB_PLAN` 和 `ATTACHMENT`。不支持分类、用户或组织恢复，不支持批量恢复、物理删除、自动清理、独立恢复ACL或挂载关系，也不移动、复制或删除原始文件。

通用恢复规则：

1. 记录必须存在、当前已逻辑删除且 `deleted_at` 非空；
2. 请求必须携带当前删除记录的 `rowVersion`，冲突时不得静默覆盖；
3. 原文件路径必须是存储根目录内的安全相对路径；
4. 文件必须存在，大小、SHA-256、扩展名、文件头或Office容器结构必须与记录一致；
5. 文件检查采用事务前完整校验、事务内提交前状态复核的两阶段方案；
6. 数据库事务不能锁定磁盘文件，当前以应用独占管理UUID正式文件为前提；
7. 恢复文档、关系、冗余计数和成功审计必须原子提交；
8. 任一校验失败时整体回滚，文档继续保持逻辑删除；
9. 文档名称、原文件名、分类下名称、同一主案下子方案名称和文件哈希允许重复；
10. 恢复响应使用纯序列化，不增加查看次数，不产生 `VIEW_DOCUMENT` 审计。

主案采用关系恢复方案B：只恢复与本次主案删除具有相同文档ID、`deletedAt`和`updatedBy`的级联删除ACL及挂载关系。更早的历史删除关系不恢复。无效或禁用权限主体、已删除或不存在的附件跳过并计数；已存在有效同主体ACL或同附件挂载时整体冲突回滚。

- `ORGANIZATION`恢复后必须至少有一个有效且 `canView=true` 的组织权限；
- `CUSTOM`允许空权限集合，普通用户默认无权访问；
- `ALL_AUTHENTICATED`权限集合必须为空；
- 恢复后重新统计分类文档数、子方案数和挂载附件数。

子方案恢复要求父主案与根主案指向同一有效 `MAIN`，不恢复独立ACL，恢复后动态继承主案当前最新权限并重新统计主案子方案数。

共享附件恢复后继续固定为 `PUBLIC`、`ALL_AUTHENTICATED`，分类、父文档和根文档均为空；不创建ACL，不恢复任何历史挂载。若异常存在指向已删除附件的有效挂载关系，恢复必须返回关系冲突。

恢复新增错误码固定为：

```text
DOCUMENT_NOT_DELETED
FILE_INTEGRITY_MISMATCH
FILE_PATH_INVALID
RESTORE_FILE_CHANGED
RESTORE_CATEGORY_INVALID
RESTORE_PARENT_INVALID
RESTORE_PERMISSION_INVALID
RESTORE_RELATION_CONFLICT
```

具体HTTP状态、message和details结构以 `DMS_API_CONTRACT.md` 1.1为准。错误不得泄露绝对路径、哈希、SQL、数据库连接信息或内部异常堆栈。

## 12. 审计规则

至少记录：

- 登录成功、登录失败和退出；
- 查看与下载；
- 上传与批量导入；
- 编辑与删除；
- 分类新增、编辑和删除；
- 权限变更；
- 挂载和解除挂载。
- 文档恢复成功。

文档恢复使用审计动作 `RESTORE_DOCUMENT`。恢复成功审计与恢复事务原子提交；最小范围内恢复失败不写业务审计，只写安全应用日志和统一错误响应。

`sys_audit_log` 是追加写表：

- 不允许普通业务修改、逻辑删除或物理删除；
- 只要求 `created_at`，不要求 `updated_at`、`deleted_at`、`is_deleted`、`row_version`；
- 保存用户、组织和目标名称快照，不依赖当前名称；
- 修改类业务的审计与业务写入处于同一事务，审计失败则业务回滚；
- 查看、下载、登录失败使用独立短事务，审计失败记录系统错误，但第一阶段不阻断已通过鉴权的文件读取。

## 13. 登录凭证

- 第一阶段采用 JWT Bearer Token；
- `sys_user` 保存 `auth_version`；
- Token 包含签发时的 `authVersion`；
- 每次鉴权验证用户有效状态和 `auth_version`；
- 退出时递增 `auth_version`，使该用户全部已签发 Token 失效；
- 第一阶段不增加会话表，不提供刷新令牌；
- 接受“一个终端退出导致全部终端退出”的行为。

## 14. 数据与存储边界

- MySQL 8.x 是结构化业务数据的事实来源；
- `backend/dms-storage/` 是文件内容的事实来源；
- 第一阶段只使用七张核心表；
- `sys_user` 必须增加 `auth_version`；
- `doc_document` 必须增加可空的 `attachment_type`；
- 允许名称、路径和计数冗余，由后端维护；
- 普通业务表采用逻辑删除和 `row_version` 乐观锁；
- 审计表是上述通用字段规则的明确例外；
- 事务隔离级别统一为 `READ COMMITTED`；
- 禁止数据库级联删除；
- 现有 `backend/src/**` AI 模块不得被新 DMS 业务直接调用。

逻辑删除关系的有效唯一性采用 MySQL 生成列：

```text
active_marker = IF(is_deleted = 0, 1, NULL)
```

至少建立以下唯一约束：

```text
doc_attachment_binding:
UNIQUE(main_document_id, attachment_document_id, active_marker)

doc_permission:
UNIQUE(document_id, subject_type, subject_id, active_marker)
```

这允许保留多条历史删除记录，同时保证同一业务关系只有一条有效记录。

B8实现必须新增迁移 `0002_add_restore_audit_action`，不得修改 `0001_initial_schema`。该迁移只扩展 `sys_audit_log.action_type` CHECK以加入 `RESTORE_DOCUMENT`，并在 `doc_document` 增加回收站索引：

```text
INDEX(is_deleted, deleted_at, id)
```

迁移不得新增表或修改历史审计，必须提供安全的upgrade和downgrade。


## 15. LibreOffice 离线文档转换与 PDF 预览

统一预览接口 `GET /api/v1/documents/{id}/preview` 行为：

- PDF：直接返回原始 PDF 二进制，Content-Type 为 `application/pdf`；
- DOC、DOCX：调用 LibreOffice 离线转换为 PDF 后返回，Content-Type 为 `application/pdf`；
- 转换失败时返回正式错误码，不得返回原始 DOC/DOCX 冒充 PDF；
- 转换生成的 PDF 为预览衍生缓存，不是档案原件，原始 DOC/DOCX 仍保留并可通过下载接口获取；
- XLS、XLSX 继续返回 `415 PREVIEW_UNAVAILABLE`；
- 不调用 Microsoft Office、WPS 或任何在线云服务。

转换错误码：

- `PREVIEW_CONVERTER_UNAVAILABLE`：转换器未启用或 LibreOffice 可执行文件不存在/不可执行；
- `PREVIEW_CONVERSION_TIMEOUT`：转换超出配置时间；
- `PREVIEW_CONVERSION_FAILED`：LibreOffice 退出码非零、未生成输出或输出非有效 PDF。

缓存规则：

- 预览缓存目录为 `backend/dms-storage/preview/`；
- 缓存键基于原文件 SHA-256，例如 `preview/{fileHash}.pdf`，可通过前两位哈希分层；
- 缓存为按需生成，首次转换后原子写入；再次预览直接命中缓存，不重复启动 LibreOffice；
- 旧缓存可在后续维护中由显式回填/清理命令处理，业务接口不在事务中扫描或物理清理；
- 文件覆盖后原文件哈希变化，新预览自动使用新缓存路径。

DOC 正文检索规则：

- DOCX 继续使用原生 DOCX 解析提取正文，不改为先转 PDF 再提取；
- DOC 上传时由 LibreOffice 转换为 PDF，再由现有 PDF 文字提取能力提取正文；
- DOC 转换失败时 `content_extract_status` 标记为 `FAILED`，不得写入空字符串冒充成功；
- 文档名称、概述、标签与正文共同写入 `search_text`。

## 16. 完成判定

只有同时满足以下条件，第一阶段才算完成：

1. 前端主要页面不再依赖内置业务模拟数据；
2. 登录、分类、方案、附件、挂载、权限、文件和日志形成真实闭环；
3. MySQL 七张表、索引、外键和逻辑删除规则可验证；
4. 文件真实写入约定目录，且路径安全；
5. 前后端请求和响应符合 `DMS_API_CONTRACT.md`；
6. 所有敏感读取经过后端鉴权；
7. 关键操作生成真实审计日志；
8. 失败补偿、并发冲突和逐文件批量结果有自动化测试；
9. 现有 AI 接口和模块行为未被无关修改；
10. 任何契约偏差均已经统领会话书面确认。
11. 回收站和三类文档恢复符合文件完整性、关系恢复、乐观锁和审计规则。
12. 所有正式界面枚举的中文展示标签由后端统一UI字典配置提供，前端不重复维护同义映射。
13. 主案、全部方案、最近更新、直属子方案和主案已挂载附件均支持基于文档自身 `updated_at` 的统一UTC时间范围检索。
14. DOC、DOCX 通过 LibreOffice 离线转换为 PDF 预览；PDF 直接预览；XLS、XLSX 不支持在线预览。
15. DOC 可通过 LibreOffice 转换后回填 PDF 正文至 `content_text`；DOCX 仍使用原生 DOCX 解析提取正文。
16. 预览缓存以原文件 SHA-256 为键，位于 `backend/dms-storage/preview/`；覆盖上传后使用新文件哈希生成新缓存。

## 15A. 统一UI展示字典

1. API和数据库中的枚举代码、存储值及业务语义保持不变；中文标签只用于界面展示。
2. 后端配置文件是展示标签的唯一来源。默认文件为 `backend/dms/resources/ui-dictionaries.zh-CN.json`，部署时可通过 `DMS_UI_DICTIONARY_CONFIG_PATH` 指向外部UTF-8 JSON文件。
3. 配置必须完整覆盖 `roleCodes`、`allowedModules`、`documentTypes`、`documentStatuses`、`securityLevels`、`visibilityTypes`、`attachmentTypes`、`subjectTypes`、`allowedActions`、`categoryTypes`、`enabledStatuses`、`auditResults`、`auditActions`、`auditTargets`。
4. 服务启动时必须严格校验文件、编码、JSON结构、版本、区域、字典完整性、代码唯一性、非空标签和非负整数排序号。每组代码集合必须与对应Python枚举完全一致；配置无效时启动失败，不得回退为英文代码或部分字典。
5. 配置在应用启动时加载为不可变内存对象，请求不得重复读取磁盘或修改该对象。修改配置后必须重启服务，本阶段不提供热更新。
6. 所有状态有效的登录用户均可查询UI字典；查询不产生业务审计，字典读取本身不访问数据库。
7. 不提供UI字典新增、编辑、删除或刷新接口；接口不得返回配置文件路径、加载时间、文件内容或内部异常。
8. 认证仍遵循现有登录状态和令牌失效校验规则；字典读取不额外引入业务数据查询。

## 15B. Q2-B更新时间范围检索

1. `GET /api/v1/documents`、`GET /api/v1/main-plans/{id}/sub-plans`和`GET /api/v1/main-plans/{id}/attachments`统一支持可选的 `updatedFrom`、`updatedTo`。
2. `updatedFrom`表示文档 `updated_at >= updatedFrom`，`updatedTo`表示文档 `updated_at <= updatedTo`；同时提供时为闭区间，且开始时间不得晚于结束时间。
3. 子方案按子方案自身更新时间检索；已挂载附件按附件文档自身更新时间检索，不使用挂载关系的创建时间、更新时间或排序号。
4. 参数必须是包含明确时区的ISO 8601时间。前端统一发送UTC `Z`格式；后端接受合法带时区ISO 8601并归一化为UTC，禁止把无时区时间当作UTC。
5. 空字符串、非法时间和反向区间统一返回 `400 INVALID_ARGUMENT`及requestId。
6. 时间条件必须在数据库查询层、权限可见性判定和分页之前执行，不得读取完整结果后用Python过滤。
7. 检索不增加查看次数、下载次数或审计记录，不改变既有权限、密级、状态、排序、字符串ID、DTO和附件共享规则。

## 17. 变更控制

执行会话不得直接修改本文件。需要变更时，应：

1. 在执行报告中列出拟变更条款、原因和影响；
2. 停止依赖该条款的实现；
3. 由统领会话确认并修改契约版本；
4. 前端、后端和 OpenAPI 同步后再继续。

版本1.1经统领会话批准，引入独立回收站资源和单条文档恢复；旧文档恢复路径继续永久废止。

版本1.2经统领会话批准，引入只读统一UI字典配置和查询接口，不修改既有枚举、数据库结构或业务语义。

版本1.3经统领会话批准，统一三个既有列表接口的文档更新时间范围检索，不新增路径、数据库字段、索引或迁移；Office预览不得占用1.3版本。

Q2-B实现时必须同步将OpenAPI `info.version`升级为 `1.3.0`。

## 18. Q3-B 1.4业务对齐

1. 正式角色仅保留 `ADMIN` 和 `USER`。`ADMIN`返回`DOCUMENT_BROWSER`、`BACKEND_MANAGEMENT`、`AUDIT_LOG`，`USER`只返回`DOCUMENT_BROWSER`。四个审计查询接口和文档统计接口仅ADMIN可访问。历史AUDITOR必须失效旧Token、停用并逻辑删除，不得提升为ADMIN。
2. 只有有效叶子分类可直接存放MAIN。选择存在有效子分类的分类创建或编辑MAIN时返回`409 CATEGORY_NOT_LEAF`，并在details返回`categoryId`和`childCategoryCount`。已直接存放有效MAIN的叶子分类不得新增有效子分类，也不得成为分类移动目标。
3. `CategoryNode.documentCount`表示当前JWT用户在该分类完整有效子树内有权查看的有效MAIN数量。关键词过滤保留祖先节点时，计数仍基于完整业务子树，不按过滤后的可见节点截断。
4. `GET /api/v1/documents`支持`includeDescendants`。未传`categoryId`时查询全部可见MAIN；传分类并设置true时查询该分类及全部有效后代；false或缺省时精确匹配分类。
5. MAIN创建不接收status和visibilityType，服务端固定为`PUBLISHED`、`CUSTOM`且初始ACL为空。SUB_PLAN创建不接收categoryId、status或visibilityType，分类继承父MAIN，状态固定`PUBLISHED`，可见范围动态继承父MAIN。编辑同样不接收status和visibilityType，SUB_PLAN也不接收categoryId；主案改分类时同步有效子方案的分类冗余。
6. 独立共享附件库仅ADMIN可枚举和管理。USER仅能通过至少一个有VIEW或DOWNLOAD权限的有效已挂载主案访问对应附件详情、预览或下载。
7. PDF与DOCX预览返回原文件二进制及准确MIME，使用inline Content-Disposition、`nosniff`和`private, no-store`，并执行授权、相对路径、普通文件、存在性、大小、SHA-256、扩展名、文件头及Office ZIP结构校验。DOC、XLS、XLSX返回`415 PREVIEW_UNAVAILABLE`。预览不增加downloadCount且不写DOWNLOAD_DOCUMENT审计。
8. `doc_document`保存`content_text`、`content_extract_status`和`content_extracted_at`；`search_text`升级为LONGTEXT并组合名称、摘要、标签和正文。DOCX提取段落、表格、页眉页脚，PDF提取文本层；DOC为UNSUPPORTED。提取失败不丢失原文件。回填命令只允许显式连接`dms_test`且不得自动执行。
9. 业务时区来自`DMS_BUSINESS_TIMEZONE`，默认`Asia/Shanghai`，非法值在应用配置加载阶段失败。本版本数据库迁移为`0003_q3_business_alignment_and_content_search`，OpenAPI版本为`1.4.0`。
