我设计了一个系统，内部有如下实体：

1. **文章**：以文本为主体，可能包含少量表格、代码、文头文尾等内容，包含大量需提取的关键信息。一个典型的示例：

    ```
    2026年6月1日晚8点，好友们决定为王若飞（男）组织一场生日派对。这次小范围聚会共有5人参加，刘阳（女）作为总策划，除寿星和总策划外，还有李四（女）、王五（男）和赵六（男）参加。活动总预算为3000元，且已完成备案，现场安排了掷骰子、扔飞镖和打桌球等破冰游戏，确保大家玩得尽兴。
    ```

2. **关键信息定义**：使用Json定义的树状结构，每个节点以`node_label`为key，包含`metadata`字段和`subnodes`字段，`metadata`字段中包含`node_label`（取关键信息名称的拼音首字母、`node_name`（关键信息名称）、`node_type`（取值为`concept`时表示分类节点，`property`时表示属性节点）、`value_type`（仅当`node_type`为`property`时生效，取值为`integer`、`float`、`string`、`boolean`、`date_time`、`class`，其中`class`表示关键信息为复合结构，包含子属性）、`is_list`（仅当`node_type`为`property`时生效，标识关键信息是否为列表类型，即是否可以有多个实例）字段。一个典型的示例：

    ```json
    {
      "metadata": {
        "node_label": "PDJH",
        "node_name": "派对计划",
        "node_type": "concept"
      },
      "subnodes": {
        "JBXX": {
          "metadata": {
            "node_label": "JBXX",
            "node_name": "基本信息",
            "node_type": "concept"
          },
          "subnodes": {
            "PDMC": {
              "metadata": {
                "node_label": "PDMC",
                "node_name": "派对名称",
                "node_type": "property",
                "value_type": "string",
                "is_list": false
              }
            },
            "PDKSSJ": {
              "metadata": {
                "node_label": "PDKSSJ",
                "node_name": "派对开始时间",
                "node_type": "property",
                "value_type": "date_time",
                "is_list": false
              }
            },
            "PDZRS": {
              "metadata": {
                "node_label": "PDZRS",
                "node_name": "派对总人数",
                "node_type": "property",
                "value_type": "integer",
                "is_list": false
              }
            },
            "SFBA": {
              "metadata": {
                "node_label": "SFBA",
                "node_name": "是否备案",
                "node_type": "property",
                "value_type": "boolean",
                "is_list": false
              }
            }
          }
        },
        "RYXX": {
          "metadata": {
            "node_label": "RYXX",
            "node_name": "人员信息",
            "node_type": "concept"
          },
          "subnodes": {
            "ZCH": {
              "metadata": {
                "node_label": "ZCH",
                "node_name": "总策划",
                "node_type": "property",
                "value_type": "class",
                "is_list": false
              },
              "subnodes": {
                "XM": {
                  "metadata": {
                    "node_label": "XM",
                    "node_name": "姓名",
                    "node_type": "property",
                    "value_type": "string",
                    "is_list": false
                  }
                },
                "SFZH": {
                  "metadata": {
                    "node_label": "SFZH",
                    "node_name": "身份证号",
                    "node_type": "property",
                    "value_type": "string",
                    "is_list": false
                  }
                }
              }
            },
            "RYQD": {
              "metadata": {
                "node_label": "RYQD",
                "node_name": "人员清单",
                "node_type": "property",
                "value_type": "class",
                "is_list": true
              },
              "subnodes": {
                "XM": {
                  "metadata": {
                    "node_label": "XM",
                    "node_name": "姓名",
                    "node_type": "property",
                    "value_type": "string",
                    "is_list": false
                  }
                },
                "SFZH": {
                  "metadata": {
                    "node_label": "SFZH",
                    "node_name": "身份证号",
                    "node_type": "property",
                    "value_type": "string",
                    "is_list": false
                  }
                }
              }
            }
          }
        },
        "HDXX": {
          "metadata": {
            "node_label": "HDXX",
            "node_name": "活动信息",
            "node_type": "concept"
          },
          "subnodes": {
            "PBYXLB": {
              "metadata": {
                "node_label": "PBYXLB",
                "node_name": "破冰游戏清单",
                "node_type": "property",
                "value_type": "string",
                "is_list": true
              }
            }
          }
        }
      }
    }
    ```

3. **关键信息实例**：根据**关键信息定义**，对其中的节点进行赋值而得到的实例，以Json形式表示。其中，有如下注意事项：

    1. 节点以`node_label`为key；
    
    2. 在**关键信息定义**中，`node_type`为`concept`的，value为Json对象，包含其子节点或子属性的值；

    3. 在**关键信息定义**中，`value_type`为`integer`、`float`、`string`、`boolean`、`date_time`时，如果`is_list`为`false`，value为对应类型；如果`is_list`为`true`，value为对应类型列表。特别地，`date_time`类型的值设置为字符串，但格式固定为`XXXX-XX-XXTXX:XX:XX.XXX`；

    4. 在**关键信息定义**中，`value_type`为`class`的，如果`is_list`为`false`，value为Json对象，包含其子属性的值；如果`is_list`为`true`，value为Json对象列表，每个元素包含其子属性的值。

    针对**文章**示例，根据上述**关键信息定义**，生成对应的**关键信息实例**为：

    ```json
    {
      "PDJH": {
        "JBXX": {
          "PDMC": "王若飞生日派对",
          "PDKSSJ": "2026-06-01T20:00:00.000",
          "PDZRS": 5,
          "PDZYS": 3000.0,
          "SFBA": true
        },
        "RYXX": {
          "ZCH": {
            "XM": "刘阳",
            "XB": "女"
          },
          "RYQD": [
            {
              "XM": "刘阳",
              "XB": "女"
            },
            {
              "XM": "王若飞",
              "XB": "男"
            },
            {
              "XM": "李四",
              "XB": "女"
            },
            {
              "XM": "王五",
              "XB": "男"
            },
            {
              "XM": "赵六",
              "XB": "男"
            }
          ]
        },
        "HDXX": {
          "PBYXQD": [
            "掷骰子",
            "扔飞镖",
            "打桌球"
          ]
        }
      }
    }
    ```

现在，我需要依托大模型，根据输入的**文章**和**关键信息定义**，从文章中将所有关键信息提取出来；请帮我设计一个prompt，使得大模型能够准确、高效、稳定地完成这个task。如果有必要，请使用langchain等框架，分步骤进行，以达到最好的效果。

--------

OK，现在，我需要将原始文本中相关的关键信息区域也标识出来。例如，针对“2026年6月1日晚8点，好友们决定为王若飞（男）组织一场生日派对，活动已完成备案。”这个文本，我希望生成类似于“{[PDJH.JBXX.PDKSSJ],2026-06-01T20:00:00.000,2026年6月1日晚8点,2026年1月1日~2026年12月31日,YYYY年M月D日(上午/下午/晚)H点[m分[s秒]]}，好友们决定为{[PDJH.RYXX.RYQD[0].XM],王若飞,王若飞,2~4个汉字,不作转换}（{[PDJH.RYXX.RYQD[0].XB],男,男,男/女,不作转换}）组织一场生日派对，活动{[PDJH.JBXX.SFBA],true,已完成备案,已完成备案/未完成备案,true->已完成备案/false->未完成备案}”。每块关键信息区域包括{节点路径,提取值,原始文本,提取值的约束（以自然语言表示，供大模型理解）,提取值到原始文本的转换规则（以自然语言表示，供大模型理解）}。请先帮我思考这个标识相关的符号体系是否合理，是否会与原文符号产生歧义，如果会，则不一定使用这套符号体系，改进该体系。

--------

1. 值约束来源是关键信息定义Json，部分关键信息会带一个constraints属性，里面会带{"raw_constraints":{"min_value":[0],"max_value":[100]}}、{"raw_constraints":{"enum_string":[["男", "女"]]}}这样的约束，确保将其精准地转换为自然语言，也可以在值约束里将原始的Json约束附带上。如果关键信息没有自带该属性，那么对于显而易见的关键信息约束，如“身份证号（18位数字，最后一位可以是X）”、“性别（男/女）”、“年龄（0-110）”等，你要进行推断，给出约束；对于你无法确定的约束，不要随意给出。  转换规则由你自行给出。
2. 标注以一个独立字段输出。

--------

有几个提取的原则需要微调一下：
1. 如果原文中某处同时对应关键信息定义中的多个关键信息，如“刘阳”即对应“总策划”，又对应“人员清单”中的一员，那么标注时，将多处同时标注，并在中间使用“||”分隔开，如：`{{PDJH.RYXX.ZCH.XM||刘阳||刘阳|| ||不作转换}}||{{PDJH.RYXX.RYQD[1].XM||刘阳||刘阳|| ||不作转换}}`；
2. 如果原文中不存在关键信息实例中的某处关键信息原文，例如原文为“为王若飞（男）组织一场生日派对”，而关键信息实例中为“王若飞生日派对”，那么在原文中找到最相关的部分，如“生日派对”进行标注，并将转换规则写为“根据原文进行微调”，结果为`{{PDJH.JBXX.PDMC||王若飞生日派对||生日派对|| ||根据原文进行微调}}`；
3. 对于时间、布尔等类型的值，你原来的做法是在代码里写死，我需要你根据原文内容，利用大模型（为确保快速处理，可不使用reasoning推理）给出明确的转换规则，例如`ISO 8601 → YYYY年M月D日(上午/下午/晚)H点[m分[s秒]]，根据是否为整点，可视情省略[]中的内容`；
4. 你现在预置了一些关键信息的约束，我同样要求你不要写死，根据不同关键信息定义中的内容，利用大模型（为确保快速处理，可不使用reasoning推理）给出相应的约束。

--------

针对某个提取值在原文中拥有多个对应文本的情况（如“王五”这个人在文中出现了多次），我新增了一条提取规则：“10. **同一提取值可多次标注**：如果某个提取值在原文中拥有多个对应文本，对这些文本分别进行标注，各标注的“节点路径”、“提取值”、“值约束”需保持一致，“原始文本”、“转换规则”可以不同，视原文决定”。但是经过测试，该规则未生效，多个对应文本只有一个被标注。请帮我检查并使之生效。

--------

请为我增加调试日志，在每一次与LLM交互前，打印本次交互是整个处理流程中的哪个分步骤，同时打印给LLM发送的prompt，以便我了解发送的内容。同时，在最外层函数调用处增加debug变量，只有当它为True时才打印上述日志。

--------

目前，在转换规则生成过程中，由于直接把整篇文章给了LLM，导致LLM识别提取值对应的原文不清晰（例如提取值“3000.0”对应文中的“3000”，但LLM认为是“3000元”）。所以，我想将“Step 2: LLM辅助生成约束和转换规则”拆分成两步，第一步将提取值对应的原文提取出来，第二步仅根据对应的原文进行约束和转换规则的生成。为了速度快，两步均采用大模型的轻量级调用，不调用reasoning推理。

--------

现在，我希望做一个界面，左侧为菜单栏，目前包含两个菜单：文档解析和信息管理。
1. 文档解析：分为两栏，左栏为文档展示区域，如未选择文档，显示“请选择文档”；上传文档后，左侧可浏览文档（支持pdf和word），右侧区域分为上下两栏，比例约为3:1，上方显示从文档中提取的正文内容（不含页眉页脚等，可转为markdown格式），右上方有一个按钮，“开始解析”，点击后，下栏开始输出正文内容关键信息提取的思考过程（即原python文件输出的内容）；思考完成后，上方内容区域变为提取后的文本（带{{||}}）。
2. 信息管理：针对关键信息树状结构的管理维护模块，以折叠树形式展示，每个节点均可编辑其元数据和子节点（参考之前的设计：**关键信息定义**：使用Json定义的树状结构，每个节点以`node_label`为key，包含`metadata`字段和`subnodes`字段，`metadata`字段中包含`node_label`（取关键信息名称的拼音首字母、`node_name`（关键信息名称）、`node_type`（取值为`concept`时表示分类节点，`property`时表示属性节点）、`value_type`（仅当`node_type`为`property`时生效，取值为`integer`、`float`、`string`、`boolean`、`date_time`、`class`，其中`class`表示关键信息为复合结构，包含子属性）、`is_list`（仅当`node_type`为`property`时生效，标识关键信息是否为列表类型，即是否可以有多个实例）字段。）
前端请使用vue实现，后端引入flask框架。请实现。

---

后端配置里有API Key，前端不用输入，请求发给后端，后端使用配置的API key即可。

---

将后端端口改为8754，前端端口改为8774，然后重启服务。

---

我要求左侧是文档预览，word打开什么样子就是什么样子，pdf打开什么样子就是什么样子；右侧md文字行距过大，减小一些

---

1. 上传pdf，报错：上传失败：文档解析失败: PyMuPDF 未安装，无法解析 PDF
2. 上传word，不报错，右侧也能解析出来，但左侧为空白页面，无word文档显示。

---

点击上传，报错：上传失败：Promise.withResolvers is not a function

---

页面缩放大小为125%时，显示没有问题；100%时，右侧有一大块区域是空白的，鼠标也无法交互。请修复。

---

信息管理中，我希望不止可以管理这一条关键信息，而是整理一个列表，每一条关键信息有名称、描述等元数据，点开后是这个树状结构，可以进行管理。支持json格式的导入，导入时填入名称和描述，导入格式参考@data/samples/info_definition.json。

---

我希望在@extractor.py中写一个函数，输入一段文本，从中提取**关键信息定义**Json，格式参考@data/samples/info_definition.json，具体含义：使用Json定义的树状结构，每个节点以`node_label`为key，包含`metadata`字段和`subnodes`字段，`metadata`字段中包含`node_label`（取关键信息名称的拼音首字母、`node_name`（关键信息名称）、`node_type`（取值为`concept`时表示分类节点，`property`时表示属性节点）、`value_type`（仅当`node_type`为`property`时生效，取值为`integer`、`float`、`string`、`boolean`、`date_time`、`class`，其中`class`表示关键信息为复合结构，包含子属性）、`is_list`（仅当`node_type`为`property`时生效，标识关键信息是否为列表类型，即是否可以有多个实例）字段。使用大模型来实现，生成后校验Json结构是否正确，如不正确，给出错误信息让大模型重试。

---

创建一个 test_definition.py 示例脚本，但复用test.py 中已有的 API 配置调用模型


---

右下方的文本框里完全没有思考过程输出，之前annotator.py、extractor.py等文件的思考过程输出呢？请将它们返回给前端，并实时流式输出至右下方文本框中。

---

写个启动脚本吧，里面设置INFO_EXTRACTOR_API_KEY变量，我稍后填入key。

---

1. 界面右下方，我希望将当前Step也输出出来，当前阶段的思考过程和输出前方统一加4个空格缩进（原有n个空格缩进的话，变为n+4个），阶段之间用“====================================”隔开，例如：
```
阶段一：XXXXX
    XXXX（思考过程）
    XXXX（思考过程）
    输出：
    {
      XXXXX
    }
====================================
阶段2a：XXXX
    ……
====================================
2. 增加功能：右侧上方区域，不再以markdown形式展示，直接将文字排版显示出来，类似于新闻页面，默认使用仿宋_GB2312字体。解析完成后，对于{{COL1||COL2||COL3||COL4||COL5}}中间的内容进行渲染，文字为原文（COL3），但使用灰色背景将对应文字框选。点击框选区域任意位置，弹出对话框，对话框内表单包含字段：
①关键信息名称，值为COL1对应中文字段名，不可编辑；②关键信息值，值为COL2，可编辑；这里你需要根据COL1对应的数据类型和COL4生成组件，如COL1为数字，这里就是数字输入框；COL1为时间，这里就是时间选择框；COL1为字符串，这里就是文本框；COL1为字符串，且COL4约束为枚举型，这里就是下拉选择框；等等。③文中展示值，值为COL3，可编辑，文本框。修改并确认后，后台更新{{}}中的COL2和COL3值，原文中灰色背景框选的部分更新为新的COL3值。

---

提取报错：提取失败：maximum recursion depth exceeded in comparison

---

基本实现了，但存在两个问题：
1. 灰色选中的区域，不要换行显示，嵌入原文中即可。我以[]表示灰色区域，示例：
```
[小明]是个[医生]。
```
而非
```
[小明]
是个
[医生]
。
```
2. 文本区域，段落与段落之间空行太多，去掉，一个空行都不要。

---

另外，右下方思考过程输出时，区域内滚动条自动不断滚到最底部吧

---

你个垃圾，灰色框选区域不要换行展示，而是嵌入文中，你一点没改；段落与段落之间一个空行都不要，但不是不要换行，还是要一个换行的。你行不行？你不行有的是大模型能干。

---

有没有一种可能，你用<span>，它灰色区域就一定会换行？我需要它在原文中的原位置，不能换行显示。

---

基本实现了，但段落之间的换行还要留着啊，怎么没了？从前后端都找找问题。
另外，每个段落前要缩进2字符。

---

当关键信息在段首时，你的处理方式会导致它前面的换行丢失，与上一段接在一起。请解决。
另外，为了测试方便，在文本区域增加按钮：导入文本，让我粘贴带{{}}的文本进去，你直接展示，省去标注流程。

---

段落间的换行又没了，你仔细看看怎么回事，我的要求：段落间的换行要留着，尤其注意段首有关键信息的情况；关键信息两侧的换行不要（除非它在段落首尾）

---

.replace(/(\{\{[^{}]*\}\})/g, (m) => m.replace(/\n+/g, ''))，这个语句是干什么用的？逻辑不对吧，执行完以后，文中就没有段落间的换行了

---

function confirmImport() {
  const text = importText.value || ''
  if (!text.trim()) {
    ElMessage.warning('请粘贴要导入的文本')
    return
  }
  annotatedContent.value = normalizeNewsText(text)
  hasResult.value = true
  importDialogVisible.value = false
  ElMessage.success('已导入文本')
  persistAnnotation()
}
这个方法中，normalizeNewsText(text)的结果含\n，为什么最终渲染出来的丢失了换行符？

---

导入时输入：
1
2
3
解析完后，也显示 1 2 3，换行到底怎么回事？？？

---

我希望启动前端，但页面上不显示vue调试工具

---

取消所有TODO。现在，信息管理中，条目无法删除，请实现。写完后，不要启动playwright验证。

---

把模型的model_name、base_url都做到start_backend的两个脚本里。

---

现在，我的test.py可以正常使用大模型推理，但使用start_backend.bat调用app.py中的推理，报错： {'error': {'code': '1113', 'message': '余额不足或无可用资源包,请充值。'}}，请帮我检查配置。

---

test.py 日志：POST https://open.bigmodel.cn/api/coding/paas/v4/chat/completions "HTTP/1.1 200 OK"
app.py 日志：POST https://open.bigmodel.cn/api/coding/paas/v4/chat/completions "HTTP/1.1 429 Too Many Requests"
你发个请求验证吧

---

三个问题：
1. 最终输出结果，前后容易带上<article>和</article>，过滤掉；
2. 对照我的原始需求：“右侧上方区域，不再以markdown形式展示，直接将文字排版显示出来，类似于新闻页面，默认使用仿宋_GB2312字体。解析完成后，对于{{COL1||COL2||COL3||COL4||COL5}}中间的内容进行渲染，文字为原文（COL3），但使用灰色背景将对应文字框选。点击框选区域任意位置，弹出对话框，对话框内表单包含字段：①关键信息名称，值为COL1对应中文字段名，不可编辑；②关键信息值，值为COL2，可编辑；这里你需要根据COL1对应的数据类型和COL4生成组件，如COL1为数字，这里就是数字输入框；COL1为时间，这里就是时间选择框；COL1为字符串，这里就是文本框；COL1为字符串，且COL4约束为枚举型，这里就是下拉选择框；等等。③文中展示值，值为COL3，可编辑，文本框。修改并确认后，后台更新{{}}中的COL2和COL3值，原文中灰色背景框选的部分更新为新的COL3值。” 你现在在灰色背景框选区域中，显示的是COL2的值，而非COL3的值；另外，弹出对话框中，“关键信息值”应为COL2的值，目前为空值；“文中展示值”应为COL3的值，目前为COL2的值。请修改；
3. 思考过程容易输出英文思考，请强调输出中文思考过程。

---

你说“LLM 有时输出 {{path||raw_text||...}}（第 2 段空、第 3 段填了原文），看起来像"COL2 显示成了 COL3"。” 根据我观察，不是的，就算{{}}中内容正确，前端处理也有问题。
另外，我不希望完全依赖大模型，请写一个方法，校验LLM输出的结果，{{}}外的内容+{{}}内的原文字段，能否完全匹配原文，若不能，进行相应修改后再输出。

---

这个新的修复步骤，是否在日志和前端思考过程输出中打印了？如果没有，请补充

---

重建报错： [修复] text-anchored 重建不可行：text segment 在原文中找不到（从位置 230 起）：'指挥员仍然感觉意犹未尽："每次训练，都是一次全新挑战！"\n\n'
就是因为LLM容易出现幻觉，比如，就算让LLM原样输出，它也容易把“”符号输出为""符号，或者出现一些小的问题。所以重建并不是只重建{{}}内的部分，还需要针对{{}}外的部分进行校验和修复，且保持最小的编辑距离。

---

“导入文本”和“提取完成”后，在渲染文本中的组件时，请把每个组件对应的关键信息值、文中展示值打印出来，我查看一下，现在还有问题。

---

@DocumentParse.vue文件中，第341行左右，
    if (ch === '|') {
      fields.push(buf)
      buf = ''
      continue
    }
这里有问题，分隔符为“||”而非“|”，修复它。这么明显的问题，为什么会出现？？？

---------------------------------------
你是本项目的“会话3：后端设计与实现工程师”。
项目名称：方案计划文档管理系统。
你的长期职责是负责后端业务设计、MySQL持久化、文件存储、REST接口、权限、安全、审计、测试和前后端联调支持。
一、工作边界
你主要只允许修改 backend/**。
不修改 frontend/**。
不自行修改以下统领文档：FUNCTION_AND_API_SPECIFICATION.md
CODEX_BACKEND_PERSISTENCE_GUIDE.md

如果需求、数据库或接口设计存在问题，只提交“变更建议”，等待统领会话批准。
现有backend/src中的AI抽取、标注、校验和修复代码不在本阶段范围内，不得无关重构。
不接入Milvus，不实现向量化。
不实现电子签章和审批流程。
不考虑国产数据库和操作系统。
结构化数据库使用MySQL 8。
文件存储根目录为项目中的backend/dms-storage/。
业务接口统一使用/api/v1；现有AI相关/api接口暂时保留。
不一次实现全部功能。每次只完成统领会话指定的一个阶段。
不自动提交、合并或推送Git，除非我明确要求。
保留用户已有修改，不覆盖无关文件。
二、必须先阅读
FUNCTION_AND_API_SPECIFICATION.md
CODEX_BACKEND_PERSISTENCE_GUIDE.md
backend/app.py
backend/src/**
backend/requirements.txt
当前Git状态和后端目录结构
三、已确定的数据和业务规则
第一阶段最小结构包含：sys_organization
sys_user
doc_category
doc_document
doc_attachment_binding
doc_permission
sys_audit_log

所有业务表采用创建时间、更新时间、删除时间、逻辑删除和rowVersion。
允许名称、路径、计数和日志快照字段适当冗余。
主案与子方案使用parent_document_id表达一对多关系。
共享附件的document_type为ATTACHMENT，parent_document_id为空。
主案与共享附件通过doc_attachment_binding建立多对多关系。
同一主案和附件只能存在一条有效挂载关系。
解除挂载只逻辑删除关系，不删除附件。
共享附件对所有已登录用户共享，不写doc_permission。
删除仍在挂载的附件返回409 ATTACHMENT_IN_USE。
文件写入backend/dms-storage/，数据库只保存相对路径。
文件上传必须具备数据库事务和文件失败补偿。
数据库字段使用snake_case，JSON字段使用lowerCamelCase。
接口使用统一响应、分页、错误码和requestId。
现有AI代码不得与文档管理业务强耦合。
四、长期实施阶段
B0：后端现状、数据库和接口设计核对
B1：MySQL、迁移、配置、统一响应和OpenAPI骨架
B2：登录、用户、组织和审计基础
B3：方案分类
B4：主案、子方案和共享附件只读接口
B5：文件上传、预览、下载、逻辑删除和恢复
B6：共享附件挂载和解除挂载
B7：文档权限
B8：日志与统计
B9：集成测试、安全检查和清理
在当前轮次只执行B0，不修改任何代码。
五、本轮任务：B0
请只读分析当前后端，并输出：
当前Flask后端结构、既有接口和AI模块边界。
在不影响现有AI接口的前提下，业务后端建议的模块目录。
MySQL 7张核心表的最终逻辑核对。
必要索引、唯一性约束、外键取舍和事务边界。
backend/dms-storage/文件目录、上传流程和失败补偿设计。
FUNCTION_AND_API_SPECIFICATION.md中全部业务接口的实现优先级。
拟使用的MySQL驱动、ORM或迁移工具建议及理由，但不要安装。
OpenAPI文件建议位置和组织方式。
现有接口与新/api/v1接口可能发生的冲突。
需要统领会话裁决的事项。
B1阶段建议新增和修改的具体文件，但不要实际修改。
输出格式固定为：
【阶段】
【现有后端分析】
【建议模块结构】
【数据库核对】
【文件存储设计】
【接口实施顺序】
【技术选型建议】
【冲突与待确认】
【本轮未执行】
【下一阶段建议】
完成B0后立即停止，不要继续B1。
-------------------------------------------------------------------------------------------------
【会话身份】

你是“会话3：后端设计与实现工程师”。

你负责本项目的后端设计、数据库设计、持久化实现、文件存储、接口实现、后端测试及后续与前端联调。

本轮执行阶段：

B1：DMS后端基础设施、七张数据库表、初始迁移、存储目录和OpenAPI公共骨架

B0现状分析已经通过统领会话审核。本轮允许修改后端代码，但不得提前实现具体业务接口。

--------------------------------------------------
一、强制加载的契约
--------------------------------------------------

执行任何操作前，必须完整读取：

1. DMS_FUNCTION_CONTRACT.md
2. DMS_API_CONTRACT.md

这两份文件是本阶段强制执行契约：

- DMS_FUNCTION_CONTRACT.md 是业务规则、数据边界、权限和存储规则的最高执行依据。
- DMS_API_CONTRACT.md 是接口路径、字段、DTO、枚举、响应和错误码的最高执行依据。

以下文件只作为背景和数据库字段设计参考：

3. FUNCTION_AND_API_SPECIFICATION.md
4. CODEX_BACKEND_PERSISTENCE_GUIDE.md

约束优先级：

1. 用户或统领会话最新明确确认的要求
2. DMS_FUNCTION_CONTRACT.md
3. DMS_API_CONTRACT.md
4. FUNCTION_AND_API_SPECIFICATION.md
5. CODEX_BACKEND_PERSISTENCE_GUIDE.md
6. 当前代码和历史实现

低优先级材料与新契约冲突时，以新契约为准。

不得自行修改两份新契约。

不得为了兼容旧文档实现废止接口。

不得自行增加第八张业务表。

发现契约冲突或数据库无法严格表达的规则时，应停止相关部分并提交统领会话裁决，不得自行改变业务语义。

--------------------------------------------------
二、本轮目标
--------------------------------------------------

本轮只建设DMS后端基础设施：

1. 独立DMS模块结构。
2. MySQL数据库配置。
3. SQLAlchemy模型。
4. 七张核心表。
5. Alembic初始迁移。
6. 统一响应、异常、分页和requestId。
7. /api/v1 Blueprint。
8. /api/v1/health。
9. Bearer认证扩展骨架。
10. backend/dms-storage/目录骨架。
11. OpenAPI公共组件骨架。
12. 后端基础自动化测试。

本轮不实现登录、分类、文档、附件、挂载、权限、审计查询等具体业务接口。

--------------------------------------------------
三、现有系统保护边界
--------------------------------------------------

必须保护：

- backend/src/**
- 现有AI抽取、标注、校验和修复模块
- 现有/api/*接口
- 现有AI数据和测试

要求：

1. 新DMS业务放入backend/dms/。
2. 新接口统一注册到/api/v1。
3. 现有旧/api/*不能自动套用DMS统一响应。
4. 现有旧/api/*不能自动要求DMS登录。
5. app.py只允许做最小化DMS初始化和Blueprint注册。
6. 不得无关重构现有AI代码。
7. 不得改变旧/api/health的响应结构和行为。

--------------------------------------------------
四、本轮允许修改范围
--------------------------------------------------

允许新增或修改：

backend/dms/**
backend/migrations/**
backend/openapi/**
backend/tests/dms/**
backend/dms-storage/**
backend/requirements.txt
backend/.env.example
backend/.gitignore
项目根.gitignore（仅在确有必要时做最小修改）
backend/app.py（仅最小化注册）

不得修改：

frontend/**
DMS_FUNCTION_CONTRACT.md
DMS_API_CONTRACT.md
FUNCTION_AND_API_SPECIFICATION.md
CODEX_BACKEND_PERSISTENCE_GUIDE.md
backend/src/**

不得进行Git提交、推送、合并、重置、清理或覆盖用户改动。

--------------------------------------------------
五、技术栈
--------------------------------------------------

采用：

- Flask
- SQLAlchemy 2.x
- Flask-SQLAlchemy 3.1.x
- PyMySQL
- Alembic
- pytest
- MySQL 8.x
- 同步数据库访问
- READ COMMITTED

不得引入：

- 异步ORM
- FastAPI替换
- Milvus
- Celery或其他任务队列
- 文档向量化
- Office转换服务
- 动态RBAC平台
- 新AI框架

--------------------------------------------------
六、建议模块结构
--------------------------------------------------

建议：

backend/dms/
- __init__.py
- config.py
- extensions.py

backend/dms/common/
- errors.py
- response.py
- request_context.py
- pagination.py
- enums.py

backend/dms/database/
- base.py
- transaction.py

backend/dms/models/
- organization.py
- user.py
- category.py
- document.py
- attachment_binding.py
- permission.py
- audit_log.py

backend/dms/security/
- auth_context.py
- decorators.py

backend/dms/storage/
- paths.py

backend/dms/api/v1/
- blueprint.py
- health.py

可以根据现有工程做小幅调整，但必须维持职责分离。

B1不要创建大量空repository和空service文件。

--------------------------------------------------
七、七张核心表
--------------------------------------------------

只能建立以下七张核心表：

1. sys_organization
2. sys_user
3. doc_category
4. doc_document
5. doc_attachment_binding
6. doc_permission
7. sys_audit_log

具体基础字段可参考CODEX_BACKEND_PERSISTENCE_GUIDE.md，但冲突规则必须以两份新契约为准。

普通业务表统一包含：

- id
- created_by
- updated_by
- created_at
- updated_at
- deleted_at
- is_deleted
- row_version

要求：

- id为BIGINT；
- is_deleted默认0；
- row_version默认0；
- created_at和updated_at精确到毫秒；
- 时间按UTC管理；
- 正常查询以后默认过滤is_deleted=0；
- created_by和updated_by不建立外键；
- row_version用于乐观锁；
- 逻辑删除时写入deleted_at；
- 不使用数据库级联删除。

--------------------------------------------------
八、表级强制裁决
--------------------------------------------------

1. sys_organization

必须支持：

- 组织编码
- 组织名称
- 父组织
- 组织路径冗余
- 排序
- ENABLED/DISABLED
- 逻辑删除
- 乐观锁

要求：

- org_code永久唯一，逻辑删除后也不得复用；
- parent_id自外键；
- ON DELETE RESTRICT或NO ACTION；
- 不允许级联删除。

2. sys_user

必须支持：

- username
- password_hash
- real_name
- organization_id
- organization_name冗余
- role_code
- security_level
- status
- auth_version
- 最近登录时间和IP
- 通用审计字段

要求：

- username永久唯一；
- organization_id建立外键并RESTRICT；
- auth_version默认0；
- B1不实现真实密码校验和JWT签发；
- 不增加会话表。

3. doc_category

必须支持：

- category_code
- category_name
- category_type
- parent_id
- category_path
- sort_no
- document_count
- status
- 通用审计字段

要求：

- category_code永久唯一；
- parent_id自外键并RESTRICT；
- document_count只统计有效MAIN和SUB_PLAN；
- 不统计ATTACHMENT。

4. doc_document

必须同时承载：

- MAIN
- SUB_PLAN
- ATTACHMENT

必须包含：

- document_name
- summary
- document_type
- document_status
- security_level
- visibility_type
- attachment_type
- category_id
- category_name冗余
- category_path冗余
- parent_document_id
- root_document_id
- tags或等价最小存储
- original_file_name
- file_relative_path
- file_extension
- mime_type
- file_size
- file_hash
- child_count
- attachment_count
- view_count
- download_count
- search_text
- 创建人和更新人名称冗余
- 通用审计字段

强制语义：

MAIN：
- parent_document_id=NULL
- root_document_id最终指向自身
- category_id必填
- attachment_type=NULL

SUB_PLAN：
- parent_document_id指向有效MAIN
- root_document_id指向同一MAIN
- category_id必填
- attachment_type=NULL
- 不允许继续嵌套

ATTACHMENT：
- parent_document_id=NULL
- root_document_id=NULL
- category_id=NULL
- attachment_type必填
- security_level=PUBLIC
- visibility_type=ALL_AUTHENTICATED

要求：

- file_format不重复存储，由file_extension派生；
- file_hash只建立普通索引，不建立全局唯一约束；
- 类型和父级语义由服务层最终校验；
- 可使用合理CHECK约束，但不得依赖CHECK替代服务层验证；
- root_document_id=id需要插入后回写时，应通过事务处理；
- 不实现文件版本表。

5. doc_attachment_binding

必须包含：

- main_document_id
- attachment_document_id
- sort_no
- active_marker生成列
- 通用审计字段

要求：

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

建立：

UNIQUE(
  main_document_id,
  attachment_document_id,
  active_marker
)

两个文档字段均建立外键到doc_document，并使用RESTRICT或NO ACTION。

数据库外键只能保证ID存在，文档类型由服务层校验。

6. doc_permission

必须包含：

- document_id
- subject_type
- subject_id
- subject_name冗余
- can_view
- can_download
- can_edit
- can_manage_permission
- can_delete
- active_marker生成列
- 通用审计字段

建立：

UNIQUE(
  document_id,
  subject_type,
  subject_id,
  active_marker
)

规则：

- 权限只表达允许，不增加DENY；
- subject_id是ORG或USER多态引用，不建立单一外键；
- document_id建立外键并RESTRICT；
- 第一阶段只为MAIN保存权限；
- SUB_PLAN动态继承MAIN权限；
- 不复制子方案权限快照；
- 不使用INHERITED权限记录；
- ATTACHMENT不写本表。

7. sys_audit_log

这是追加写例外表。

必须包含：

- id
- user_id
- username
- real_name
- organization_id
- organization_name
- action_type
- target_type
- target_id
- target_name
- operation_result
- failure_reason
- client_ip
- user_agent
- request_id
- operation_detail
- created_at

明确不包含：

- updated_at
- deleted_at
- is_deleted
- row_version

要求：

- 不建立业务对象外键；
- 保存名称快照；
- 普通业务不得更新或删除；
- 至少建立时间、用户、组织、动作、目标、结果和requestId索引。

--------------------------------------------------
九、固定枚举
--------------------------------------------------

必须严格读取DMS_API_CONTRACT.md中的全部固定枚举。

至少包括：

RoleCode：
- USER
- ADMIN
- AUDITOR

AllowedModule：
- DOCUMENT_BROWSER
- BACKEND_MANAGEMENT
- AUDIT_LOG

DocumentType：
- MAIN
- SUB_PLAN
- ATTACHMENT

DocumentStatus：
- DRAFT
- PUBLISHED
- ARCHIVED

SecurityLevel：
- PUBLIC
- INTERNAL
- SECRET
- CONFIDENTIAL
- TOP_SECRET

比较值：
- PUBLIC=10
- INTERNAL=20
- SECRET=30
- CONFIDENTIAL=40
- TOP_SECRET=50

VisibilityType：
- ALL_AUTHENTICATED
- ORGANIZATION
- CUSTOM

AttachmentType：
- POLICY
- WORK_STANDARD
- TABLE
- DIAGRAM
- OTHER

SubjectType：
- ORG
- USER

CategoryType：
- SCENE
- STYLE
- SITUATION
- VERSION
- OTHER

EnabledStatus：
- ENABLED
- DISABLED

同时建立契约规定的审计动作、审计结果和审计目标枚举。

不得自行增加UNKNOWN等兼容值。

--------------------------------------------------
十、统一HTTP基础设施
--------------------------------------------------

只应用于/api/v1 Blueprint。

1. 成功响应：

{
  "code": "OK",
  "message": "success",
  "data": {},
  "requestId": "uuid"
}

不得自行增加timestamp。

2. 错误响应：

{
  "code": "ERROR_CODE",
  "message": "错误说明",
  "details": null,
  "requestId": "uuid"
}

details不得省略，无附加内容时为null。

3. 分页：

{
  "items": [],
  "page": 1,
  "pageSize": 20,
  "total": 0,
  "totalPages": 0
}

4. requestId：

- 接收X-Request-Id；
- 未提供时生成UUID；
- JSON响应体包含requestId；
- 响应头包含相同X-Request-Id；
- requestId在请求上下文中可供后续服务和审计使用。

5. 业务异常：

至少建立：

- 参数错误
- 未认证
- 无权限
- 未找到
- 冲突
- 文件格式错误
- 请求过大
- 系统错误

错误码语义必须与DMS_API_CONTRACT.md一致。

6. ID序列化：

- 数据库使用BIGINT；
- 对外JSON和OpenAPI使用字符串；
- 不把BIGINT直接序列化为JSON number。

7. Bearer认证：

B1只建立可扩展骨架或装饰器，不实现：

- JWT签发
- JWT验签业务
- 用户查询
- auth_version查询
- 登录和退出接口

未实现的认证装饰器不得被业务路由误用。

--------------------------------------------------
十一、Blueprint和健康检查
--------------------------------------------------

建立：

GET /api/v1/health

要求：

- 无需认证；
- 使用统一成功响应；
- 返回X-Request-Id；
- 不泄露数据库URL、密码、JWT密钥或磁盘绝对路径。

不得在B1创建其他业务占位接口。

不得实现契约中废止的路径。

--------------------------------------------------
十二、数据库迁移
--------------------------------------------------

使用Alembic创建初始迁移，例如：

backend/migrations/versions/0001_initial_schema.py

要求：

1. 面向MySQL 8.x。
2. 包含七张表。
3. 包含全部必要索引。
4. 包含唯一约束。
5. 包含外键。
6. 包含active_marker生成列。
7. 外键使用RESTRICT或NO ACTION。
8. 不使用ON DELETE CASCADE。
9. downgrade能够按依赖顺序回滚。
10. 不使用零散手写SQL作为唯一迁移机制。
11. 生成列如果必须使用MySQL专用DDL，应清楚封装并测试。
12. 不得声称未实际连接的MySQL迁移已经成功运行。
13. 没有测试MySQL时，集成测试应明确skip，而不是伪造通过。

--------------------------------------------------
十三、事务规则
--------------------------------------------------

配置或明确采用：

READ COMMITTED

为后续业务提供：

- 事务上下文
- 回滚机制
- 乐观锁基础
- SELECT FOR UPDATE使用入口
- 有限死锁重试的扩展位置

B1不提前实现具体业务事务。

不得在文件流传输期间长期持有数据库事务。

--------------------------------------------------
十四、文件存储目录
--------------------------------------------------

创建：

backend/dms-storage/
- original/
- preview/
- extracted/
- temporary/
- quarantine/
- recycle/

要求：

1. 目录结构保留。
2. 运行时业务文件不进入Git。
3. 可以使用.gitignore或.gitkeep保留目录。
4. 默认存储根目录指向backend/dms-storage/。
5. 允许DMS_STORAGE_ROOT环境变量覆盖。
6. 数据库未来只保存相对路径。
7. 不把目录注册为Flask静态目录。
8. 不放入示例业务文档。
9. B1只建立路径解析和安全校验基础，不实现上传。
10. 解析后的路径必须仍在存储根目录内。

--------------------------------------------------
十五、OpenAPI公共骨架
--------------------------------------------------

创建：

backend/openapi/openapi.yaml
backend/openapi/components/

建议组件：

- schemas.yaml
- responses.yaml
- parameters.yaml
- security-schemes.yaml

B1只定义：

1. OpenAPI基本信息。
2. /api/v1/health。
3. Bearer安全方案。
4. X-Request-Id请求头。
5. 统一成功响应。
6. 统一错误响应。
7. 分页结构。
8. 固定枚举。
9. 字符串ID schema。
10. date-time schema。

所有ID必须定义为：

type: string
pattern: '^[0-9]+$'

B1不得提前定义尚未实现的业务路径。

后续业务阶段才逐步将DMS_API_CONTRACT.md转换为完整OpenAPI。

--------------------------------------------------
十六、配置和依赖
--------------------------------------------------

更新backend/requirements.txt，增加B1必要依赖。

提供backend/.env.example，至少说明：

DMS_DATABASE_URL
DMS_STORAGE_ROOT
DMS_MAX_UPLOAD_SIZE
DMS_BATCH_MAX_FILES
DMS_JWT_SECRET

要求：

- 不写入真实账号密码；
- 不写入真实JWT密钥；
- 数据库默认配置不得含用户真实凭据；
- 不破坏现有AI配置；
- 真实.env必须被Git忽略；
- 不在代码中硬编码机器绝对路径。

--------------------------------------------------
十七、测试要求
--------------------------------------------------

至少增加和执行：

1. 配置加载测试。
2. DMS Blueprint注册测试。
3. 旧/api/health行为保持测试。
4. 新/api/v1/health统一响应测试。
5. X-Request-Id透传测试。
6. X-Request-Id自动生成测试。
7. 统一错误响应测试。
8. 七张模型导入测试。
9. SQLAlchemy metadata表数量和表名测试。
10. 普通业务表通用字段测试。
11. sys_audit_log例外字段测试。
12. auth_version字段测试。
13. attachment_type字段测试。
14. active_marker生成列测试。
15. 两个有效唯一约束测试。
16. 外键不存在CASCADE测试。
17. 关键索引测试。
18. OpenAPI文件解析测试。
19. OpenAPI健康接口测试。
20. OpenAPI字符串ID组件测试。
21. 存储路径不能逃逸根目录测试。
22. 未配置MySQL测试库时集成测试明确skip。
23. 现有AI模块仍可导入。
24. 现有后端测试不得因DMS初始化被破坏。

--------------------------------------------------
十八、验证要求
--------------------------------------------------

必须真实执行并报告：

1. Python模块导入检查。
2. pytest测试。
3. Flask应用创建或导入检查。
4. Alembic配置检查。
5. OpenAPI YAML解析检查。
6. SQLAlchemy metadata检查。
7. 如果存在可用MySQL测试库，再执行upgrade和downgrade。
8. 如果没有MySQL测试库，明确说明未执行真实迁移。

不得因为缺少MySQL而使用SQLite结果冒充MySQL迁移验证。

--------------------------------------------------
十九、验收标准
--------------------------------------------------

B1完成必须满足：

1. 现有AI代码和接口行为不变。
2. /api/v1独立注册。
3. 七张表模型完整。
4. 初始迁移与模型一致。
5. active_marker唯一性真实进入迁移。
6. sys_audit_log符合追加写例外。
7. 文件存储目录安全保留。
8. 统一响应、错误和requestId可验证。
9. OpenAPI公共骨架可解析。
10. 没有提前实现业务接口。
11. 没有恢复接口。
12. 没有废止路径。
13. 没有第八张业务表。
14. 测试结果真实可复查。

--------------------------------------------------
二十、完成报告格式
--------------------------------------------------

完成后必须返回：

1. 阶段结论
   - B1完成、部分完成或阻塞

2. 契约加载情况
   - 确认完整读取了哪些契约文件

3. 修改文件清单
   - 每个文件说明用途

4. 模块结构
   - 展示backend/dms目录

5. 七张表摘要
   - 主要字段
   - 索引
   - 唯一约束
   - 外键
   - 逻辑删除
   - row_version
   - audit_log例外

6. 与旧指导文档相比的调整
   - auth_version
   - attachment_type
   - active_marker
   - 子方案动态继承权限
   - 附件固定PUBLIC和ALL_AUTHENTICATED
   - audit_log字段例外
   - 不提供恢复

7. 迁移验证
   - 实际验证了什么
   - 是否连接真实MySQL
   - 未验证什么

8. 测试结果
   - 命令
   - 退出码
   - 通过、失败和跳过数量
   - 失败原因

9. 契约符合性检查
   - 对应DMS_FUNCTION_CONTRACT.md哪些章节
   - 对应DMS_API_CONTRACT.md哪些章节
   - 是否存在偏差
   - 是否增加未授权表、字段、路径或枚举
   - 是否误用了废止接口

10. 未实现内容
    - 明确说明属于B2及以后阶段的内容

11. 风险和待统领裁决事项

12. 下一阶段建议
    - 只提出B2建议，不得自行执行

本轮到B1完成后立即停止，等待统领会话验收。
----------------------------------------------------------
【阶段】B2：认证、当前用户、组织人员查询和本地初始化数据

你是会话3：后端设计与实现工程师。

B1已经通过统领会话验收。本地MySQL已经由统领会话完成建库和初始迁移。

本轮允许修改后端代码，只实现认证闭环、组织树、人员查询、最小开发数据初始化及对应OpenAPI和测试。

本轮不得进入分类、方案、附件、挂载、文档权限和文件上传等后续业务。

一、强制契约

执行前必须完整读取：

1. DMS_FUNCTION_CONTRACT.md
2. DMS_API_CONTRACT.md

补充参考：

3. FUNCTION_AND_API_SPECIFICATION.md
4. CODEX_BACKEND_PERSISTENCE_GUIDE.md

优先级：

用户或统领会话最新要求
→ DMS_FUNCTION_CONTRACT.md
→ DMS_API_CONTRACT.md
→ 两份旧参考文档
→ 当前代码

不得修改两份DMS契约。

发现冲突时停止相关实现并报告，不得自行改变接口。

二、数据库现状

本地MySQL：

- 主机：localhost
- 端口：3306
- 用户名：root
- 密码：root
- 数据库：dms
- MySQL版本：8.4.8
- 当前迁移：0001_initial_schema
- 当前业务表：7张
- sys_user当前为空

本轮运行时使用临时环境变量：

DMS_DATABASE_URL=mysql+pymysql://root:root@localhost:3306/dms?charset=utf8mb4

禁止把root密码写入：

- Python代码
- .env.example
- Git跟踪文件
- 测试源码
- OpenAPI文件

三、本轮接口范围

只实现：

POST /api/v1/auth/login
POST /api/v1/auth/logout
GET  /api/v1/users/me
GET  /api/v1/organizations/tree
GET  /api/v1/users

不得实现其他业务接口。

四、依赖与密码安全

采用：

- PyJWT：JWT签发和验证
- argon2-cffi：Argon2id密码哈希

要求：

1. requirements.txt声明必要依赖。
2. 数据库不得保存明文密码。
3. 日志不得记录密码、密码哈希或完整Token。
4. 用户名或密码错误统一返回401 INVALID_CREDENTIALS。
5. 不得泄露用户名是否存在。
6. JWT密钥必须从DMS_JWT_SECRET读取。
7. DMS_JWT_SECRET为空时，除测试配置外，登录签发应拒绝启动或明确失败。
8. 不得在代码中设置可用于正式环境的默认密钥。

五、JWT契约

Token至少包含：

- sub：字符串用户ID
- username
- roleCode
- authVersion
- iat
- exp
- jti
- iss=dms

有效期：

- keepSignedIn=false：7200秒
- keepSignedIn=true：604800秒

每次鉴权必须：

1. 验证签名。
2. 验证exp。
3. 验证iss。
4. 查询用户。
5. 检查用户未逻辑删除。
6. 检查status=ENABLED。
7. 比较Token authVersion与sys_user.auth_version。
8. 将用户放入请求级认证上下文。

对应错误：

- TOKEN_INVALID
- TOKEN_EXPIRED
- USER_DISABLED
- AUTH_VERSION_MISMATCH

六、登录接口

POST /api/v1/auth/login

请求严格使用：

{
  "username": "admin",
  "password": "******",
  "keepSignedIn": true
}

登录成功：

1. 校验输入。
2. 查询有效用户。
3. 使用Argon2id验证密码。
4. 更新最近登录时间和IP。
5. 签发JWT。
6. 写LOGIN成功审计。
7. 返回DMS_API_CONTRACT.md定义的响应。
8. ID必须返回字符串。

登录失败：

1. 统一返回INVALID_CREDENTIALS。
2. 写LOGIN失败审计。
3. 未知用户名时userId可以为空。
4. 失败审计使用独立短事务。
5. 不更新登录信息。

allowedModules固定映射：

USER：
- DOCUMENT_BROWSER

ADMIN：
- DOCUMENT_BROWSER
- BACKEND_MANAGEMENT

AUDITOR：
- AUDIT_LOG

不得由前端自行推断或覆盖。

七、退出接口

POST /api/v1/auth/logout

要求：

1. 必须认证。
2. 在事务中锁定当前用户。
3. auth_version递增。
4. row_version递增。
5. 写LOGOUT成功审计。
6. 同一事务提交。
7. 返回统一成功响应。
8. 旧Token在退出响应后立即失效。
9. 第一阶段明确为“一个终端退出导致该用户全部Token失效”。

八、当前用户接口

GET /api/v1/users/me

要求：

- 必须认证；
- 返回UserSummary；
- 所有ID为字符串；
- 包含allowedModules；
- 不返回password_hash；
- 不返回auth_version；
- 不返回内部数据库字段；
- organizationId和organizationName来自有效组织数据或受控冗余。

九、组织树

GET /api/v1/organizations/tree

本阶段仅ADMIN可调用。

支持：

- keyword
- status

要求：

1. 默认只返回有效、未删除组织。
2. 按sortNo及稳定次级顺序排序。
3. 返回树形children。
4. keyword只匹配组织名称或编码。
5. 搜索结果必须保留必要祖先路径，使前端仍可展示树。
6. ID为字符串。
7. 不返回内部绝对路径或数据库对象。

十、人员查询

GET /api/v1/users

本阶段仅ADMIN可调用。

支持：

- organizationId
- includeDescendants
- keyword
- status
- page
- pageSize

要求：

1. 返回契约分页结构。
2. keyword搜索username和realName。
3. includeDescendants=false时只查指定组织。
4. includeDescendants=true时包含有效下级组织。
5. 不返回密码哈希和auth_version。
6. 所有ID为字符串。
7. pageSize最大100。
8. 默认过滤逻辑删除数据。

十一、Bearer认证基础设施

完成B1认证骨架：

- bearer_required装饰器或等价机制
- current_user/current_auth_context
- 统一401和403
- requestId贯穿
- 不影响旧/api/*接口
- 不把认证自动套到/api/v1/health
- 只保护B2新增接口

十二、审计

本轮实现：

- LOGIN成功
- LOGIN失败
- LOGOUT成功

要求：

1. 保存用户名、姓名、组织名称快照。
2. 保存clientIp、userAgent、requestId。
3. 不保存密码和Token。
4. 登录成功和退出审计与对应修改处于同一事务。
5. 登录失败审计使用独立短事务。
6. sys_audit_log仍为追加写。
7. 不实现审计查询接口。

十三、开发数据初始化

实现显式、可重复执行的开发数据初始化命令。

不得在应用启动或Alembic迁移时自动插入默认账号。

建议形式：

python -m dms.seed_dev

或等价Flask CLI命令。

要求：

1. 只有显式执行才写数据。
2. 密码通过环境变量传入。
3. 缺少密码变量时拒绝执行。
4. 使用Argon2id生成哈希。
5. 初始化操作幂等。
6. 不重复创建组织和用户。
7. 默认不得覆盖已有用户密码。
8. 提供显式覆盖开关时必须谨慎说明。
9. 不把开发密码写入源码或提交文件。

初始化以下最小数据：

组织：

- ORG_ROOT / 机关
- ORG_OPS / 作战部
- ORG_COMMS / 通信部

其中作战部、通信部属于机关。

用户：

- admin / ADMIN / TOP_SECRET / 机关
- auditor / AUDITOR / TOP_SECRET / 机关
- user / USER / SECRET / 作战部

本地执行初始化时，可以只在命令环境中使用：

DMS_SEED_ADMIN_PASSWORD=Admin@123456
DMS_SEED_AUDITOR_PASSWORD=Auditor@123456
DMS_SEED_USER_PASSWORD=User@123456

这些仅为本地开发凭据，不得写入Git跟踪文件，完成报告中必须明确提示后续环境更换。

十四、OpenAPI

同步补充：

- POST /api/v1/auth/login
- POST /api/v1/auth/logout
- GET /api/v1/users/me
- GET /api/v1/organizations/tree
- GET /api/v1/users

必须完整声明：

- operationId
- 请求DTO
- 响应DTO
- Bearer安全要求
- 查询参数
- 字符串ID
- 枚举
- 400/401/403/500
- 分页结构

登录接口和health不声明Bearer要求。

不得定义B3及以后路径。

十五、MySQL测试

禁止在dms主库中执行破坏性测试或downgrade。

如需迁移升级、回滚集成测试：

1. 创建独立测试库，例如dms_b2_test。
2. 测试库名称必须以_test结尾。
3. 使用DMS_TEST_DATABASE_URL。
4. 测试完成后可以保留测试库供后续阶段使用。
5. 不得对dms执行downgrade。
6. 不得删除dms。

B2完成后，在真实dms库执行：

- 当前迁移检查
- 开发数据初始化
- 登录成功验证
- /users/me验证
- 组织树验证
- 人员分页验证
- 退出后旧Token失效验证

十六、测试要求

至少覆盖：

1. Argon2id密码哈希和验证。
2. 登录成功。
3. 用户名错误。
4. 密码错误。
5. 禁用用户。
6. keepSignedIn两种有效期。
7. JWT签名错误。
8. JWT过期。
9. authVersion不一致。
10. 缺少Bearer。
11. 非Bearer认证头。
12. /users/me。
13. allowedModules映射。
14. 退出auth_version递增。
15. 退出后旧Token失效。
16. 登录成功审计。
17. 登录失败审计。
18. 退出审计。
19. 审计不包含密码和Token。
20. 组织树结构。
21. 组织关键词保留祖先。
22. 人员分页。
23. includeDescendants。
24. ADMIN权限。
25. USER和AUDITOR不能调用管理查询。
26. JSON ID均为字符串。
27. OpenAPI解析和路径契约。
28. 旧/api/*行为不变。
29. 现有AI测试不受影响。

十七、禁止事项

- 不修改前端。
- 不修改两份DMS契约。
- 不实现分类接口。
- 不实现文档、附件、挂载、权限、文件接口。
- 不实现审计查询。
- 不增加第八张业务表。
- 不增加刷新Token。
- 不增加会话表。
- 不实现动态角色。
- 不修改backend/src/**。
- 不提交或推送Git。
- 不进入B3。

十八、完成报告

必须返回：

1. 阶段结论。
2. 契约加载情况。
3. 修改文件清单。
4. JWT设计和claims。
5. 密码哈希方案。
6. auth_version处理。
7. allowedModules映射。
8. 组织和人员查询行为。
9. 初始化数据和执行方式。
10. 实际MySQL验证结果。
11. OpenAPI变化。
12. 测试命令、通过数、失败数、跳过数。
13. 契约符合性检查。
14. 是否存在偏差。
15. 尚未实现内容。
16. B3建议，但不得执行。

本轮完成B2后停止，等待统领会话验收。
--------------------------------------------
【阶段】B2-R1：修正无测试环境变量时的测试失败

你是会话3：后端设计与实现工程师。

本轮是B2收尾修正，不得进入B3。

一、强制读取

完整读取：

1. DMS_FUNCTION_CONTRACT.md
2. DMS_API_CONTRACT.md

不得修改契约。

二、问题

统领会话独立执行：

python -m pytest -q tests/dms test_annotation_repair.py

在未设置DMS测试环境变量时出现：

KeyError: DMS_TEST_USER_PASSWORD

位置：

backend/tests/dms/conftest.py
b2_password fixture

预期行为：

- 未配置真实MySQL测试环境变量时，依赖这些变量的集成测试应该明确skip；
- 不应产生KeyError或测试错误；
- 普通B1基础测试和不依赖数据库的单元测试仍应执行。

三、本轮工作

1. 修正b2_password或相关fixture。
2. 缺少DMS_TEST_USER_PASSWORD时使用pytest.skip并给出明确原因。
3. 检查DMS_TEST_DATABASE_URL、DMS_TEST_JWT_SECRET等fixture是否存在同类问题。
4. 不允许使用dms主库运行破坏性测试。
5. 不修改认证业务实现。
6. 不修改数据库模型和迁移。
7. 不修改前端。
8. 不增加业务接口。
9. 不进入B3。
10. 不提交或推送Git。

四、必须验证两种模式

模式一：不设置测试环境变量

执行：

python -m pytest -q tests/dms test_annotation_repair.py

要求：

- 退出码0；
- 允许明确skip；
- 不允许error；
- 不允许KeyError。

模式二：设置完整测试环境

使用：

DMS_TEST_DATABASE_URL=mysql+pymysql://root:root@localhost:3306/dms_b2_test?charset=utf8mb4
DMS_TEST_USER_PASSWORD=TestUser@123456
DMS_TEST_JWT_SECRET=local-test-jwt-secret-not-for-production

执行相同测试。

要求：

- 89项测试全部通过；
- 不连接或清理dms主库；
- 不执行主库downgrade。

五、完成报告

返回：

1. 修改文件。
2. 根因。
3. 修正方式。
4. 无环境变量测试结果。
5. 完整环境变量测试结果。
6. 是否修改业务代码。
7. 契约符合性检查。

完成B2-R1后停止。
--------------------------------------
【统领裁决】B3-C1：分类操作人名称字段

统领会话已经完成裁决。

选择方案2：

不为doc_category增加created_by_name和updated_by_name字段，不创建新的数据库迁移。

一、裁决依据

1. DMS_FUNCTION_CONTRACT.md未强制要求分类记录保存操作人名称。
2. DMS_API_CONTRACT.md中的CategoryNode不包含createdByName和updatedByName。
3. doc_category现有created_by和updated_by足以保存操作人ID。
4. 不可变的用户、姓名和组织名称快照由sys_audit_log负责。
5. 为非接口字段增加迁移不符合当前七表最小设计原则。
6. 原B3提示词中的名称快照要求属于提示词过度约束，不是数据库模型缺陷。

二、B3要求调整

撤销原B3提示词中的以下要求：

- 分类新增时在doc_category保存createdByName。
- 分类新增时在doc_category保存updatedByName。
- 分类编辑时在doc_category更新updatedByName。
- 分类删除时在doc_category更新updatedByName。
- 因上述名称字段创建数据库迁移。

调整为：

分类新增：

- created_by保存当前用户ID。
- updated_by保存当前用户ID。
- created_at和updated_at按模型规则维护。
- CREATE_CATEGORY审计保存username、real_name和organization_name快照。

分类编辑：

- updated_by保存当前用户ID。
- updated_at更新。
- row_version递增。
- EDIT_CATEGORY审计保存用户、姓名、组织和分类名称快照。

分类删除：

- updated_by保存当前用户ID。
- updated_at和deleted_at更新。
- row_version递增。
- DELETE_CATEGORY审计保存用户、姓名、组织和分类名称快照。

三、禁止事项

1. 不增加created_by_name。
2. 不增加updated_by_name。
3. 不创建0002迁移。
4. 不修改现有0001迁移。
5. 不修改两份DMS契约。
6. 不在CategoryNode中增加操作人名称字段。
7. 不通过关联查询临时增加未定义API字段。
8. 不进入B4。

四、继续执行

请从B3中断位置继续执行原B3任务。

除本裁决明确调整的名称快照要求外，原B3提示词其他内容继续有效，包括：

- 四个分类接口；
- 分类树搜索；
- 分类新增、编辑、移动；
- 后代路径同步；
- 删除占用冲突；
- rowVersion并发控制；
- 分类审计；
- 开发分类数据初始化；
- OpenAPI；
- 自动化测试；
- 真实MySQL验证。

五、完成报告补充要求

B3最终报告增加“统领裁决执行情况”章节，明确说明：

1. doc_category只保存created_by和updated_by。
2. 操作人名称快照保存在sys_audit_log。
3. 未新增数据库字段。
4. 未创建新迁移。
5. 主库迁移仍为0001_initial_schema。
6. 该处理属于统领授权，不计为契约偏差。

现在继续执行B3，完成后停止，等待统领会话验收。
-------------------------------------------------------
【阶段】B4：主案、子方案、共享附件和挂载关系只读接口

你是“会话3：后端设计与实现工程师”。

B1、B2、B2-R1、B3均已经通过统领会话验收。

本轮只实现：

1. 主案和子方案分页查询
2. 主案、子方案详情
3. 主案直属子方案查询
4. 共享附件分页查询
5. 共享附件详情
6. 附件挂载主案查询
7. 主案已挂载附件查询
8. 文档可见性、密级、状态和allowedActions计算
9. 开发演示文档数据初始化
10. OpenAPI和自动化测试

不得进入文档上传、编辑、删除、附件挂载写入、权限保存、文件预览下载、审计查询和统计接口。

--------------------------------------------------
一、强制契约
--------------------------------------------------

执行前完整读取：

1. DMS_FUNCTION_CONTRACT.md
2. DMS_API_CONTRACT.md

补充参考：

3. FUNCTION_AND_API_SPECIFICATION.md
4. CODEX_BACKEND_PERSISTENCE_GUIDE.md

优先级：

用户或统领会话最新明确要求
→ DMS_FUNCTION_CONTRACT.md
→ DMS_API_CONTRACT.md
→ 两份旧参考文档
→ 当前代码

不得修改两份DMS契约。

发现模型缺口或契约冲突时：

1. 停止相关实现。
2. 不得自行新增字段、表或迁移。
3. 报告具体缺口。
4. 等待统领会话裁决。

--------------------------------------------------
二、测试数据库统一规则
--------------------------------------------------

从B4开始，所有后端阶段统一使用：

主库：

dms

测试库：

dms_test

本地连接：

DMS_DATABASE_URL=mysql+pymysql://root:root@localhost:3306/dms?charset=utf8mb4

DMS_TEST_DATABASE_URL=mysql+pymysql://root:root@localhost:3306/dms_test?charset=utf8mb4

强制规则：

1. 不得创建dms_b4_test。
2. 不得创建其他以阶段命名的新测试库。
3. 如果dms_test不存在，只创建一次。
4. 每个阶段开始前执行alembic upgrade head。
5. 测试只清理dms_test数据。
6. 禁止在dms主库执行测试清表。
7. 禁止对dms主库执行downgrade。
8. 禁止删除dms主库。
9. 已有dms_b2_test、dms_b3_test不得在本轮删除或修改。
10. 后续B5、B6等继续复用dms_test。
11. 测试数据使用明确前缀，例如B4_TEST_*。
12. 测试结束时清理测试库中的测试数据，或者通过可靠测试隔离恢复基线。

如果使用事务回滚进行隔离，必须确认MySQL和Flask-SQLAlchemy会话行为真实有效；不得用SQLite结果代替MySQL验证。

--------------------------------------------------
三、当前运行环境
--------------------------------------------------

MySQL：

- 主机：localhost
- 端口：3306
- 用户：root
- 密码：root
- 主库：dms
- MySQL版本：8.4.8
- 当前迁移：0001_initial_schema

后端：

http://localhost:8754

本地开发用户：

- admin / ADMIN / TOP_SECRET
- auditor / AUDITOR / TOP_SECRET
- user / USER / SECRET

本地密码只通过环境变量或本地执行命令提供，不得写入源码、OpenAPI或Git文件。

--------------------------------------------------
四、本轮接口范围
--------------------------------------------------

只实现：

GET /api/v1/documents

GET /api/v1/documents/{id}

GET /api/v1/main-plans/{id}/sub-plans

GET /api/v1/attachments

GET /api/v1/attachments/{id}

GET /api/v1/attachments/{id}/main-plans

GET /api/v1/main-plans/{id}/attachments

不得实现：

POST /documents
PUT /documents/{id}
DELETE /documents/{id}
POST /documents/batch-import

POST /attachments
PUT /attachments/{id}
DELETE /attachments/{id}
POST /attachments/batch-import

POST /main-plans/{id}/attachments/bind
DELETE /main-plans/{id}/attachments/{attachmentId}

GET或PUT文档权限
文件preview或download
审计查询
统计查询
恢复接口
任何废止路径

--------------------------------------------------
五、服务结构
--------------------------------------------------

建议新增：

backend/dms/services/document_query_service.py
backend/dms/services/attachment_query_service.py
backend/dms/services/authorization_service.py

backend/dms/api/v1/documents.py
backend/dms/api/v1/attachments.py
backend/dms/api/v1/main_plans.py

可以根据现有结构合理调整。

要求：

1. 路由层只处理HTTP输入输出。
2. 查询和业务过滤位于service。
3. 权限、密级、状态和allowedActions集中计算。
4. 不在各路由复制权限逻辑。
5. 不在路由中直接拼接复杂SQL。
6. 不修改现有认证、分类和AI模块行为。
7. 不增加空repository或无意义抽象。

--------------------------------------------------
六、访问与权限判定
--------------------------------------------------

方案访问顺序严格为：

1. 用户已经认证。
2. 用户状态为ENABLED。
3. 文档未逻辑删除。
4. 文档类型正确。
5. 文档状态检查。
6. 用户密级大于或等于文档密级。
7. ADMIN业务特权。
8. visibilityType。
9. doc_permission允许记录。
10. 计算具体allowedActions。

密级比较值：

- PUBLIC=10
- INTERNAL=20
- SECRET=30
- CONFIDENTIAL=40
- TOP_SECRET=50

状态规则：

普通USER：

- 只允许查询PUBLISHED方案。
- DRAFT和ARCHIVED不可见。
- 仍需通过密级和ACL。

ADMIN：

- 可以查询DRAFT、PUBLISHED和ARCHIVED。
- 仍需通过密级检查。
- 开发admin为TOP_SECRET，因此能够查看全部开发数据。

AUDITOR：

- 审计角色不自动获得主案和子方案访问权。
- GET /documents、文档详情和子方案接口默认返回403。
- 共享附件仍按“全部有效登录用户共享”规则允许查看元数据。

--------------------------------------------------
七、可见范围
--------------------------------------------------

ALL_AUTHENTICATED：

- 通过认证、状态、文档状态和密级检查后可见。
- 不要求doc_permission记录。

ORGANIZATION：

- 只使用ORG类型允许权限。
- 用户属于授权组织自身或其有效下级组织时可见。
- 不使用USER权限记录。
- 组织上下级关系必须通过有效组织树判断。

CUSTOM：

- 使用ORG和USER允许权限。
- 用户直接允许或所属组织命中时可见。
- ORG和USER取并集。
- 第一阶段没有显式拒绝。

没有匹配允许权限时拒绝访问。

SUB_PLAN：

- 不读取自身doc_permission。
- 动态继承root_document_id所指向主案的visibilityType和ACL。
- 子方案仍使用自身securityLevel和documentStatus。
- root主案无效时，子方案不可见并记录系统一致性问题。

ATTACHMENT：

- 不读取doc_permission。
- 固定PUBLIC。
- 固定ALL_AUTHENTICATED。
- 所有ENABLED登录用户可查询。

--------------------------------------------------
八、allowedActions
--------------------------------------------------

返回值只能使用契约枚举。

USER对MAIN、SUB_PLAN：

- 具有查看权限时返回VIEW。
- 具有下载权限时返回DOWNLOAD。
- 不返回EDIT、DELETE、CONFIG_PERMISSION。
- 不返回BIND_ATTACHMENT或UNBIND_ATTACHMENT。

ADMIN对MAIN：

- VIEW
- DOWNLOAD
- EDIT
- CONFIG_PERMISSION
- DELETE
- BIND_ATTACHMENT
- 如果当前主案存在有效附件挂载，可返回UNBIND_ATTACHMENT。

ADMIN对SUB_PLAN：

- VIEW
- DOWNLOAD
- EDIT
- DELETE
- 不返回CONFIG_PERMISSION。
- 不返回BIND_ATTACHMENT和UNBIND_ATTACHMENT。

共享附件：

普通有效登录用户：

- VIEW
- DOWNLOAD

ADMIN额外：

- EDIT
- DELETE

注意：

本轮尚未实现文件下载和写操作接口。

allowedActions表达业务授权能力，不代表本轮已经实现对应HTTP写接口。OpenAPI和完成报告必须明确当前只开放查询接口。

--------------------------------------------------
九、文档分页查询
--------------------------------------------------

实现：

GET /api/v1/documents

只查询：

- MAIN
- SUB_PLAN

不得通过该接口查询ATTACHMENT。

支持参数：

- documentType
- categoryId
- keyword
- securityLevel
- visibilityType
- status
- updatedFrom
- updatedTo
- sortBy
- sortDirection
- page
- pageSize

documentType：

- 支持MAIN
- 支持SUB_PLAN
- 支持逗号分隔MAIN,SUB_PLAN
- 包含ATTACHMENT时返回400 INVALID_ARGUMENT
- 不传时默认MAIN,SUB_PLAN

keyword：

搜索：

- documentName
- summary
- tags
- searchText

使用MySQL普通检索，不调用AI、Milvus或向量服务。

categoryId：

- 必须是有效数字字符串。
- 按指定分类精确筛选。
- 本阶段不自动包含下级分类，除非契约已有明确规定。
- 不存在分类可以返回空列表，不必泄露分类状态。

分页：

- page默认1
- pageSize默认20
- pageSize最大100

sortBy只允许：

- documentName
- createdAt
- updatedAt
- viewCount
- downloadCount

sortDirection：

- asc
- desc

必须使用白名单，禁止把客户端字段直接拼入SQL。

返回：

PageResult<DocumentSummary>

所有ID使用字符串。

普通USER必须在SQL查询或可靠服务层过滤中排除无权记录，不能先返回再依赖前端隐藏。

--------------------------------------------------
十、文档详情
--------------------------------------------------

实现：

GET /api/v1/documents/{id}

仅适用于：

- MAIN
- SUB_PLAN

ATTACHMENT调用该接口时：

- 返回404 RESOURCE_NOT_FOUND，或按契约既定方式处理；
- 不得通过此接口返回附件详情。

规则：

1. 检查文档有效性。
2. 检查文档类型。
3. 检查状态。
4. 检查密级。
5. 检查可见范围和ACL。
6. 返回DocumentDetail。
7. 不返回绝对文件路径。
8. 不返回password、Token或内部权限查询细节。
9. fileRelativePath不得对外返回。
10. ID全部为字符串。
11. permissionSummary按契约返回。
12. 子方案permissionSummary标记动态继承主案。

查看行为：

- 成功查询详情视为VIEW_DOCUMENT。
- 使用独立短事务更新view_count。
- 写VIEW_DOCUMENT审计。
- 审计失败按功能契约既定规则处理。
- 不在数据库事务中执行长时间文件操作。
- 本轮不读取磁盘文件。

列表查询不增加view_count，不写逐条查看日志。

--------------------------------------------------
十一、主案子方案
--------------------------------------------------

实现：

GET /api/v1/main-plans/{id}/sub-plans

规则：

1. id必须指向有效MAIN。
2. 主案不存在或类型不正确返回404 MAIN_PLAN_NOT_FOUND。
3. 先检查当前用户对主案的访问资格。
4. 返回直属SUB_PLAN。
5. 不允许继续嵌套。
6. 子方案按自身状态和密级再次过滤。
7. ACL动态继承主案。
8. 支持keyword、status、page、pageSize、sortBy、sortDirection。
9. 返回PageResult<DocumentSummary>。
10. 不增加主案或子方案view_count。
11. 不写逐条VIEW审计。

--------------------------------------------------
十二、共享附件分页
--------------------------------------------------

实现：

GET /api/v1/attachments

支持：

- keyword
- attachmentType
- fileExtension
- updatedFrom
- updatedTo
- sortBy
- sortDirection
- page
- pageSize

keyword搜索：

- documentName
- summary
- tags
- searchText

规则：

1. 只返回ATTACHMENT。
2. 默认排除逻辑删除。
3. 所有ENABLED登录用户可查询。
4. 不使用doc_permission。
5. securityLevel固定PUBLIC。
6. visibilityType固定ALL_AUTHENTICATED。
7. category相关字段返回null。
8. attachmentType必填。
9. 返回PageResult<DocumentSummary>。
10. mountedPlanCount使用有效挂载关系真实统计。
11. 不增加view_count。
12. 不写逐条查看审计。

--------------------------------------------------
十三、附件详情
--------------------------------------------------

实现：

GET /api/v1/attachments/{id}

规则：

1. 必须是有效ATTACHMENT。
2. 所有ENABLED登录用户可调用。
3. 返回DocumentDetail。
4. category相关字段为null。
5. permissionSummary表达无ACL。
6. 不返回存储绝对路径。
7. 成功详情查询更新view_count。
8. 写VIEW_DOCUMENT审计，targetType=ATTACHMENT。
9. 不读取或返回真实文件流。
10. 非ATTACHMENT返回404。

--------------------------------------------------
十四、附件挂载的主案
--------------------------------------------------

实现：

GET /api/v1/attachments/{id}/main-plans

规则：

1. id必须是有效ATTACHMENT。
2. 只统计有效挂载关系。
3. 只返回有效、未删除MAIN。
4. ADMIN可以查看全部有效挂载主案。
5. 普通USER只能看到自己有权访问的主案。
6. AUDITOR不因附件共享而自动获得主案详情，应只返回其有权访问的主案；默认可能为空。
7. 返回契约定义的简要主案集合。
8. 不返回主案ACL内部细节。
9. 不增加view_count。
10. 不写VIEW审计。

--------------------------------------------------
十五、主案已挂载附件
--------------------------------------------------

实现：

GET /api/v1/main-plans/{id}/attachments

支持：

- keyword
- attachmentType
- page
- pageSize

规则：

1. id必须是有效MAIN。
2. 当前用户必须有权查看主案。
3. 只返回有效挂载关系。
4. 只返回有效ATTACHMENT。
5. 返回附件DocumentSummary并增加：
   - bindingId
   - bindingSortNo
   - mountedPlanCount
6. 按bindingSortNo和bindingId稳定排序。
7. keyword和attachmentType按附件字段过滤。
8. 不增加附件或主案view_count。
9. 不写逐条查看审计。

--------------------------------------------------
十六、DTO输出
--------------------------------------------------

严格遵循DMS_API_CONTRACT.md：

DocumentSummary至少包括：

- id
- documentName
- summary
- documentType
- status
- securityLevel
- visibilityType
- visibilitySummary
- categoryId
- categoryName
- categoryPath
- parentDocumentId
- rootDocumentId
- attachmentType
- fileExtension
- tags
- childCount
- attachmentCount
- viewCount
- downloadCount
- createdByName
- createdAt
- updatedAt
- rowVersion
- allowedActions

DocumentDetail增加：

- originalFileName
- mimeType
- fileSize
- fileHash
- permissionSummary

要求：

1. JSON字段使用camelCase。
2. ID全部使用字符串。
3. 数据库BIGINT不能直接输出为number。
4. 不返回fileRelativePath。
5. 不返回createdBy、updatedBy内部ID，除非契约明确要求。
6. 不返回isDeleted、deletedAt。
7. 不返回searchText。
8. 不返回authVersion。
9. tags无值时返回空数组，不返回无法预测的类型。
10. allowedActions去重并保持稳定顺序。

--------------------------------------------------
十七、开发演示数据初始化
--------------------------------------------------

为了F4能够替换模拟文档，提供显式、幂等的开发数据初始化命令。

建议：

python -m dms.seed_documents

不得在应用启动或Alembic迁移时自动执行。

初始化至少包括：

主案：

1. 2024年度综合应急预案
   - MAIN
   - 分类SCENE_A
   - PUBLISHED
   - INTERNAL
   - ALL_AUTHENTICATED

2. 战略物资管理规程
   - MAIN
   - 分类SCENE_B
   - PUBLISHED
   - SECRET
   - ORGANIZATION
   - 授权作战部

3. 通信保障方案
   - MAIN
   - 分类SCENE_C
   - PUBLISHED
   - SECRET
   - CUSTOM
   - 直接授权user查看和下载

第一个主案的子方案：

- 人员组织子案
- 资源组织子案
- 重点任务子案

共享附件至少三个：

- 装备保障工作规范 / WORK_STANDARD
- 应急资源清单模板 / TABLE
- 通信保障流程图 / DIAGRAM

挂载关系：

- 2024年度综合应急预案挂载前两个附件
- 战略物资管理规程挂载装备保障工作规范
- 用于验证一个附件挂载多个主案

初始化要求：

1. 显式执行。
2. 幂等。
3. 不重复创建。
4. 不覆盖已有同名有效业务数据。
5. 不恢复已逻辑删除数据。
6. 正确维护child_count。
7. 正确维护attachment_count。
8. 正确维护mountedPlanCount查询结果。
9. 正确创建MAIN权限。
10. SUB_PLAN不创建权限副本。
11. ATTACHMENT不创建ACL。
12. searchText正确生成。
13. 创建真实有效的最小DOCX演示文件。
14. 文件放入backend/dms-storage/original/约定路径。
15. 数据库只保存相对路径。
16. 使用UUID磁盘文件名。
17. 原始文件名保存在数据库。
18. 生成SHA-256。
19. 生成的DOCX必须能被python-docx重新打开。
20. 不创建空文件或不存在路径记录。
21. 不调用AI、Milvus或向量化。
22. 文件创建失败时补偿数据库记录。
23. 数据库失败时清理本次新建的孤儿文件。
24. 不修改用户已有文件。
25. 输出新增、已存在和失败数量。

该命令属于开发数据工具，不是上传业务接口。

完成后在真实dms主库显式执行一次。

--------------------------------------------------
十八、OpenAPI
--------------------------------------------------

新增且只新增本轮7个查询接口。

必须包含：

- operationId
- Bearer认证
- 查询参数
- PageResult
- DocumentSummary
- DocumentDetail
- AttachmentBindingSummary
- 字符串ID
- allowedActions
- 200、400、401、403、404、500
- MAIN_PLAN_NOT_FOUND
- DOCUMENT_VIEW_FORBIDDEN
- SECURITY_LEVEL_FORBIDDEN
- RESOURCE_NOT_FOUND

不得提前加入上传、编辑、删除、挂载写、权限和文件路径。

OpenAPI路径和Flask路由必须完全一致。

外部$ref必须有效。

--------------------------------------------------
十九、测试要求
--------------------------------------------------

至少覆盖：

1. documents未认证。
2. USER文档分页。
3. ADMIN文档分页。
4. AUDITOR文档分页403。
5. MAIN筛选。
6. SUB_PLAN筛选。
7. MAIN,SUB_PLAN组合筛选。
8. ATTACHMENT类型参数拒绝。
9. 分类筛选。
10. keyword搜索名称。
11. keyword搜索摘要或标签。
12. 状态过滤。
13. 密级过滤。
14. 可见范围过滤。
15. 非法sortBy拒绝。
16. pageSize上限。
17. 字符串ID。
18. 逻辑删除过滤。
19. DRAFT对USER不可见。
20. DRAFT对ADMIN可见。
21. 密级不足拒绝。
22. ALL_AUTHENTICATED。
23. ORGANIZATION及下级组织。
24. CUSTOM用户授权。
25. 无ACL拒绝。
26. 子方案动态继承。
27. 文档详情。
28. 附件不能通过documents详情获取。
29. 详情增加view_count。
30. 详情写审计。
31. 列表不增加view_count。
32. 主案子方案分页。
33. 非主案调用子方案接口。
34. 子方案自身密级过滤。
35. attachments未认证。
36. USER附件分页。
37. AUDITOR附件分页。
38. ADMIN附件分页。
39. attachmentType过滤。
40. fileExtension过滤。
41. 附件详情。
42. 非附件调用附件详情。
43. 附件详情增加view_count和审计。
44. 附件挂载主案查询。
45. USER只能看到有权主案。
46. 主案挂载附件查询。
47. bindingId字符串。
48. bindingSortNo排序。
49. mountedPlanCount。
50. allowedActions稳定。
51. 响应不泄露fileRelativePath。
52. tags始终为数组。
53. 开发初始化幂等。
54. 开发文件真实存在。
55. DOCX可打开。
56. SHA-256正确。
57. child_count一致。
58. attachment_count一致。
59. 权限记录符合规则。
60. 测试只使用dms_test。
61. 无环境变量时skip而非error。
62. B2、B3接口回归。
63. OpenAPI和路由一致。
64. 旧AI接口回归。

--------------------------------------------------
二十、验证要求
--------------------------------------------------

执行：

python -m compileall -q dms

无测试环境变量：

python -m pytest -q tests/dms test_annotation_repair.py

完整测试环境：

DMS_TEST_DATABASE_URL=mysql+pymysql://root:root@localhost:3306/dms_test?charset=utf8mb4
DMS_TEST_USER_PASSWORD=TestUser@123456
DMS_TEST_JWT_SECRET=local-test-jwt-secret-not-for-production

python -m pytest -q tests/dms test_annotation_repair.py

真实主库只执行：

1. alembic current
2. seed_documents
3. 七个查询接口验证
4. 三类账号权限验证
5. 计数一致性检查
6. 文件存在性和哈希检查

不得在主库运行测试清理或downgrade。

--------------------------------------------------
二十一、禁止事项
--------------------------------------------------

- 不修改前端。
- 不修改DMS契约。
- 不创建新业务表。
- 不创建阶段专属测试数据库。
- 不实现上传。
- 不实现编辑和删除。
- 不实现附件挂载写操作。
- 不实现权限保存。
- 不实现文件预览下载。
- 不实现审计查询和统计。
- 不实现恢复接口。
- 不调用AI或Milvus。
- 不修改backend/src/**。
- 不提交或推送Git。
- 不进入B5。

--------------------------------------------------
二十二、完成报告
--------------------------------------------------

必须返回：

1. 阶段结论。
2. 契约加载情况。
3. 修改文件清单。
4. 七个查询接口。
5. 权限判定顺序。
6. 密级和状态过滤。
7. 子方案动态继承。
8. allowedActions。
9. 分页、搜索和排序。
10. 详情查看计数和审计。
11. 共享附件查询。
12. 挂载关系查询。
13. 开发演示数据初始化。
14. 真实文件和哈希验证。
15. dms主库验证。
16. dms_test统一测试库使用情况。
17. 测试结果。
18. OpenAPI变化。
19. 回归测试。
20. 契约符合性。
21. 是否存在偏差。
22. 未实现内容。
23. B5建议，但不得执行。

完成B4后立即停止，等待统领会话验收。
--------------------------------------------------
【阶段任务】B5：文档与共享附件的上传、批量导入、元数据编辑、逻辑删除及文件读取

你是本项目的后端设计与实现工程师。本轮只执行 B5，不得进入 B6，不得修改前端。

一、开始前必须加载的约束

开始分析或修改前，必须完整读取：

1. F:\Project\wsj\document-management-system\DMS_FUNCTION_CONTRACT.md
2. F:\Project\wsj\document-management-system\DMS_API_CONTRACT.md

以下文档只能作为背景参考；如有冲突，以上述两份 DMS 契约为准：

3. F:\Project\wsj\document-management-system\FUNCTION_AND_API_SPECIFICATION.md
4. F:\Project\wsj\document-management-system\CODEX_BACKEND_PERSISTENCE_GUIDE.md

同时检查：

5. backend/openapi/**
6. B1—B4 已实现的模型、迁移、存储服务、认证、授权、审计及查询服务
7. backend/dms-storage/**
8. backend/tests/dms/**
9. 当前 Flask 实际路由表
10. 当前 MySQL 迁移状态和七张核心表结构

不得修改上述两份 DMS 契约。

二、已完成并验收的基础

B1—B4、F1—F4 均已通过统领验收。

现有能力包括：

- JWT认证和三类角色；
- 组织、人员查询；
- 分类树和分类管理；
- 主案、子方案、共享附件只读查询；
- 主案直属子方案查询；
- 正向、反向附件挂载关系查询；
- 集中授权判定；
- 真实DOCX文件和SHA-256校验；
- 查看计数和查看审计；
- OpenAPI及自动化测试基础设施。

后端标准地址：

http://127.0.0.1:8754

数据库：

- 业务主库：dms
- 唯一自动化测试库：dms_test

数据库账号：

- 用户名：root
- 密码：root

三、统一测试数据库规则

本轮继续复用：

dms_test

禁止创建：

- dms_b5_test
- dms_b5_verify
- dms_upload_test
- 其他阶段专用测试数据库

历史库 `dms_b2_test`、`dms_b3_test` 不得连接、清理、降级或删除。

自动化测试只能对 `dms_test` 执行测试数据写入、清理和迁移验证。

不得：

- 清理 `dms` 主库；
- downgrade `dms` 主库；
- 删除 `dms` 主库业务数据；
- 使用主库执行自动化测试；
- 为每个阶段创建新测试库。

测试数据库URL继续使用：

mysql+pymysql://root:root@localhost:3306/dms_test?charset=utf8mb4

四、B5范围

本轮只新增以下10个接口。

方案文档：

1. POST /api/v1/documents
2. POST /api/v1/documents/batch-import
3. PUT /api/v1/documents/{id}
4. DELETE /api/v1/documents/{id}

共享附件：

5. POST /api/v1/attachments
6. POST /api/v1/attachments/batch-import
7. PUT /api/v1/attachments/{id}
8. DELETE /api/v1/attachments/{id}

统一文件读取：

9. GET /api/v1/documents/{id}/preview
10. GET /api/v1/documents/{id}/download

本轮不得新增其他业务接口。

五、本轮明确不实现

不得实现：

1. 附件挂载写接口；
2. 解除挂载接口；
3. 权限读取和保存接口；
4. 审计日志查询接口；
5. 统计接口；
6. 文档恢复接口；
7. 恢复页面配套接口；
8. 强制删除已挂载附件；
9. 子方案独立权限；
10. 文档文件替换；
11. 复杂版本历史；
12. 物理删除文档文件；
13. 自动清理逻辑删除文件；
14. Office转PDF；
15. Office在线预览；
16. Milvus或向量化；
17. AI模块改造；
18. frontend/**修改；
19. B6功能；
20. Git提交、推送或合并。

特别禁止实现：

POST /api/v1/documents/{id}/restore

以及任何其他恢复路径。

六、数据库和迁移边界

B5原则上应复用现有七张表和 `0001_initial_schema`。

开始实施前必须核对现有字段是否能够满足：

- 文件元数据；
- SHA-256；
-相对存储路径；
-附件类型；
-状态和密级；
-逻辑删除；
-rowVersion；
-创建人和修改人快照；
-主案子方案计数；
-附件挂载计数；
-下载计数；
-审计日志。

如果发现必须新增数据库字段、索引、约束或迁移：

1. 立即停止依赖该结构的实现；
2. 不得擅自修改 `0001_initial_schema`；
3. 不得擅自创建新迁移；
4. 向统领会话提交明确的模型缺口、影响接口和建议方案；
5. 等待书面裁决。

不得为了避免迁移而复用语义不符的字段。

七、文件存储规则

存储根目录为：

F:\Project\wsj\document-management-system\backend\dms-storage

原始文件存储在：

backend/dms-storage/original/

必须遵守：

1. 数据库只保存相对于 `dms-storage` 的路径。
2. 数据库不得保存绝对路径。
3. 原始文件名仅用于展示和下载文件名。
4. 原始文件名不得参与磁盘路径拼接。
5. 磁盘文件名必须使用后端生成的UUID。
6. 上传文件不得覆盖已有文件。
7. 计算并保存真实SHA-256。
8. 相同SHA-256允许作为不同业务文档上传。
9. 存储目录不得注册为Flask静态资源目录。
10. 所有文件读取必须经过认证、授权和业务状态检查。
11. 文件路径解析后必须仍位于配置的存储根目录。
12. 拒绝绝对路径、`..`、路径穿越和越界符号链接。
13. API响应不得返回：

   - fileRelativePath；
   - 绝对路径；
   - 临时文件路径；
   - 服务器目录结构。

八、允许的文件类型和安全校验

第一阶段只允许：

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

不得只根据扩展名或客户端Content-Type判断。

必须同时校验：

1. 扩展名；
2. 文件头或容器结构；
3. 文件类型与扩展名的一致性；
4. 文件大小；
5. 请求总大小；
6. 批量文件数量；
7. 空文件；
8. 损坏容器。

最低校验要求：

- PDF：验证合法PDF文件头；
- DOC、XLS：验证OLE/CFB文件头；
- DOCX：验证ZIP容器以及必要Office结构；
- XLSX：验证ZIP容器以及必要Office结构；
- DOCX不得只验证ZIP文件头；
- XLSX不得只验证ZIP文件头；
- 不得解压到不受控目录；
- 容器检查应避免ZIP路径穿越；
- 损坏、伪造或类型不匹配文件返回契约规定错误。

应优先复用Python标准库和现有依赖。新增依赖必须必要、最小化，并在报告中说明原因。

九、上传事务和文件补偿

单文件上传必须保证数据库和文件系统最终一致。

建议流程：

1. 校验认证和ADMIN权限；
2. 校验请求结构；
3. 校验metadata；
4. 校验文件大小和类型；
5. 写入受控临时文件；
6. 计算SHA-256；
7. 生成UUID存储名；
8. 建立数据库业务记录；
9. 写入同事务审计；
10. 提交业务事务；
11. 将文件安全落入最终目录或采用等价可靠流程；
12. 清理临时文件。

无论采用哪种顺序，都必须覆盖以下失败补偿：

- 文件写入失败时不留下数据库记录；
- 数据库提交失败时不留下孤立新文件；
- 审计写入失败时业务写入回滚；
- 临时文件必须清理；
- 已存在的其他业务文件不得受影响；
- 不得删除逻辑删除记录对应的原始文件；
- 不得在异常日志中输出文件正文、Token或密码。

需要通过自动化测试验证补偿行为，不能只在代码注释中说明。

十、方案文档单文件上传

实现：

POST /api/v1/documents

仅ADMIN可调用。

请求为 `multipart/form-data`：

- `file`：单个文件；
- `metadata`：JSON字符串。

主案metadata严格按契约处理：

- documentName
- documentType=MAIN
- summary
- categoryId
- securityLevel
- visibilityType
- status
- tags

子方案metadata增加：

- documentType=SUB_PLAN
- parentDocumentId

要求：

1. 严格拒绝未知字段。
2. `categoryId`、`parentDocumentId`为字符串ID。
3. 禁止number ID。
4. 子方案父节点必须是有效、未删除的MAIN。
5. 子方案不得挂到SUB_PLAN。
6. 子方案动态继承根主案权限，不复制ACL。
7. 后端生成：

   - 文件身份字段；
   - fileRelativePath；
   - fileHash；
   - fileSize；
   - fileExtension；
   - mimeType；
   - categoryName；
   - categoryPath；
   - rootDocumentId；
   - searchText；
   - 创建人和修改人快照；
   - 时间和rowVersion。

8. 主案的父文档字段必须为空。
9. 新建子方案后，应在同一业务事务内维护主案 `childCount`及必要版本。
10. 维护分类冗余计数和其他现有冗余字段。
11. 返回 `DocumentDetail`，HTTP 201。
12. 不得返回磁盘路径。
13. 写入 `UPLOAD_DOCUMENT` 审计。
14. 审计与业务记录在同一事务。

十一、方案文档批量导入

实现：

POST /api/v1/documents/batch-import

仅ADMIN可调用。

请求：

- 重复字段 `files`
- `items`：JSON数组字符串
- `files[n]` 与 `items[n]`按顺序一一对应

要求：

1. 数量不一致返回：

   HTTP 400
   code=BATCH_MANIFEST_MISMATCH

2. 检查批量文件数量和请求总大小。
3. 每个文件使用独立业务事务。
4. 一个文件失败不得回滚其他成功文件。
5. 返回项顺序必须与原请求顺序一致。
6. 每项必须包含：

   - index；
   - originalFileName；
   - success；
   - 成功时document；
   - 失败时errorCode和errorMessage。

7. 整体HTTP为200。
8. 统计：

   - total；
   - successCount；
   - failureCount。

9. 单项成功记录真实业务文档和审计。
10. 单项失败必须完成文件补偿。
11. 不得因单项错误泄露堆栈或服务器路径。
12. 写入契约定义的 `BATCH_IMPORT`/`UPLOAD_DOCUMENT`审计语义，不得增加未知动作枚举。
13. 批量重试不得误覆盖已有文件。

十二、方案文档元数据编辑

实现：

PUT /api/v1/documents/{id}

仅ADMIN可调用，只编辑元数据，不替换文件。

请求字段严格按契约：

- documentName
- summary
- categoryId
- securityLevel
- visibilityType
- status
- tags
- rowVersion

禁止修改：

- documentType；
- parentDocumentId；
- rootDocumentId；
- originalFileName；
- fileRelativePath；
- fileExtension；
- mimeType；
- fileSize；
- fileHash；
- 文件正文。

要求：

1. 文档必须是MAIN或SUB_PLAN。
2. 附件调用此接口必须返回类型错误。
3. 校验分类有效性和分类类型规则。
4. 使用rowVersion乐观锁。
5. 版本不一致返回：

   HTTP 409
   code=DATA_VERSION_CONFLICT
   details.currentRowVersion

6. 更新分类时同步categoryName和categoryPath。
7. 同步维护searchText。
8. 同步维护updatedBy、updatedByName、updatedAt和rowVersion。
9. 不得因编辑子方案创建独立ACL。
10. 元数据编辑和 `EDIT_DOCUMENT`审计处于同一事务。
11. 返回最新 `DocumentDetail`。
12. 编辑操作不得增加viewCount或downloadCount。

十三、方案文档逻辑删除

实现：

DELETE /api/v1/documents/{id}?rowVersion=<integer>

仅ADMIN可调用。

要求：

1. 只用于MAIN和SUB_PLAN。
2. 使用rowVersion乐观锁。
3. 不得物理删除数据库记录。
4. 不得物理删除原始文件。
5. 删除后正常查询不可见。
6. 共享附件不得通过该接口删除。

删除MAIN：

- 存在有效直属或间接子方案时返回：
  HTTP 409
  code=MAIN_PLAN_HAS_CHILDREN
- 不递归删除子方案；
- 逻辑删除该主案有效ACL；
- 逻辑删除该主案有效附件挂载关系；
- 不删除共享附件；
- 同步维护必要冗余计数和版本。

删除SUB_PLAN：

- 逻辑删除子方案；
- 更新所属主案childCount；
- 不影响主案ACL；
- 不影响共享附件；
- 不删除挂载文件。

成功返回：

- 字符串id；
- deleted=true。

逻辑删除及关联关系处理、计数维护和 `DELETE_DOCUMENT`审计必须在同一事务。

十四、共享附件单文件上传

实现：

POST /api/v1/attachments

仅ADMIN可调用。

请求为 `multipart/form-data`：

- file
- metadata

metadata严格包含契约允许字段：

- documentName
- attachmentType
- summary
- tags

后端强制设置：

- documentType=ATTACHMENT
- securityLevel=PUBLIC
- visibilityType=ALL_AUTHENTICATED
- categoryId=null
- parentDocumentId=null
- rootDocumentId=null

要求：

1. 前端即使提交固定字段，也不得允许其覆盖后端强制值。
2. 附件不得创建ACL。
3. 计算并保存真实文件元数据和SHA-256。
4. 新附件mountedPlanCount初始为0。
5. 返回 `DocumentDetail`，HTTP 201。
6. 写入 `UPLOAD_DOCUMENT`审计，targetType按现有契约使用。
7. 业务记录、文件和审计必须符合失败补偿规则。

十五、共享附件批量导入

实现：

POST /api/v1/attachments/batch-import

要求与方案批量导入一致：

- 重复 `files`；
- 顺序对应的 `items`；
- 每项使用附件metadata；
- 每个文件独立事务；
- 一个失败不影响其他成功项；
- 返回逐文件结果；
- 文件数量与items数量不一致返回 `BATCH_MANIFEST_MISMATCH`；
- 完成文件失败补偿；
- 不创建ACL或分类关系。

十六、共享附件元数据编辑

实现：

PUT /api/v1/attachments/{id}

仅ADMIN可调用。

请求字段：

- documentName
- attachmentType
- summary
- tags
- rowVersion

要求：

1. 只允许ATTACHMENT。
2. 使用rowVersion乐观锁。
3. 不得修改固定PUBLIC密级。
4. 不得修改固定ALL_AUTHENTICATED可见范围。
5. 不得修改文件身份。
6. 不得替换文件。
7. 同步维护searchText和更新人快照。
8. 编辑与 `EDIT_DOCUMENT`审计在同一事务。
9. 返回最新 `DocumentDetail`。

十七、共享附件逻辑删除

实现：

DELETE /api/v1/attachments/{id}?rowVersion=<integer>

仅ADMIN可调用。

要求：

1. 只允许ATTACHMENT。
2. 使用rowVersion乐观锁。
3. 存在有效挂载时返回：

   HTTP 409
   code=ATTACHMENT_IN_USE

4. details至少按契约提供：

   - mountedPlanCount；
   - mainPlans。

5. 不得自动解除挂载。
6. 不得提供强制删除参数。
7. 无有效挂载时允许逻辑删除。
8. 不得物理删除原始文件。
9. 删除与 `DELETE_DOCUMENT`审计在同一事务。
10. 删除后正常附件列表、详情和关系查询不可见。
11. 返回字符串ID和 `deleted=true`。

十八、统一文件预览

实现：

GET /api/v1/documents/{id}/preview

适用于：

- MAIN
- SUB_PLAN
- ATTACHMENT

不得增加附件专用preview路径。

要求：

1. 先认证，再检查用户状态和文档有效性。
2. MAIN、SUB_PLAN使用现有集中授权服务校验VIEW。
3. ATTACHMENT对所有有效登录用户开放。
4. PDF成功返回inline Blob。
5. Office文件：

   - DOC
   - DOCX
   - XLS
   - XLSX

   返回：

   HTTP 415
   code=PREVIEW_UNAVAILABLE

6. 不得将Office原文件伪装成HTML或PDF。
7. 文件不存在返回：

   HTTP 404
   code=FILE_NOT_FOUND

8. 路径越界必须拒绝，并记录系统错误。
9. 响应应设置正确Content-Type和安全的Content-Disposition。
10. 建议设置 `X-Content-Type-Options: nosniff`。
11. 不返回文件路径。
12. 预览失败不得增加下载计数。
13. 不得因preview读取绕过现有密级和ACL规则。

十九、统一文件下载

实现：

GET /api/v1/documents/{id}/download

适用于所有三类文档，不得增加附件专用下载路径。

权限规则：

- MAIN、SUB_PLAN必须具备DOWNLOAD能力；
- ATTACHMENT只要求有效登录用户；
- ADMIN仍不得绕过用户密级规则；
- AUDITOR不因审计角色自动获得方案下载权限。

成功时：

1. 返回真实原始文件。
2. 使用数据库保存的原始文件名作为下载名称。
3. 文件名必须安全编码，防止响应头注入。
4. 设置正确Content-Type。
5. 设置attachment Content-Disposition。
6. 建议设置 `X-Content-Type-Options: nosniff`。
7. 原子更新downloadCount。
8. 写入 `DOWNLOAD_DOCUMENT`审计。
9. 不得返回fileRelativePath。
10. 不得一次请求重复增加下载计数。

失败规则：

- 无权限：403 `DOCUMENT_DOWNLOAD_FORBIDDEN`或契约规定错误；
- 文件不存在：404 `FILE_NOT_FOUND`；
- 文档不存在或已删除：404；
- 未认证或Token失效：401；
- 文件读取失败不得伪装成成功下载。

根据正式契约，下载计数和下载审计采用独立短事务。审计失败应记录系统错误，但第一阶段不阻断已通过鉴权的文件读取。

二十、授权与allowedActions

必须复用B4集中授权服务，禁止在新接口中复制一套不一致的权限判断。

要求：

1. 所有写接口仅ADMIN。
2. 查看和下载使用集中授权逻辑。
3. 子方案权限动态继承根主案。
4. 共享附件不读取doc_permission。
5. 文档状态、密级、角色、可见范围和ACL的顺序与B4一致。
6. 新增、编辑、删除后返回的 `allowedActions`必须基于最新状态实时生成。
7. 后端不得相信前端按钮隐藏。
8. 不能用allowedActions代替后端鉴权。

二十一、审计要求

本轮使用契约已有动作：

- DOWNLOAD_DOCUMENT
- UPLOAD_DOCUMENT
- BATCH_IMPORT
- EDIT_DOCUMENT
- DELETE_DOCUMENT

不得增加未知动作枚举。

审计必须包含：

- 用户ID；
- 用户名；
-姓名快照；
-组织ID；
-组织名称快照；
-客户端IP；
-User-Agent；
-requestId；
-targetType；
-targetId；
-targetName快照；
-operationResult；
-必要操作差异或失败原因。

规则：

1. 上传、编辑、删除等修改类审计与业务事务一致。
2. 修改类审计失败时业务回滚。
3. 下载审计采用独立短事务。
4. 审计不得保存文件正文、密码、Token或绝对路径。
5. `sys_audit_log`不得被修改或删除。

二十二、错误处理

必须按契约实现并测试相关错误，包括但不限于：

- INVALID_REQUEST
- INVALID_ID
- UNAUTHORIZED
- FORBIDDEN
- RESOURCE_NOT_FOUND
- DOCUMENT_VIEW_FORBIDDEN
- DOCUMENT_DOWNLOAD_FORBIDDEN
- UNSUPPORTED_FILE_TYPE
- PREVIEW_UNAVAILABLE
- PAYLOAD_TOO_LARGE
- BATCH_MANIFEST_MISMATCH
- DATA_VERSION_CONFLICT
- MAIN_PLAN_HAS_CHILDREN
- ATTACHMENT_IN_USE
- FILE_NOT_FOUND
- INTERNAL_ERROR

如果契约已有更精确错误码，以契约为准。

所有JSON错误响应必须保持统一结构，并包含requestId。

Blob成功响应也应保留 `X-Request-Id` 响应头。

不得泄露：

- SQL语句；
- Python堆栈；
- 文件绝对路径；
- JWT；
- 数据库连接串；
- 文件正文。

二十三、OpenAPI要求

更新 backend/openapi，仅增加B5的10个接口及必要DTO、参数、multipart和错误响应。

要求：

1. 路径、方法和Flask实际路由完全一致。
2. operationId唯一。
3. multipart/form-data定义准确。
4. 批量重复files字段和items JSON字符串描述准确。
5. preview/download使用二进制响应定义。
6. 201、200、400、401、403、404、409、413、415、500按实际接口声明。
7. 字符串ID不得声明为integer。
8. 不得加入恢复、挂载写、权限写、审计或统计路径。
9. 全部外部 `$ref` 和fragment引用必须可解析。

二十四、自动化测试要求

必须在统一 `dms_test` 上覆盖，至少包括：

（一）单文件上传

- MAIN上传成功；
- SUB_PLAN上传成功；
- ATTACHMENT上传成功；
- 字符串ID；
- number ID拒绝；
- 未知字段拒绝；
- 无效枚举拒绝；
- 无效分类拒绝；
- 子方案父级不是MAIN；
- 非ADMIN返回403；
- 文件元数据和SHA-256正确；
- 数据库只保存相对路径；
- 原始文件名不参与磁盘路径；
- 同哈希允许多业务文档。

（二）文件安全

- 合法PDF；
- 合法DOCX；
- 合法XLSX；
- 合法DOC或XLS的CFB校验；
- 扩展名和文件头不一致；
- 损坏ZIP；
- 普通ZIP伪装DOCX；
- 普通ZIP伪装XLSX；
- 空文件；
- 不支持扩展名；
- 路径穿越文件名；
- 超过单文件限制；
- 超过请求总大小；
- 超过批量文件数；
- 临时文件清理；
- 数据库失败后的文件补偿；
- 文件失败后的数据库回滚。

（三）批量导入

- 全部成功；
- 部分成功；
- 全部失败；
- files/items数量不一致；
- 返回顺序稳定；
- 成功失败统计正确；
- 每文件事务相互隔离；
- 单项失败不回滚其他项；
- 失败不留下孤立文件或数据库记录。

（四）编辑

- MAIN编辑；
- SUB_PLAN编辑；
- ATTACHMENT编辑；
- 非ADMIN拒绝；
- rowVersion冲突；
- 禁止修改文件身份；
- 禁止修改文档类型和父子关系；
- 分类冗余更新；
- searchText更新；
- 编辑不增加查看或下载计数；
- 审计与业务事务一致。

（五）删除

- MAIN无子方案时逻辑删除；
- MAIN存在子方案时409；
- SUB_PLAN逻辑删除并更新主案计数；
- 删除MAIN逻辑删除ACL和挂载关系；
- 删除MAIN不删除共享附件；
- ATTACHMENT未挂载时逻辑删除；
- ATTACHMENT已挂载时409；
- 不强制解除挂载；
- 不物理删除文件；
- rowVersion冲突；
- 删除后正常查询不可见；
- 无恢复接口。

（六）预览和下载

- PDF预览成功；
- Office预览415；
- 文件缺失404；
- 路径越界拒绝；
- 无VIEW权限拒绝预览；
- 无DOWNLOAD权限拒绝下载；
- 共享附件有效用户可下载；
- AUDITOR不能下载无权方案；
- ADMIN不绕过密级；
- 下载文件内容与磁盘一致；
- 下载文件名安全；
- 下载计数准确加1；
- 下载审计准确且只写一次；
- 失败下载不增加计数；
- 响应不泄露文件路径；
- X-Request-Id存在。

（七）回归

- B1—B4全部测试继续通过；
- 现有AI测试继续通过；
- OpenAPI解析和路由一致性通过；
- 无环境变量模式不得出现KeyError；
- 测试数据库必须仍为dms_test；
- 不得连接主库执行清理或downgrade。

二十五、真实验证策略

自动化测试和写接口验证统一使用 `dms_test`。

如需启动真实HTTP服务验证写接口：

1. 使用独立端口；
2. 数据库指向 `dms_test`；
3. 存储根目录指向明确的测试存储目录；
4. 不得使用 `dms` 主库上传测试文档；
5. 不得污染业务主库存储目录；
6. 验证后停止测试服务；
7. 只清理明确的测试临时目录；
8. 清理前必须确认目录位于指定测试根目录内。

`dms` 主库只做非破坏性检查：

- 迁移版本；
- 表结构；
- 已有业务数据计数；
- 已有文件存在性。

除非统领会话另行授权，不要向 `dms` 主库写入B5验证数据。

二十六、必须执行的验证命令

至少执行：

1. 完整B1—B5测试；
2. 既有AI回归测试；
3. python -m compileall -q dms；
4. OpenAPI YAML解析和 `$ref`检查；
5. Flask实际路由与OpenAPI一致性检查；
6. 有测试环境变量的完整测试；
7. 无测试环境变量的安全跳过测试。

测试环境变量继续使用：

DMS_TEST_DATABASE_URL=mysql+pymysql://root:root@localhost:3306/dms_test?charset=utf8mb4
DMS_TEST_USER_PASSWORD=TestUser@123456
DMS_TEST_JWT_SECRET=local-test-jwt-secret-not-for-production

测试凭据不得写入源码、OpenAPI、契约或提交文件。

二十七、阻塞规则

发生以下情况必须停止并提交统领裁决：

1. 需要新增数据库字段或迁移；
2. 正式契约与OpenAPI存在冲突；
3. 需要修改两份DMS契约；
4. 需要新增第11个B5接口；
5. 需要实现恢复；
6. 需要物理删除文件；
7. 需要实现挂载或权限写入；
8. 需要修改前端；
9. 需要修改AI模块；
10. 现有授权服务无法表达契约规则；
11. 文件事务无法在不改变契约的情况下保证补偿；
12. 需要创建新的测试数据库；
13. 真实响应需要增加未定义字段；
14. 测试必须清理主库才能继续。

不得自行兼容、猜测或扩大范围。

二十八、最终报告格式

完成后必须输出：

1. 阶段结论：B5是否完成，是否停止在B5。
2. 契约加载情况。
3. 修改文件清单。
4. 数据库和迁移检查结果。
5. 10个B5接口实现情况。
6. 文件存储结构。
7. 文件类型及安全校验方案。
8. 单文件上传事务边界。
9. 文件失败补偿机制。
10. 主案和子方案上传结果。
11. 共享附件上传结果。
12. 两类批量导入结果。
13. 文档元数据编辑规则。
14. 附件元数据编辑规则。
15. 主案、子方案和附件删除规则。
16. PDF预览和Office拒绝预览结果。
17. 下载授权、计数和审计结果。
18. 冗余字段和计数维护结果。
19. rowVersion并发控制结果。
20. 审计实现结果。
21. OpenAPI变化。
22. dms_test使用情况。
23. 是否创建了任何新测试数据库。
24. 完整测试结果。
25. 无环境变量测试结果。
26. AI和旧阶段回归结果。
27. dms主库非破坏检查结果。
28. 契约符合性检查。
29. 是否存在偏差。
30. 未实现内容。
31. B6建议，但不得执行。

再次强调：

- 本轮只执行B5。
- 只复用dms_test。
- 不创建阶段测试库。
- 不向dms主库写入测试文档。
- 不修改前端。
- 不实现挂载写和权限写。
- 不实现审计查询和统计。
- 不实现任何恢复功能。
- 不物理删除原始文件。
- 不进入B6。
- 不提交或推送Git。
--------------------------------------