Files
mdpolish/research-wiki/explanation/local-markdown-reviewer.md
T

110 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地 Markdown 清洗评审器如何保持只读和可追踪
## 1. 它解决什么问题
本地清洗实验已经保存最终 Markdown、逐条审计和 unified diff,但人工评审仍需要在多个文件之间切换,也看不到某个组件
执行前后的完整文本。当前评审器把一次已发布运行变成只读页面:主视图比较原文和最终成功输出,组件时间线则比较每个
组件实际收到的快照和它产生的新快照。
实现范围来自已批准的
[`0007-local-markdown-reviewer.md`](../design/0007-local-markdown-reviewer.md)。精确 API、字段和运行行为以
[`reviewer/`](../../reviewer/) 中的代码、类型和测试为准。
## 2. 同仓库怎样保持解耦
```text
mdpolish 本地实验层
manifest / result / cleaned / review-locator
Python 产物适配器与共享重放 ──► 本地只读 API ──► React 页面
```
- `src/mdpolish/` 不导入 `reviewer/`
- `reviewer/server/` 只从 `mdpolish` 导入无文件 I/O 的 `_artifact_replay.py`,不导入组件、流水线或实验入口;
- 浏览器只读取 `/api/v1/`,不解析磁盘 JSON,也不知道绝对路径;
- Python wheel 不包含前端代码或 Node.js 依赖;
- 产物 schema 以后改变时,差异集中在服务端版本适配器,不扩散到页面组件。
前端与核心位于同一 Git 仓库,方便开发和评审,但仍是独立的 Node.js package。依赖版本只在
[`reviewer/package.json`](../../reviewer/package.json) 和锁文件维护。
## 3. 运行定位文件保存什么
新实验会在运行目录根部原子保存 `review-locator.json`。它记录:
- 运行 ID 和发布时的绝对运行目录;
- `manifest.json` 的固定相对位置;
- 每份文档的 ID、实际读取的绝对源路径和输入 SHA-256。
定位文件不复制原文,也不替代 manifest 或 result。评审器由用户显式指定当前运行目录;记录的旧运行目录只用于判断目录
是否被移动。服务只根据定位文件读取对应原文,并在每次展示前重新计算哈希。源文件不存在或内容变化时,页面明确报告
不可用,不按名称搜索替代文件。
绝对路径会暴露本机目录结构,所以定位文件与其他 artifact 一样使用 `0600` 权限并按本地敏感数据处理。历史运行没有该
文件时仍可查看清单和局部审计,但不能自动展示完整原文;评审器不会回写历史目录。
## 4. 服务为什么只读取一次运行
启动时必须传入一个具体运行目录。服务不会扫描 `artifacts/`,也没有让浏览器传入任意文件路径的 API。它先校验:
1. manifest、result 和存在的 locator 都是支持的 schema、严格 UTF-8 JSON
2. 文档、组件、状态、路径和计数彼此一致;
3. 所有 artifact 路径都留在所选运行目录;
4. 原文和成功输出的字节哈希与审计一致;
5. 成功文档确实同时具有 `cleaned.md``changes.diff`
HTTP 只监听 `127.0.0.1` 的随机空闲端口,只接受 `GET``HEAD`。服务拒绝非本机 Host、跨域 Origin、路径穿越和
写请求,不提供删除、移动、重新清洗或 shell 执行能力。响应禁止缓存,不开放 CORS,也不向浏览器返回绝对源路径。
## 5. 组件阶段怎样准确重放
`Change.span` 使用 Python Unicode 码点位置,而 JavaScript 编辑器使用 UTF-16 code unit。共享 Python 重放模块直接按
原生码点范围逐组件处理,不让浏览器应用修改:
1. 当前完整文本哈希必须等于组件批次的 `before_sha256`
2. 每条范围内文本必须等于 `before`
3. 同一批次不得有冲突范围,并按位置从后向前应用;
4. 应用后完整文本哈希必须等于 `after_sha256`
5. 全部组件结束后必须逐字等于 `cleaned.md`
6. 服务端另外从已验证的组件前快照派生 UTF-16 `editor_range`,只供 CodeMirror 跳转。
组件没有修改时,阶段前后文本和哈希相同,但该组件仍显示在时间线中。包含中文、emoji、组合字符、BOM、CRLF 和无末尾
换行的合成测试用于保护跨语言坐标。任一重放校验失败时,页面拒绝显示组件阶段,不通过搜索或 diff 猜测位置。
中间快照只在服务内存中按需生成,不保存新的 Markdown 文件。`failed``unstable` 文档只显示错误、残留候选和已有的
局部审计,不重建一份看似正式的部分输出。
## 6. 页面当前能看什么
页面当前提供:
- 运行状态、文档状态、哈希和实际 `Change` 数量;
- 完整原文与最终成功 Markdown 的只读双栏源码比较;
- 8 个组件的实际顺序、版本和每份文档修改数量;
- 任一组件执行前后的完整文本比较;
- 修改理由、派生行列、`before` / `after` 和同候选修改关联;
- `failed``unstable`、路径失效、哈希变化和未知 schema 的独立错误状态。
Markdown 只作为文本交给 CodeMirror,不进入 `innerHTML`。第一版不渲染 Markdown、HTML 或图片,不加载 CDN、远程字体、
遥测和其他外部资源,也不提供编辑、审核或回写。
## 7. 当前验证结果和边界
2026-08-23 使用 Python 3.13.11 和当前用户 nvm 中的 Node.js 24.19.0 完成:
- Python Ruff、mypy 和 229 项 pytest 通过;
- reviewer ESLint、TypeScript、6 项 Vitest 和生产构建通过;
- 新运行 `clindb-first-batch-reviewer-v1` 的 5 份论文全部 `success`,共 155 条实际修改;
- 5 份原文运行前后哈希不变;
- 本地 API 成功校验 5 份文档、8 个组件和 40 个组件阶段;
- 所有原文、成功输出和阶段前后文本的 SHA-256 与运行审计一致;
- 生产页面和全部本地构建资源可以通过只读服务读取,响应没有 CORS 并包含禁止缓存和内容类型保护头。
当前环境没有可用于自动视觉检查的本地浏览器,因此布局的真实浏览器视觉效果尚未验证。当前结果证明构建、服务、数据
重放和主要 React 状态可以运行,不等于已经完成跨浏览器、极端长度、渲染预览或生产部署验证。
实际启动与评审步骤见 [`review-local-cleaning-run.md`](../guides/review-local-cleaning-run.md)。