实现本地 Markdown 清洗评审器
This commit is contained in:
@@ -12,21 +12,30 @@
|
||||
- 只有 `success` 文档才产生正式的清洗后 Markdown。
|
||||
|
||||
已经实现的范围来自已批准的
|
||||
[`0005-local-experiment-runner-and-artifacts.md`](../design/0005-local-experiment-runner-and-artifacts.md)。
|
||||
[`0005-local-experiment-runner-and-artifacts.md`](../design/0005-local-experiment-runner-and-artifacts.md) 和
|
||||
[`0007-local-markdown-reviewer.md`](../design/0007-local-markdown-reviewer.md)。
|
||||
精确字段、校验和函数签名以 `src/mdpolish/` 中的代码与测试为准。
|
||||
|
||||
## 2. 三层怎样解耦
|
||||
## 2. 保存与评审怎样解耦
|
||||
|
||||
```text
|
||||
experiment.py
|
||||
├── pipeline.py 只负责内存清洗
|
||||
├── reporting.py 只负责 JSON、行列和 unified diff
|
||||
└── artifact_store.py 只负责日期目录、权限和原子发布
|
||||
│
|
||||
▼
|
||||
已发布运行目录
|
||||
│
|
||||
▼
|
||||
reviewer/ 只通过发布后的文件做本地只读评审
|
||||
```
|
||||
|
||||
- `experiment.py` 严格读取调用方显式列出的 UTF-8 Markdown,逐份调用同一个 `Pipeline`;
|
||||
- `reporting.py` 重放并校验 `Change` 的快照链,再生成机器可读审计和人可读 diff;
|
||||
- `artifact_store.py` 不理解清洗规则,只把已经生成的字节写入私有临时目录,校验后一次性发布。
|
||||
- 同仓库 `reviewer/server/` 只共用无文件 I/O 的 Python 快照重放模块,不导入组件或流水线;它读取 manifest、result、
|
||||
成功输出和本机定位文件。
|
||||
|
||||
因此,新增组件不会改变文件层;调整目录布局不会影响清洗和报告;修改 JSON 或 diff 时也不需要碰流水线。
|
||||
ClinDB 的 5 份论文、历史 arXiv 单组件组合和当前 first-batch 组合只存在于两个仓库内实验脚本,通用模块没有硬编码
|
||||
@@ -71,6 +80,7 @@ artifacts/
|
||||
└── runs/
|
||||
└── <run_id>/
|
||||
├── manifest.json
|
||||
├── review-locator.json
|
||||
└── documents/
|
||||
└── <document_id>/
|
||||
├── result.json
|
||||
@@ -95,6 +105,9 @@ artifacts/
|
||||
`changes.diff` 是原始输入到最终成功输出的 unified diff,只用于人工查看。它不包含绝对路径或时间戳,
|
||||
也不是修改重放的权威;机器审计仍以 `result.json` 为准。
|
||||
|
||||
`review-locator.json` 只记录本次运行目录、每份输入的绝对解析路径和输入哈希,供本地评审器重新找到完整原文。它不复制
|
||||
原文,不替代 manifest 或 result,也不作为可移植运行身份。绝对路径可能泄露本机目录结构,因此该文件同样是本地敏感数据。
|
||||
|
||||
## 6. 为什么 reporter 要重放修改
|
||||
|
||||
第二个组件看到的是第一个组件修改后的快照,因此后续 `Change.span` 不一定对应最初输入。为了生成准确行列,
|
||||
@@ -137,6 +150,9 @@ artifact store 先在同一日期的 `runs/` 下建立本次专用临时目录
|
||||
|
||||
当前只批准对 `data/md/` 中 5 份论文副本保存产物。仓库外 GovDoc 和其他真实数据没有因此获得输出授权。
|
||||
|
||||
同仓库只读页面的路径验证、组件快照重放和使用边界见
|
||||
[`local-markdown-reviewer.md`](local-markdown-reviewer.md)。
|
||||
|
||||
## 9. 已完成的真实验证
|
||||
|
||||
2026-08-22 使用 `paper.arxiv_submission_stamp` `1.0.0` 对 5 份本地论文副本完成一次保存型实验:
|
||||
@@ -173,3 +189,7 @@ artifact store 先在同一日期的 `runs/` 下建立本次专用临时目录
|
||||
当前完整运行方法见
|
||||
[`run-local-clindb-first-batch-experiment.md`](../guides/run-local-clindb-first-batch-experiment.md)。实验层的文件、
|
||||
JSON、diff 和权限契约没有因组件增多而改变。
|
||||
|
||||
2026-08-23 又产生运行 `clindb-first-batch-reviewer-v1`,用于验证新定位文件和本地页面:5/5 文档为 `success`,合计
|
||||
155 条修改,输入运行前后哈希不变。评审器 API 校验了 5 份原文、成功输出以及 8 个组件形成的 40 个阶段,所有文本哈希
|
||||
均与审计一致。实际页面启动步骤见 [`review-local-cleaning-run.md`](../guides/review-local-cleaning-run.md)。
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# 本地 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)。
|
||||
Reference in New Issue
Block a user