实现本地 Markdown 清洗评审器
This commit is contained in:
@@ -0,0 +1,480 @@
|
||||
# 0007:同仓库本地 Markdown 清洗评审器
|
||||
|
||||
## 状态
|
||||
|
||||
已批准并冻结(2026-08-23)。
|
||||
|
||||
本设计已经用户明确批准,授权按第 16 节实施。后续契约或范围变化必须新增 design,不回写本文。
|
||||
|
||||
`supersedes: 0005`(范围有限):本文只替代 `0005` 中“不建设 Web 界面”和运行目录只有既有文件的选择,
|
||||
允许在本地实验层增加机器定位文件,并在同一仓库建立独立的只读评审器。`0005` 已确定的输入只读、成功输出边界、
|
||||
JSON 审计权威、原子发布、私有权限、30 日保留和真实数据限制继续有效。
|
||||
|
||||
## 1. 问题与可观察现象
|
||||
|
||||
当前本地实验已经为每份成功文档保存 `cleaned.md`、`result.json` 和 `changes.diff`。这些文件可以证明最终文本和逐项
|
||||
修改,但人工评审仍需要分别打开原文、清洗结果和 JSON,难以快速回答三个问题:
|
||||
|
||||
1. 原文和最终清洗结果在整篇文档中有什么差异;
|
||||
2. 每个组件实际修改了什么、修改了多少处;
|
||||
3. 多个组件顺序执行时,某个组件看到的输入和它产生的输出分别是什么。
|
||||
|
||||
现有运行层在预检时已经取得每份输入的绝对解析路径,在发布前也知道最终运行目录,但只把用于展示的
|
||||
`source_label` 和内容哈希写入清单。运行结束后,评审工具不能只靠运行目录找到完整原文;`changes.diff` 只有差异上下文,
|
||||
不能代替完整输入。
|
||||
|
||||
`result.json` 中的 `Change.span` 又绑定各组件执行前的中间快照。它使用 Python 字符串下标,不能被浏览器当作 JavaScript
|
||||
字符串下标直接使用。前端如果自行猜测坐标或只把所有变化涂在最终文本上,可能把组件归属显示错。
|
||||
|
||||
用户已经确定前端与 Python 核心放在同一个仓库,减少跨仓开发、评审和版本协调成本。这里的解耦目标因此不是物理分仓,
|
||||
而是保持依赖方向和数据契约清楚,使清洗核心与界面可以分别修改和验证。
|
||||
|
||||
## 2. 目标与非目标
|
||||
|
||||
### 2.1 目标
|
||||
|
||||
- 在本仓库增加一个只服务本机的 Markdown 清洗评审器;
|
||||
- 同时展示一份成功文档的完整原文和完整清洗结果;
|
||||
- 按流水线顺序列出组件身份、版本和实际修改数量;
|
||||
- 选择组件后,准确展示该组件执行前后的 Markdown,而不是把中间坐标错误套到最终文本;
|
||||
- 点击修改时显示修改理由、位置、`before` 和 `after`;
|
||||
- 运行时记录最终运行目录和每份输入的本机路径,不复制或改写原文;
|
||||
- 读取前验证原文、产物和修改链哈希,验证失败时拒绝近似展示;
|
||||
- 让浏览器界面只依赖评审器内部的版本化 API,不依赖 Python 类、源码路径或 `TransformResult`;
|
||||
- 让报告生成和评审服务共用同一套 Python 快照重放逻辑,不在 TypeScript 中复制审计规则;
|
||||
- 前端代码、依赖和检查保存在独立子项目中,不进入 `mdpolish` Python 分发包。
|
||||
|
||||
### 2.2 非目标
|
||||
|
||||
- 不编辑 Markdown,不批准、拒绝或调整某条修改;
|
||||
- 不从页面重新运行清洗,不改变组件、参数或顺序;
|
||||
- 不原地覆盖输入,也不把页面状态写回运行目录;
|
||||
- 不建设远程服务、多人协作、账户、数据库、上传、分享或长期归档;
|
||||
- 不提供安装后的稳定公共 CLI、公共 HTTP API 或生产部署接口;
|
||||
- 第一版不渲染 Markdown、原始 HTML、图片或外部资源,只展示忠实的 Markdown 源文本;
|
||||
- 不为 `failed` 或 `unstable` 文档构造、保存或展示一份看似正式的完整输出;
|
||||
- 不改变清洗语义、组件版本、规则顺序、统计口径或核心数据模型;
|
||||
- 不增加 GovDoc 或其他仓库外真实数据的产物保存权限;
|
||||
- 不兼容任意历史或未来产物格式,第一版只读取本文明确批准的版本。
|
||||
|
||||
源码视图是第一版的有意边界。当前规则包含换行、HTML 源码和精确字符修改,渲染后的页面可能隐藏这些变化;同时允许
|
||||
原始 HTML 和远程图片进入页面会扩大安全与数据泄露风险。以后确实需要渲染预览时,应单独决定 Markdown 方言、HTML
|
||||
净化、图片寻址和网络策略。
|
||||
|
||||
## 3. 不改变的现有事实
|
||||
|
||||
本文继续沿用以下权威:
|
||||
|
||||
- `manifest.json` 仍是运行身份、环境、流水线、文档索引和汇总的权威;
|
||||
- `result.json` 仍是文档状态、修改、错误和残留候选的权威;
|
||||
- `cleaned.md` 仍是 `success` 文档最终文本的权威;
|
||||
- `changes.diff` 仍只是原文到最终成功输出的人工评审视图;
|
||||
- `failed` 和 `unstable` 文档没有 `cleaned.md`,评审器不得把重放得到的部分文本命名或展示为成功结果;
|
||||
- 所有产物继续位于 Git 忽略的 `artifacts/`,目录权限为 `0700`、文件权限为 `0600`,默认保留 30 个日历日;
|
||||
- 组件和内存核心继续不知道文件路径、运行目录、HTTP 或前端。
|
||||
|
||||
本文不修改 `manifest.json` 和 `result.json` 的 `schema_version: 1`。本机路径进入独立定位文件,避免给已存在的审计字段
|
||||
增加未版本化含义。
|
||||
|
||||
## 4. 方案比较
|
||||
|
||||
### 4.1 前端直接导入或调用 `mdpolish`
|
||||
|
||||
这种方式可以少写一层适配,但界面会依赖 Python 包布局、类和调用方式,浏览器也不能直接执行 Python。核心升级容易迫使
|
||||
前端同步修改。不采用。
|
||||
|
||||
### 4.2 纯静态页面要求用户每次选择原文和所有产物
|
||||
|
||||
不需要本地服务,但浏览器对本机路径有权限限制,不同浏览器的目录选择能力也不同。每次手工配对多份文档容易选错,且无法
|
||||
自然复用运行时已经验证过的路径。不采用为默认流程。
|
||||
|
||||
### 4.3 打包为桌面应用
|
||||
|
||||
桌面壳可以直接访问文件,但第一版会额外引入安装包、自动更新、签名和多平台问题,超过本地实验评审需要。不采用。
|
||||
|
||||
### 4.4 独立前端加 Node.js 本地只读服务
|
||||
|
||||
运行层只增加本机路径定位文件;评审器用独立适配器读取产物,校验后通过同源本地 API 提供给界面。前端不接触任意文件
|
||||
路径,也不理解 Python 对象。这能让评审器脱离 Python 独立运行,但必须在 TypeScript 中重新实现 Python 已有的组件分批、
|
||||
编辑排序、逐项原文验证和逐批哈希验证,还要持续处理 Python 码点与 JavaScript UTF-16 坐标的差异。
|
||||
|
||||
哈希校验能阻止错误重放被静默展示,却不能消除两套实现的维护成本。第一版没有“把运行目录交给一台不含 Python 和
|
||||
`mdpolish` 的机器独立评审”的目标,因此不采用。
|
||||
|
||||
### 4.5 独立前端加 Python 标准库本地只读服务
|
||||
|
||||
Python 服务读取已经发布的文件契约,并通过一个从 `reporting.py` 提取的纯重放模块复核组件快照。报告生成和评审服务共用
|
||||
同一套应用顺序与哈希验证;服务再把完整阶段文本和派生的编辑器坐标通过同源本地 API 提供给前端。React/TypeScript
|
||||
只负责交互和展示,不解释 artifact,也不应用 Python span。
|
||||
|
||||
采用此方案。它保留浏览器与核心对象之间的 API 边界,同时把风险最高的可信重放留在唯一的 Python 实现中。代价是启动
|
||||
评审器必须具有本仓库支持的 Python 环境和匹配版本的 `mdpolish`;这是第一版本地实验流程可以接受的约束。
|
||||
|
||||
## 5. 总体结构与依赖方向
|
||||
|
||||
```text
|
||||
显式 Markdown 输入
|
||||
│
|
||||
▼
|
||||
mdpolish 本地实验层
|
||||
│
|
||||
├── 既有 manifest / result / cleaned / diff
|
||||
└── 新增 review-locator.json
|
||||
│
|
||||
▼
|
||||
Python 产物适配与重放服务
|
||||
│ │
|
||||
│ ├── 验证路径、哈希和状态
|
||||
│ └── 用共享 Python 逻辑重放组件快照
|
||||
▼
|
||||
本地只读 API
|
||||
│
|
||||
▼
|
||||
浏览器评审界面
|
||||
```
|
||||
|
||||
依赖规则固定为:
|
||||
|
||||
- `src/mdpolish/` 不导入 `reviewer/`;
|
||||
- `reporting.py` 和评审服务只共同依赖一个不读写文件的 Python 重放模块;
|
||||
- `reviewer/server/` 可以导入该重放模块,但不导入或调用组件、`Pipeline`、实验入口,也不重新运行清洗;
|
||||
- Python 产物适配器只读取已经发布的文件契约,不把内部模型当作 artifact 格式;
|
||||
- 前端组件只读取评审器 API,不读取磁盘,不解析 `manifest.json` 或 `result.json`;
|
||||
- Python 服务负责产物版本差异、组件快照重放和编辑器坐标派生,页面不维护第二套产物解释逻辑;
|
||||
- 评审器不成为 `mdpolish` wheel 的一部分。
|
||||
|
||||
评审服务依赖共享重放模块是本文唯一批准的源码级连接。它用于消除两套可信重放实现,不允许扩展为从页面调用清洗核心。
|
||||
除此之外,同仓库只用于共享开发流程、提交历史和契约测试。
|
||||
|
||||
## 6. 仓库结构与技术选择
|
||||
|
||||
批准后允许新增以下结构:
|
||||
|
||||
```text
|
||||
src/mdpolish/
|
||||
└── _artifact_replay.py # 无文件 I/O 的共享快照重放逻辑
|
||||
reviewer/
|
||||
├── .nvmrc
|
||||
├── __init__.py
|
||||
├── server/ # Python 标准库 HTTP 服务和产物版本适配器
|
||||
├── package.json
|
||||
├── package-lock.json
|
||||
├── tsconfig.json
|
||||
├── vite.config.ts
|
||||
├── src/
|
||||
│ ├── client/ # React 浏览器界面,只依赖本地 API
|
||||
│ └── shared/ # 前端使用的 API 类型和运行时校验
|
||||
└── tests/ # 前端合成数据和界面测试
|
||||
tests/
|
||||
└── test_reviewer_*.py # Python 产物适配、重放和服务测试
|
||||
```
|
||||
|
||||
第一版采用:
|
||||
|
||||
- 仓库现有 Python 环境运行本地只读服务;
|
||||
- Python 标准库实现 HTTP、文件读取和 JSON 解析,不增加 Python 运行依赖;
|
||||
- `_artifact_replay.py` 同时供 `reporting.py` 和评审服务调用,保持唯一的可信重放实现;
|
||||
- Node.js 24 LTS 只用于前端开发、测试和构建,不负责读取或重放 artifact;
|
||||
- TypeScript 表达前端 API 数据和界面类型;
|
||||
- React 构建交互界面;
|
||||
- Vite 提供开发与构建入口;
|
||||
- CodeMirror 6 Merge View 提供只读双栏文本比较;
|
||||
- Vitest 和 React Testing Library 覆盖前端 API 数据校验与主要界面状态。
|
||||
|
||||
选择 React 与 Vite 是为了在一个独立目录内保留成熟的模块、类型和开发服务器,而不把 JavaScript 构建配置混入
|
||||
Python 包。[React 官方文档](https://react.dev/learn/build-a-react-app-from-scratch)把 Vite 列为从零建立客户端应用可用的
|
||||
构建工具;[Vite 官方文档](https://vite.dev/guide/features)说明它原生处理 TypeScript,但类型检查需要作为独立检查执行。
|
||||
[CodeMirror 的 Merge View](https://codemirror.net/docs/ref/#merge.MergeView)能直接比较两个文本并标记插入与删除,避免本项目
|
||||
自行实现文本 diff 编辑器。
|
||||
|
||||
精确前端依赖版本只在 `reviewer/package.json` 和锁文件中维护,本文不复制版本清单。运行时不得从 CDN 下载脚本、字体、
|
||||
样式或其他资源。根据 [Node.js 官方版本状态](https://nodejs.org/en/about/previous-releases),当前机器上的 Node.js 20 已结束
|
||||
官方支持,不能作为前端实现验收环境。
|
||||
|
||||
批准本文同时授权实施者在当前用户已有的 nvm 中执行 `nvm install 24` 和 `nvm use 24`,在 `reviewer/.nvmrc` 固定主版本
|
||||
`24`,并在 `package.json` 的 `engines.node` 中限制为 Node.js 24。该授权不包括使用系统包管理器安装 Node.js、替换
|
||||
`/usr/bin/node` 或修改其他用户的环境;如果当前用户的 nvm 不可用,应停止并另行确认。
|
||||
|
||||
## 7. 本机运行定位文件
|
||||
|
||||
每次新实验在运行目录根部增加:
|
||||
|
||||
```text
|
||||
artifacts/<run_date>/runs/<run_id>/review-locator.json
|
||||
```
|
||||
|
||||
第一版结构固定为:
|
||||
|
||||
```text
|
||||
schema_version
|
||||
run
|
||||
run_id
|
||||
run_directory
|
||||
manifest_path
|
||||
documents[]
|
||||
document_id
|
||||
source_path
|
||||
input_sha256
|
||||
```
|
||||
|
||||
字段语义如下:
|
||||
|
||||
- `schema_version` 固定为整数 `1`;
|
||||
- `run_id` 必须与 `manifest.json` 和目录身份一致;
|
||||
- `run_directory` 是运行发布时最终目录的绝对解析路径;
|
||||
- `manifest_path` 固定为相对路径 `manifest.json`;
|
||||
- `source_path` 是预检实际读取的普通文件的绝对解析路径,不保存调用方未解析的写法;
|
||||
- `input_sha256` 必须与对应 manifest、result 和预检字节一致;
|
||||
- 文档顺序必须与 manifest 一致。
|
||||
|
||||
定位文件使用与现有 JSON 相同的 UTF-8、无 BOM、两空格缩进、保留 Unicode 和末尾换行规则,权限为 `0600`。它与其他
|
||||
文件一起写入临时运行目录、完成校验后原子发布;任一字段、写入或回读校验失败时不得发布最终运行目录。
|
||||
|
||||
职责边界固定为:`experiment.py` 根据已预检的文档身份、解析后的源路径、输入哈希和 artifact store 计算的最终目标目录
|
||||
生成定位文件内容;`artifact_store.py` 不猜测或生成 `source_path` 等字段,只负责校验它与目标目录、manifest、result 的
|
||||
结构一致性,以及权限、写入、回读和原子发布。最终路径布局仍只由 artifact store 决定,不能在实验层复制日期目录规则。
|
||||
|
||||
`review-locator.json` 只是本机寻址信息,不替代 manifest 或 result 的运行事实。绝对路径可能包含用户名和本机目录结构,
|
||||
因此它属于本地敏感产物,不得提交、推送、上传、复制到 Wiki 或显示在普通终端摘要中。
|
||||
|
||||
评审器启动时由用户明确传入当前运行目录。若目录后来被移动,启动参数中的实际目录是读取产物的依据;定位文件中的
|
||||
`run_directory` 只用于提示位置已经变化,不能让服务跳转读取另一个运行目录。`source_path` 失效或哈希不符时,该文档原文
|
||||
标记为不可用,不能退化为按文件名搜索或继续展示不匹配内容。
|
||||
|
||||
已有运行目录没有定位文件,仍然是合法的历史实验产物。第一版评审器可以展示其清单和审计,但不承诺自动找到完整原文,
|
||||
也不向历史目录补写定位文件。要进行完整双栏评审,应产生一次新的、具有不同运行 ID 的实验。
|
||||
|
||||
## 8. 本地服务入口与边界
|
||||
|
||||
评审器提供仓库内入口,概念调用方式为:
|
||||
|
||||
```text
|
||||
python -m reviewer.server --run-dir <run_directory>
|
||||
```
|
||||
|
||||
生产构建得到的静态页面由 Python 服务与 API 一起提供。开发时 Vite 可以通过同源代理连接同一个 Python API,但 Node 进程不读取
|
||||
artifact。精确的开发、构建和启动命令在实现并验证后只进入 README 的当前检查入口和对应 guide,不在多份文档维护不同
|
||||
写法。入口不安装到系统,也不承诺长期参数兼容。
|
||||
|
||||
本地服务必须:
|
||||
|
||||
- 只绑定 `127.0.0.1`,默认使用操作系统分配的空闲端口;
|
||||
- 只服务启动参数指定的一次运行,不扫描整个 `artifacts/`;
|
||||
- 只接受允许的 `GET` 和 `HEAD`,其他方法返回明确错误;
|
||||
- 不设置跨域许可,只接受本服务自身页面的同源请求;
|
||||
- 校验 `Host`,拒绝非本机目标和路径穿越;
|
||||
- 不提供任意文件路径读取接口;
|
||||
- 不提供写、删、移动、清理、重新运行或 shell 执行接口;
|
||||
- 对页面和 API 设置禁止缓存、内容类型保护和限制脚本来源的安全响应头;
|
||||
- 退出时不修改运行目录、原文或浏览器外的任何状态。
|
||||
|
||||
服务读取的所有 artifact 相对路径都必须解析在启动运行目录内部。`source_path` 是唯一允许指向运行目录外的文件路径,且只在
|
||||
定位文件、manifest 和 result 的文档身份与哈希全部一致后读取。绝对源路径不返回给浏览器,页面只显示 `source_label`。
|
||||
|
||||
## 9. 评审器内部 API
|
||||
|
||||
浏览器只使用同源 `/api/v1/`。第一版至少提供三个只读资源:
|
||||
|
||||
### 9.1 运行摘要
|
||||
|
||||
返回运行身份、状态、组件顺序、文档索引和汇总计数,并为每份文档说明原文和成功输出是否可用。它不返回绝对路径、Git
|
||||
仓库路径或整篇 Markdown。
|
||||
|
||||
### 9.2 文档比较
|
||||
|
||||
对 `success` 文档返回:
|
||||
|
||||
- 完整且通过哈希验证的原始 Markdown;
|
||||
- 完整且通过哈希验证的 `cleaned.md`;
|
||||
- 文档状态、前后哈希和总修改数量;
|
||||
- 按组件位置分组的修改数量;
|
||||
- 每条修改的组件、版本、理由、派生行列、`before` 和 `after`;
|
||||
- 由服务端根据对应组件前快照派生的 UTF-16 `editor_range`,只用于 CodeMirror 定位,不作为修改权威。
|
||||
|
||||
对 `failed` 和 `unstable` 文档只返回状态、已有审计、错误和残留候选,不返回或重建一份完整部分输出。原文可以作为只读
|
||||
诊断背景返回,但页面必须明确该文档没有正式成功结果。
|
||||
|
||||
### 9.3 组件阶段比较
|
||||
|
||||
只对 `success` 文档提供。调用方按 `component_position` 请求一个组件,服务返回该组件执行前和执行后的完整 Markdown、
|
||||
组件元数据和属于该组件的实际修改。零修改组件也返回相同的前后文本和零计数,使流水线顺序完整可见。
|
||||
|
||||
这是评审器内部契约,不是面向其他项目的公共 API。服务端和前端同属 `reviewer/`,字段改变仍需同步类型、运行时校验和
|
||||
测试;不得让页面回退为直接读取磁盘 JSON。
|
||||
|
||||
## 10. 中间快照重放与编辑器坐标
|
||||
|
||||
组件阶段视图必须从已验证原文按实际 `Change` 重放,不保存新的中间 Markdown 文件。`manifest.pipeline.components[]`
|
||||
是完整组件顺序的权威,`result.json.changes[]` 只记录实际发生的修改。对 `success` 文档,服务按以下规则处理:
|
||||
|
||||
1. 从 manifest 的第一个组件开始按位置遍历,先验证 `changes[]` 中的组件位置不倒退、同一位置连续出现,并且组件身份和
|
||||
版本与 manifest 对应项一致;
|
||||
2. 收集当前位置的全部 `Change`。没有 Change 时,该组件仍形成一个合法的零修改阶段:前后文本都等于当前重放文本,
|
||||
修改数为零,不要求或伪造不存在的批次哈希;
|
||||
3. 有 Change 时,要求该位置所有记录具有相同的 `before_sha256` 和 `after_sha256`,并验证当前文本哈希等于
|
||||
`before_sha256`;
|
||||
4. 逐项验证 Python 码点范围合法,且当前范围文字等于 `before`;
|
||||
5. 按 `(span.start, span.end, proposal_index, edit_index)` 降序应用该组件的编辑。范围已经由核心冲突契约保证互不冲突,
|
||||
完整排序键用于保持与现有 Python 执行器一致和结果确定;artifact 的记录顺序不是应用顺序;
|
||||
6. 验证批次结果哈希等于 `after_sha256`,再把该阶段的前后文本和修改交给 API;
|
||||
7. 全部 manifest 组件结束后,验证结果字节和哈希分别等于 `cleaned.md` 与 `current_sha256`。
|
||||
|
||||
因此,对 `success` 文档,“manifest 中存在、该位置没有 Change”明确表示组件运行过但没有修改;manifest 中没有该位置才是
|
||||
组件缺失。`failed` 和 `unstable` 不能仅凭 manifest 推断全部组件已经运行,服务不为它们建立完整组件阶段链。
|
||||
|
||||
共享重放模块直接使用 Python 码点范围,不进行跨语言应用。为了让 CodeMirror 跳转,Python 服务在已经验证的组件前快照上
|
||||
另外计算零起始、半开区间的 UTF-16 code unit `editor_range`。该范围只是 API 派生视图;前端不得用它重新应用修改,
|
||||
`result.json` 的 Python span 仍是审计权威。
|
||||
|
||||
严格 UTF-8 输入不会包含无法重新编码的孤立代理项。仍需用包含中文、补充平面字符、组合字符、BOM、CRLF 和无末尾换行的
|
||||
合成测试证明服务返回的 UTF-16 范围能准确定位对应 `before`。
|
||||
|
||||
任一步失败都把该文档标记为“产物无法可信重放”,不生成组件阶段数据,不用文本搜索、diff 猜测或跳过错误继续展示。
|
||||
`residual_proposals` 只显示为最终复查证据,绝不应用。
|
||||
|
||||
## 11. 第一版界面行为
|
||||
|
||||
第一版页面分为三个区域:
|
||||
|
||||
1. 运行与文档列表:显示整体状态、文档状态、前后哈希和修改数量;
|
||||
2. 主双栏:只读展示完整原文与最终成功 Markdown,支持差异标记、行号和联动滚动;
|
||||
3. 组件侧栏:按实际流水线顺序显示组件标识、版本和修改数,选择后把主双栏切换为该组件执行前后视图。
|
||||
|
||||
修改详情显示理由、修改前位置、`before` 和 `after`;点击后跳转到对应组件阶段的差异位置。同一个候选修改中的多条编辑应
|
||||
继续用 `proposal_ref` 关联,不能把一项多位置动作错误显示为互不相关的业务问题。
|
||||
|
||||
页面必须清楚区分:
|
||||
|
||||
- 总修改数量是实际 `Change` 条数,不是 unified diff hunk 数、问题数或修改字符数;
|
||||
- `success` 只表示选中组件运行稳定,不表示文档没有其他问题;
|
||||
- `failed` / `unstable` 没有正式清洗结果;
|
||||
- 零修改组件确实运行过,与组件缺失不是同一状态;
|
||||
- 原文路径失效、哈希变化和产物损坏属于不同错误。
|
||||
|
||||
第一版以桌面浏览器评审为主,但键盘应能切换文档、组件和修改,状态不能只依赖颜色表达。页面不提供编辑控件、文件拖放、
|
||||
远程链接预览或任何会修改外部状态的按钮。
|
||||
|
||||
## 12. 版本兼容策略
|
||||
|
||||
第一版支持:
|
||||
|
||||
- `manifest.json` schema `1`;
|
||||
- `result.json` schema `1`;
|
||||
- `review-locator.json` schema `1`;
|
||||
- 评审器内部 API `/api/v1/`。
|
||||
|
||||
遇到未知 schema 时必须说明不支持的文件和版本并拒绝读取,不能忽略版本继续猜测。以后 mdpolish 产物升级时,在
|
||||
`reviewer/server/` 增加明确的 Python 版本适配器;前端仍只使用统一内部 API。只有无法保持原含义时才升级 API 版本。
|
||||
|
||||
Python 核心和前端因此可以在同一仓库分别演进,但“任意核心变化都无需调整评审器”不是目标。真正保证的是:变化集中在
|
||||
文件契约适配器,并由兼容性测试暴露,不让内部类或目录变化直接扩散到页面。
|
||||
|
||||
## 13. 隐私与数据安全
|
||||
|
||||
- 定位文件、原文、成功 Markdown、diff 和审计继续按本地敏感数据处理;
|
||||
- 页面和本地 API 不包含遥测、错误上报、CDN、远程字体或自动更新请求;
|
||||
- 浏览器运行时只允许连接同源本地服务;
|
||||
- Markdown 只作为文本交给只读编辑器,不注入 `innerHTML`;
|
||||
- 服务不输出原文、diff、绝对源路径或 `before` / `after` 到终端日志;
|
||||
- 关闭页面或服务不删除缓存以外的任何文件,也不改变 30 日保留规则;
|
||||
- 定位文件不得解除 Git 忽略,不得进入测试 fixture;自动测试只使用虚构 Markdown 和临时目录。
|
||||
|
||||
本设计没有因为增加页面而扩大真实数据授权。当前仍只允许对 `data/md/` 中现有 5 份论文副本保存实验产物;
|
||||
`/home/lihaoze/gov_test_data` 及其他外部真实材料继续只读且不得生成本项目产物。
|
||||
|
||||
## 14. 测试与验收
|
||||
|
||||
### 14.1 Python 实验层与 artifact store
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- 定位文件记录实际最终运行目录、解析后的源路径、文档顺序和输入哈希;
|
||||
- 实验层生成定位内容,artifact store 不自行推断源路径;
|
||||
- 定位文件、manifest 和 result 的身份或哈希不一致时拒绝发布;
|
||||
- 定位文件使用批准的 JSON 编码和 `0600` 权限;
|
||||
- 任一定位文件写入、回读或校验失败时不发布最终运行目录;
|
||||
- 输入路径和文件内容在运行前后不变;
|
||||
- `failed` / `unstable` 仍不产生 `cleaned.md`;
|
||||
- 既有 manifest 和 result schema 不被静默改变。
|
||||
|
||||
### 14.2 Python 重放、产物适配器与服务
|
||||
|
||||
合成测试至少覆盖:
|
||||
|
||||
- 合法 `success`、`failed` 和 `unstable` 产物;
|
||||
- 缺失、未知版本、非法 JSON、BOM、错误编码和路径穿越;
|
||||
- 源文件缺失、不是普通文件、哈希改变或身份不匹配;
|
||||
- cleaned 哈希不符、修改原文不符和中间快照链断裂;
|
||||
- 报告生成和评审服务对同一合成修改链得到完全相同的中间与最终文本;
|
||||
- 精确按 `(span.start, span.end, proposal_index, edit_index)` 降序应用,不把 artifact 记录顺序当作应用顺序;
|
||||
- 中文、补充平面字符、组合字符、CRLF、空文档和无末尾换行的快照重放及 UTF-16 编辑器范围;
|
||||
- 多组件、首个/中间/末尾零修改组件、空流水线、一个候选多编辑和后续组件基于新快照修改;
|
||||
- manifest 中缺失组件与合法零修改组件能被区分,倒序、非连续重复或身份不匹配的组件批次被拒绝;
|
||||
- 未知文档和组件位置返回明确错误;
|
||||
- 非 GET/HEAD 方法、非本机 Host、跨域和任意路径读取被拒绝;
|
||||
- API 不泄露绝对源路径,并包含禁止缓存和内容类型保护头。
|
||||
|
||||
### 14.3 前端
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- 运行和文档状态列表;
|
||||
- 原文与成功结果双栏;
|
||||
- 组件顺序、版本、零修改和修改数量;
|
||||
- 组件视图切换、修改详情和跳转;
|
||||
- `failed` / `unstable` 不显示正式结果;
|
||||
- 路径失效、哈希不符、产物损坏和未知 schema 的清楚错误;
|
||||
- 不把 Markdown 当 HTML 执行;
|
||||
- 键盘操作和不依赖颜色的状态表达。
|
||||
|
||||
评审器检查至少包括 Python 的 Ruff、mypy 和 pytest,以及前端 TypeScript 类型检查、lint、单元/组件测试和生产构建。
|
||||
精确命令和依赖版本在实现后进入各自唯一权威;根目录 README 只记录当前真实可用的总体验收入口。
|
||||
|
||||
### 14.4 本地 5 份论文验收
|
||||
|
||||
实现和合成测试通过后,允许使用新的运行 ID 对 `data/md/` 中 5 份本地论文副本再次运行已批准的第一批流水线,并验证:
|
||||
|
||||
1. 定位文件列出 5 份输入,路径和哈希正确,输入字节不变;
|
||||
2. 页面列出 8 个组件及其实际顺序和版本;
|
||||
3. 5 份文档都能同时打开原文和成功输出;
|
||||
4. 页面总修改数与 manifest、result 一致;
|
||||
5. 每个组件阶段都能重放到正确前后哈希,零修改组件显示为零而不是缺失;
|
||||
6. JAMA Abstract 前内容、Springer 合法 arXiv 引用和图片引用文字等既有反例仍保持不变;
|
||||
7. 浏览器和终端不发生外部网络请求,不输出真实文本或绝对源路径;
|
||||
8. 新运行仍只位于 Git 忽略的 `artifacts/`,权限和保留日期符合 `0005`。
|
||||
|
||||
这一验收只证明当前 5 份论文和既有 8 个组件能够被本地评审,不表示通用数据集、GovDoc 或生产部署已经支持。
|
||||
|
||||
## 15. 风险与代价
|
||||
|
||||
- **评审服务依赖本仓库 Python 环境:** 它换来唯一的可信重放实现,但不能在只有静态产物和 Node.js 的机器上独立启动;
|
||||
- **共享 Python 模块仍是源码耦合点:** 依赖只限纯重放函数,并用报告与评审一致性测试控制,不能扩展到组件或流水线;
|
||||
- **绝对路径会泄露本机结构并可能失效:** 路径只进入私有定位文件,移动后明确报错,不把路径当可移植身份;
|
||||
- **新增 Node.js 工具链:** Node.js 只用于前端开发和构建,但仓库仍需要第二套受支持环境和锁文件;
|
||||
- **编辑器坐标仍有跨语言差异:** Python 服务派生并测试 UTF-16 范围;坐标错误只能影响跳转,不能改变已经在 Python 中完成并
|
||||
验证哈希的阶段文本;
|
||||
- **完整文本会占用浏览器内存:** 第一版面向当前本地实验,不宣称支持任意极端长度;出现真实瓶颈后再设计流式读取或虚拟化;
|
||||
- **源码视图不能展示最终排版:** 它优先保证修改证据忠实;渲染预览另行处理安全、方言和资源边界;
|
||||
- **本地 HTTP 仍有攻击面:** 仅回环监听、同源、Host 校验、无写接口和严格路径白名单共同缩小范围;
|
||||
- **旧运行无法自动双栏:** 不回写历史或猜测路径,代价是完整查看需要重新产生带定位文件的新运行。
|
||||
|
||||
## 16. 批准后的实施边界
|
||||
|
||||
批准本文只授权:
|
||||
|
||||
1. 由现有实验层生成 `review-locator.json` 内容,由 artifact store 校验、写入、回读并随运行目录原子发布;
|
||||
2. 从 `reporting.py` 提取第 10 节所需的纯 Python 重放逻辑,并由报告生成和评审服务共同调用;
|
||||
3. 创建第 6 节列出的 Python 服务、前端子项目、锁文件、合成测试和构建配置;
|
||||
4. 实现第 8 至 11 节的本地只读 API、产物适配、双栏源码比较和组件阶段视图;
|
||||
5. 在当前用户已有的 nvm 中安装和使用 Node.js 24,并新增 `reviewer/.nvmrc` 和 `package.json` 的版本限制;
|
||||
6. 在 `.gitignore` 中忽略评审器构建、依赖和覆盖率产物;
|
||||
7. 更新根目录 README 当前能力与实际检查入口,并新增对应 explanation 和经验证 guide;
|
||||
8. 在合成测试通过后,按第 14.4 节对本地 5 份论文副本产生一次新的私有运行并完成只读页面验收。
|
||||
|
||||
批准不授权:
|
||||
|
||||
- 改变清洗规则、组件顺序、核心模型或既有 JSON schema;
|
||||
- 修改、覆盖、移动或复制任何输入 Markdown;
|
||||
- 为 GovDoc 或其他仓库外数据生成产物;
|
||||
- 实现 Markdown/HTML 渲染、图片访问、编辑、审核、回写、远程服务、认证或数据库;
|
||||
- 修改系统级 Node.js 安装,提交、推送、创建 PR 或发布。
|
||||
Reference in New Issue
Block a user