feat: 增加项目无关的本地清洗评审器
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
# 0014:项目无关的本地清洗评审器
|
||||
|
||||
## 状态
|
||||
|
||||
已于 2026-08-28 获用户明确批准,按本文第 15 节实施。本文自批准起冻结;后续改变决策需新增 design 并使用
|
||||
`supersedes` 指向本文。
|
||||
|
||||
用户在批准本文时同时明确要求:完成实施和验收后提交 Git,创建并推送 `v0.7.0` tag,再用同一个已验收 wheel 及其
|
||||
SHA-256 校验文件创建 GitHub Release。该授权不包括 PyPI、其他包索引、PR 或其他仓库修改。
|
||||
|
||||
`supersedes: 0008`(范围有限):本文拟改变“Web 评审器全部留在项目端”的边界。项目规则、流水线、文件写入和业务审核流程
|
||||
仍由使用项目拥有;`mdpolish` 只增加读取正式 review JSON 的通用本地查看工具。
|
||||
|
||||
`supersedes: 0011`(范围有限):本文拟增加 HTML 浏览器界面和本机只读服务,但不改变 `ReviewDocument`、可信重放、
|
||||
Markdown reporter 或核心无文件 I/O 的决定。
|
||||
|
||||
`supersedes: 0012`(范围有限):本文拟增加正式 JSON 的只读解析与校验入口。它不会把 JSON 恢复为 `ReviewDocument`,
|
||||
不会重新运行 `Modifier`,也不会把机器投影变成可重新应用修改的权威输入。
|
||||
|
||||
历史 `0007` 已经被 `0008` 替代,不因本文重新生效。本文只借鉴其本机服务安全边界,以及
|
||||
`/home/lihaoze/work/mdpolish-wheel-pilot` 中已经实现的双栏界面;不恢复旧 artifact、locator、manifest 或项目实验系统。
|
||||
|
||||
## 1. 问题与可观察现象
|
||||
|
||||
`mdpolish v0.6.0` 已经能把可信的 `ReviewDocument` 生成为 schema `1.0` 的 `full` JSON。这个 JSON 包含完整输入、当前文本、
|
||||
所有 Modifier 阶段和实际 Change,足以支持准确的浏览器评审。
|
||||
|
||||
但是当前库仍明确不提供 JSON 读取器、本地服务或评审页面。每个项目如果要查看结果,还要重复完成以下工作:
|
||||
|
||||
1. 解析并校验 `mdpolish.review` JSON;
|
||||
2. 验证正文哈希、阶段链、Change 范围和计数;
|
||||
3. 把 Python Unicode 码点坐标转换成浏览器编辑器使用的 UTF-16 坐标;
|
||||
4. 编写本机只读服务、双栏界面和 Change 跳转;
|
||||
5. 持续跟随 review schema 和前端依赖变化。
|
||||
|
||||
wheel-pilot 已经按自己的 `0004` design 实现一版 React + CodeMirror 评审器。只读调查确认它的主要交互是通用的:选择文档、
|
||||
查看总体输入/输出、按 Modifier 查看完整阶段、保留零修改阶段、滚动长文档,以及点击 Change 跳转。当前参考生产构建约
|
||||
842 KiB;这只是本次方案比较的观测值,不是未来 wheel 大小承诺。
|
||||
|
||||
wheel-pilot 中真正属于项目的部分是论文 Modifier、七步顺序、批处理、`artifacts/v0.6.0/` 路径和五篇文档身份。
|
||||
页面和只读服务不需要理解这些业务事实。因此,让每个项目继续复制整套 viewer 会形成重复实现和不一致的校验口径。
|
||||
|
||||
## 2. 决定摘要
|
||||
|
||||
第一版采用以下边界:
|
||||
|
||||
```text
|
||||
使用项目
|
||||
├── 选择 Modifier、参数和顺序
|
||||
├── Pipeline.transform()
|
||||
├── build_review_document()
|
||||
├── render_json_report(..., detail="full")
|
||||
└── 自行保存 *.review.json、决定权限与保留周期
|
||||
│
|
||||
▼
|
||||
mdpolish-reviewer --review-dir <明确目录>
|
||||
├── mdpolish 官方 JSON 解析与语义校验
|
||||
├── Python 码点 → UTF-16 只读定位
|
||||
├── 回环地址上的只读 HTTP 服务
|
||||
└── React + CodeMirror 双栏页面
|
||||
```
|
||||
|
||||
评审器随同一个 `mdpolish` wheel 交付,但与内存清洗核心隔离。安装 wheel 不会启动服务、读取文件或改变任何 Markdown;
|
||||
只有用户显式运行评审器命令时,工具才读取明确传入的目录。
|
||||
|
||||
项目无需复制前端或 Python 服务。项目只要保存正式 `full` JSON,就能使用同一界面。
|
||||
|
||||
## 3. 目标与非目标
|
||||
|
||||
### 3.1 目标
|
||||
|
||||
- 为所有使用项目提供同一套本地只读清洗结果页面;
|
||||
- 保留 wheel-pilot 当前已经验证的双栏布局、Modifier 时间线、完整滚动和 Change 跳转体验;
|
||||
- 直接消费 `mdpolish.review` 正式机器投影,不建立项目 artifact schema;
|
||||
- 由 `mdpolish` 提供正式 JSON 的解析和校验,不让 viewer 私下维护另一套 schema 解释;
|
||||
- 对 `full` 数据验证正文哈希、阶段首尾、Change 批次、位置、计数和状态一致性,失败时拒绝近似展示;
|
||||
- 保持 Modifier 阶段坐标的原语义,只为 CodeMirror 额外派生 UTF-16 范围;
|
||||
- 只绑定本机回环地址,只提供同源静态页面和只读 API;
|
||||
- 前端生产资源随 wheel 提供,使用项目运行页面时不需要 Node.js,也不从 CDN 下载资源;
|
||||
- 保持普通 Python 核心安装零第三方运行依赖;
|
||||
- 使用合成文本覆盖 Unicode、不同换行、空文档、零修改阶段、失败和不稳定状态。
|
||||
|
||||
### 3.2 非目标
|
||||
|
||||
- 不替项目读取原始 Markdown、运行 Pipeline、选择 Modifier 或保存 review JSON;
|
||||
- 不定义项目的目录层级、批处理协议、文档 ID、标题、审核状态、权限模型或保留周期;
|
||||
- 不编辑、接受、拒绝、撤销或重新应用 Change,不从页面触发清洗;
|
||||
- 不渲染 Markdown 排版,不执行原文中的 HTML,不加载图片、字体或其他外部资源;
|
||||
- 不提供上传、远程访问、账户、数据库、多人协作、批注或生产部署接口;
|
||||
- 不把 JSON 恢复为 `ReviewDocument`、`TransformResult` 或 Pipeline;
|
||||
- 不改变 schema `1.0` 的字段、哈希、坐标、detail 或正文暴露语义;
|
||||
- 不增加默认流水线、项目 profile、业务规则或真实样本;
|
||||
- 不在本轮修改或删除 wheel-pilot 的 reviewer。它的迁移与清理必须在该仓库另行批准;
|
||||
- 不读取、复制、修改或提交真实文档、历史报告和外部数据。
|
||||
|
||||
## 4. 职责边界
|
||||
|
||||
| 能力 | `mdpolish` | 使用项目 |
|
||||
| --- | --- | --- |
|
||||
| Modifier 规则、参数和顺序 | 不拥有 | 拥有 |
|
||||
| 清洗执行与内存审计 | 提供通用核心 | 显式调用 |
|
||||
| `ReviewDocument` 与正式 JSON 生产 | 提供 | 决定是否生成 |
|
||||
| review JSON 文件名、目录和覆盖策略 | 不决定 | 拥有 |
|
||||
| review JSON 内容解析与通用一致性校验 | 提供 | 不再重复实现 |
|
||||
| UTF-16 编辑器定位 | reviewer 内部提供 | 不需要实现 |
|
||||
| 双栏页面、Modifier 时间线和 Change 跳转 | 提供 | 直接使用 |
|
||||
| 文档业务名称、审核结论、批注和权限 | 不拥有 | 如有需要自行实现 |
|
||||
| 数据脱敏、访问控制和保留周期 | 只说明风险 | 拥有 |
|
||||
|
||||
reviewer 不知道 JSON 对应哪个原始文件。页面展示的文档标签只能来自 review JSON 文件名,不能猜测输入路径、论文标题或业务
|
||||
身份。项目如果需要额外业务字段,应建设自己的外层页面;第一版不为此增加 sidecar manifest 或配置插件。
|
||||
|
||||
## 5. 方案比较
|
||||
|
||||
| 方案 | 优点 | 代价 | 决定 |
|
||||
| --- | --- | --- | --- |
|
||||
| 每个项目继续复制 wheel-pilot reviewer | 上游 wheel 最小 | 校验、API、前端和依赖重复;行为会漂移 | 不采用 |
|
||||
| 单独发布 `mdpolish-reviewer` wheel | 核心分发物最小 | 两个包必须配对版本、安装和发布;当前 reviewer 无第三方 Python 依赖 | 第一版不采用 |
|
||||
| 同一 wheel 内放独立 reviewer 模块和静态资源 | 一个版本同时约束 producer、reader 和页面;项目只安装一个 wheel | 所有人下载的 wheel 都会增加静态资源体积 | 采用 |
|
||||
| 运行时从网络下载页面 | wheel 较小 | 引入网络、版本漂移、隐私和供应链风险 | 不采用 |
|
||||
| pip 构建 wheel 时自动运行 npm | 不提交生产 bundle | Git direct install 需要 Node.js 和网络,破坏现有 Python 安装体验 | 不采用 |
|
||||
| 提交经过检查的生产 bundle并打进 wheel | 使用者不需要 Node.js;Python 构建保持简单 | 源码与生成资源必须同步检查,Git diff 会包含压缩文件 | 采用 |
|
||||
|
||||
这里的“同一 wheel”不表示 reviewer 成为 Pipeline 的一部分。依赖方向固定为 reviewer 可以导入 review JSON 解析能力,
|
||||
`models.py`、`edits.py`、`modifier.py` 和 `pipeline.py` 不导入 reviewer、HTTP 或前端资源。
|
||||
|
||||
## 6. 正式 JSON 读取入口
|
||||
|
||||
第一版拟在受支持的 `mdpolish.review` 路径增加:
|
||||
|
||||
```python
|
||||
class ReviewParseError(ValueError):
|
||||
"""机器投影 JSON 不能被安全读取。"""
|
||||
|
||||
|
||||
def parse_json_report(
|
||||
report: str,
|
||||
*,
|
||||
expected_detail: ReviewDetail | str | None = None,
|
||||
) -> ReviewProjection:
|
||||
...
|
||||
```
|
||||
|
||||
它接收内存字符串并返回只含 JSON 基本值的新 dict/list 容器。它不接收路径,不读取文件,不返回 `ReviewDocument`,也不重新运行
|
||||
Modifier。`expected_detail=None` 接受 schema 支持的任一已知 detail;reviewer 必须显式要求 `full`。
|
||||
|
||||
解析至少执行以下通用检查:
|
||||
|
||||
- 拒绝重复 object key、非标准 `NaN` / `Infinity`、孤立 surrogate 和非 JSON 值;
|
||||
- 要求 `schema_name == "mdpolish.review"`,并按 schema major 兼容规则处理版本;
|
||||
- 验证已知 detail 的必需字段、类型、枚举、整数范围、引用位置和正文暴露边界;
|
||||
- 对同一 schema major 的未知 object 字段按现有兼容规则忽略其语义,不改变已知字段解释;
|
||||
- 遇到未知状态、detail、坐标契约或无法安全解释的 enum 时失败,绝不把它降级成 `success`;
|
||||
- 错误消息只给字段路径和契约类别,不拼入正文、参数、reason 或诊断消息。
|
||||
|
||||
`summary` 和 `changes` 不含完整阶段文本,解析器只能验证它们实际携带的结构和引用。`full` 还必须验证:
|
||||
|
||||
1. input、current 和每个阶段全文的 UTF-8 SHA-256 与码点长度;
|
||||
2. 第一个完成阶段从 input 开始,相邻阶段首尾完全相接;
|
||||
3. 每个 Change 的 modifier 引用、哈希、范围、原文、行列和顺序;
|
||||
4. 使用核心共用的精确编辑应用原语重放当前阶段 Change,结果必须等于 stage after;
|
||||
5. 零修改阶段的 before 与 after 完全相同;
|
||||
6. 完成阶段末尾等于 current,阶段数量与 `stages_complete` 及错误阶段相容;
|
||||
7. counts、错误、残留候选和 `success` / `failed` / `unstable` 状态相容。
|
||||
|
||||
第 4 点比 wheel-pilot 当前服务只检查“before 中能找到片段”更严格。它防止攻击者同时篡改 stage after 正文和哈希后,页面仍把
|
||||
一个并非由所列 Change 产生的结果展示为可信阶段。
|
||||
|
||||
解析器可以从 `edits.py` 复用私有验证与应用原语,但不得复制一份不同的冲突、排序或字符串应用规则。正常清洗公共接口和
|
||||
schema `1.0` 生产结果必须保持不变。
|
||||
|
||||
## 7. 输入集合与文件边界
|
||||
|
||||
第一版本地入口拟为:
|
||||
|
||||
```text
|
||||
mdpolish-reviewer --review-dir <review_directory> [--port <port>]
|
||||
```
|
||||
|
||||
同时支持等价的模块入口:
|
||||
|
||||
```text
|
||||
python -m mdpolish.reviewer --review-dir <review_directory>
|
||||
```
|
||||
|
||||
`--review-dir` 必填,没有当前目录或 `artifacts/` 的隐式默认值。服务只读取该目录直属的 `*.review.json` 普通文件,按文件名
|
||||
确定顺序,不递归、不扫描父目录、不跟随目录或文件符号链接。目录为空时明确失败。
|
||||
|
||||
文件适配器负责严格 UTF-8、BOM、读取错误和路径检查,然后把内存字符串交给 `parse_json_report(...,
|
||||
expected_detail="full")`。核心解析函数不知道路径。
|
||||
|
||||
第一版使用去掉末尾 `.review.json` 后的文件名作为页面标签和内部文档身份。例如 `paper-01.review.json` 显示为
|
||||
`paper-01`;它不自动补 `.md`,也不声称这是原输入文件名。重名、空身份或无法安全形成 URL 身份时启动失败。
|
||||
|
||||
目录名只作为页面顶部的本地集合标签,不成为运行 ID、项目 ID 或 schema 字段。绝对路径不返回给浏览器,也不打印正文。
|
||||
|
||||
review JSON 可能包含完整敏感正文。`mdpolish` 不自动创建、复制、移动、删除或清理这些文件;项目继续负责把它们保存在
|
||||
合适的本地目录,设置权限、Git 忽略和保留周期。
|
||||
|
||||
## 8. 本机服务和内部 API
|
||||
|
||||
服务必须保持以下边界:
|
||||
|
||||
- 只绑定 `127.0.0.1`,默认端口 `0` 由操作系统选择;
|
||||
- 只接受 `GET` 和 `HEAD`,其他方法返回 `405`;
|
||||
- 校验 `Host` 与可选 `Origin`,不开放 CORS;
|
||||
- 不提供任意文件路径、写入、删除、移动、重新运行或 shell 接口;
|
||||
- 静态资源只来自 wheel 内固定目录,拒绝路径穿越和符号链接;
|
||||
- 页面和 API 设置 `no-store`、CSP、`nosniff`、`no-referrer` 和禁止 frame 的响应头;
|
||||
- 日志不输出正文、修改片段、参数或绝对 review 目录;
|
||||
- 退出时不修改项目目录或浏览器外状态;
|
||||
- 不自动打开浏览器,终端只打印明确的回环 URL 和不含敏感路径的文档数量。
|
||||
|
||||
浏览器使用版本化但只服务同一 reviewer 的内部 `/api/v1/`。API 至少提供:
|
||||
|
||||
| 资源 | 内容 |
|
||||
| --- | --- |
|
||||
| 集合摘要 | 集合标签、聚合状态、文档顺序和计数,不含正文 |
|
||||
| 文档比较 | 输入、成功 current、Modifier 摘要、全部 Change 和诊断 |
|
||||
| Modifier 阶段 | 指定完整阶段的 before、after、Change 和 UTF-16 定位 |
|
||||
|
||||
这是 Python 服务与同 wheel 页面之间的内部契约,不承诺给第三方项目直接调用。跨项目稳定数据契约仍是
|
||||
`mdpolish.review` schema 和第 6 节的解析入口,不能把本地 HTTP API 变成第二个公共 artifact schema。
|
||||
|
||||
第一版启动时校验目录内全部 review。任何一份损坏都会阻止服务启动,并指出不含正文的文件标签和错误类别;不在同一次集合
|
||||
中混合“已可信”和“猜测展示”的文档。若真实项目证明需要隔离单篇坏文件,再新增 design 改变失败策略。
|
||||
|
||||
## 9. UTF-16 编辑器定位
|
||||
|
||||
schema `1.0` 的 `span.start` / `span.end` 是所属阶段 before 文本中的 Python Unicode 码点半开范围。CodeMirror 使用
|
||||
JavaScript UTF-16 code unit。reviewer 在 full 阶段已经通过校验后,派生:
|
||||
|
||||
```json
|
||||
"editor_range": {
|
||||
"start": 10,
|
||||
"end": 12
|
||||
}
|
||||
```
|
||||
|
||||
这个范围只存在于内部 API,用于左栏选区和滚动,不写回正式 review JSON,不成为新的修改权威。中文基本平面字符通常不改变
|
||||
数值,emoji 等补充平面字符会占两个 UTF-16 code unit。组合字符仍按原字符串逐码点转换,不做 Unicode 规范化。
|
||||
|
||||
转换必须以该 Change 所属的 `stage.before.markdown` 为输入。不得把中间阶段 span 套到原始输入、最终 current 或其他
|
||||
Modifier 阶段。
|
||||
|
||||
## 10. 页面行为
|
||||
|
||||
第一版以 wheel-pilot 当前页面为迁移基线,保留用户已经满意的视觉和主要交互:
|
||||
|
||||
- 启动后选择第一份文档,默认比较完整 input 与成功 current;
|
||||
- 左侧列出文档和按位置排序的全部 Modifier;
|
||||
- 选择 Modifier 后比较该阶段完整 before / after;
|
||||
- 零修改 Modifier 仍显示,前后全文相同;
|
||||
- 不折叠未修改区域,长文由 MergeView 容器完整纵向滚动;
|
||||
- 总结果列出全部 Change,阶段视图只列当前 Modifier 的 Change;
|
||||
- 点击 Change 时先切换所属阶段,等待 MergeView 用新 before/after 重建,再选中并居中左栏范围;
|
||||
- `failed` 和 `unstable` 只显示准确的 partial/错误/残留证据,不把 current 命名为“清洗后”;
|
||||
- Markdown、HTML、图片和脚本语法只作为只读源码,不渲染、不请求外部资源;
|
||||
- 页面文案使用简体中文,第一版不建设主题、国际化或项目定制接口。
|
||||
|
||||
前端仍采用 React、TypeScript、Vite、CodeMirror MergeView、Vitest 和 React Testing Library。准确版本只在
|
||||
`reviewer/package.json` 与锁文件中维护,不在 design 和 README 复制第二份易漂移清单。Node.js 24 只用于仓库开发、测试和
|
||||
生成生产 bundle;使用 wheel 查看结果不需要 Node.js。
|
||||
|
||||
## 11. 源码和交付结构
|
||||
|
||||
批准后拟增加:
|
||||
|
||||
```text
|
||||
reviewer/
|
||||
├── .nvmrc
|
||||
├── package.json
|
||||
├── package-lock.json
|
||||
├── src/ # React、API client、运行时响应校验
|
||||
└── tests/ # 合成前端测试
|
||||
src/mdpolish/
|
||||
├── review.py # 增加正式 JSON 解析入口
|
||||
├── reviewer.py # 路径适配、本地 API、HTTP 服务和 CLI
|
||||
└── _reviewer_static/ # 经检查并提交的生产 HTML/JS/CSS
|
||||
tests/
|
||||
├── test_review_parsing.py # schema 与 full 语义校验
|
||||
└── test_reviewer.py # 目录、HTTP、安全和 UTF-16
|
||||
```
|
||||
|
||||
最终文件拆分可以在不改变职责的前提下机械调整,例如把 HTTP handler 放入私有模块;不得把 viewer 逻辑塞进
|
||||
`pipeline.py` 或让核心导入前端资源。
|
||||
|
||||
生产 bundle 提交到 `_reviewer_static/` 并包含在 wheel,使从 Git 地址或 Release wheel 安装时不调用 npm。前端源码或锁文件
|
||||
变化后必须重新构建并检查 bundle;Python wheel 构建只打包现有已验证资源。测试必须发现缺失或陈旧入口资源,不能在没有
|
||||
页面时静默构建一个“成功”wheel。
|
||||
|
||||
前端 bundle 引入的第三方代码必须在仓库和 wheel 中保留适用的版权与许可证说明。实现验收要列出实际 bundle 和 wheel 大小,
|
||||
检查 wheel 不包含 `node_modules`、前端测试、coverage、source map、真实数据或 review JSON。
|
||||
|
||||
`pyproject.toml` 增加 `mdpolish-reviewer` console script 和静态 package data。普通 `mdpolish` 导入路径不重新导出服务对象;
|
||||
公共 Python 读取入口仍位于 `mdpolish.review`。
|
||||
|
||||
## 12. 兼容与版本
|
||||
|
||||
本文增加公共 JSON 读取函数、公共 CLI、HTML 页面和 wheel 文件,属于 `0.x` 阶段的功能性次版本变化。实施候选版本计划从当前
|
||||
未发布的 `0.6.1` 更新为 `0.7.0`;不改变现有 Modifier 版本、Pipeline 结果或 schema `1.0`。
|
||||
|
||||
reviewer 和 producer 随同一个 wheel 发布,避免建立第二套版本配对规则。页面内部 API 可以随同一 wheel 修改,但必须同步
|
||||
Python、TypeScript 运行时校验和测试。
|
||||
|
||||
正式 `mdpolish.review` schema 继续独立版本。若未来 schema major 改变,reader 必须拒绝;同 major 的加法字段按
|
||||
`0012` 的兼容规则处理。任何修改现有字段含义、正文暴露等级或坐标口径的工作仍需新的 design,不能借 reviewer 页面绕过。
|
||||
|
||||
## 13. 测试与验收
|
||||
|
||||
### 13.1 JSON 解析与失败关闭
|
||||
|
||||
只使用虚构小文本,至少覆盖:
|
||||
|
||||
- `summary`、`changes`、`full` 的必需字段和暴露边界;
|
||||
- 空文档、空流水线、零修改 Modifier 和多 Modifier 链;
|
||||
- success、unstable、preflight failure、transform failure 和 final review failure;
|
||||
- 中文、emoji、组合字符、BOM 字符、LF、CRLF、CR 和无末尾换行;
|
||||
- full 中所有文本哈希、阶段链、Change 重放、位置、计数和状态;
|
||||
- 重复键、BOM 文件、错误 UTF-8、非有限数、孤立 surrogate、未知 schema major/detail/enum;
|
||||
- 损坏 input/current/stage 哈希、断裂阶段、错误 span、冲突 Change、篡改 after、零修改阶段却改变文本;
|
||||
- 解析错误不包含正文、参数、reason 或诊断消息哨兵;
|
||||
- 解析器不读文件、不访问网络、不调用 Modifier,不改变传入或返回外部容器。
|
||||
|
||||
同一组合成 full report 应分别通过 `ReviewDocument` 生产路径和 JSON 读取路径,逐阶段比较相同的 before、after、哈希与 Change
|
||||
顺序。只比较最终 current 不足以证明 reader 与 producer 一致。
|
||||
|
||||
### 13.2 本地服务
|
||||
|
||||
- 明确目录的多份合法 full JSON 可以得到集合、文档和 Modifier 阶段响应;
|
||||
- 文件名顺序、标签、零修改阶段和聚合计数正确;
|
||||
- 空目录、递归文件、符号链接、未知文件、损坏 JSON 和重复身份失败;
|
||||
- 路径穿越、异常 Host/Origin、未知路由和非 GET/HEAD 请求被拒绝;
|
||||
- 服务只绑定 `127.0.0.1`,安全响应头和媒体类型正确;
|
||||
- API 和日志不返回绝对目录,不输出正文到终端;
|
||||
- 码点到 UTF-16 的转换覆盖 emoji、组合字符、不同换行和空插入;
|
||||
- 缺失静态资源时明确失败,不访问 CDN 或任意磁盘路径。
|
||||
|
||||
### 13.3 前端
|
||||
|
||||
- 文档列表、聚合状态和修改数显示正确;
|
||||
- 总结果、Modifier 阶段和零修改阶段切换正确;
|
||||
- Change 筛选、同阶段跳转、跨 Modifier 跳转和重建后聚焦正确;
|
||||
- 长文不生成折叠区,MergeView 保持纵向滚动;
|
||||
- Markdown 中的 HTML、图片和脚本保持惰性文本;
|
||||
- failed、unstable、API 错误和未知内部响应不显示虚构成功结果;
|
||||
- ESLint、TypeScript、Vitest 和 Vite 生产构建通过;
|
||||
- 真实浏览器滚动和视觉仍需人工验收,jsdom 结果不能替代。
|
||||
|
||||
### 13.4 回归与交付
|
||||
|
||||
实施完成后运行根 README 当时列出的全部检查,并额外确认:
|
||||
|
||||
- 现有 Pipeline、Modifier、ReviewDocument、dict/JSON producer 和 Markdown reporter 行为不变;
|
||||
- 普通核心安装仍没有第三方 Python 运行依赖;
|
||||
- 最低和当前支持的 Python 环境都能启动 reviewer 并读取合成 full JSON;
|
||||
- wheel 能从仓库外安装和启动页面,静态资源、console script 与版本正确;
|
||||
- 记录 wheel 文件清单、压缩/解压大小和相对 `0.6.1` 候选的增量;
|
||||
- wheel 不包含 `node_modules`、前端测试、source map、真实报告、项目规则或数据;
|
||||
- 前端第三方许可证说明完整;
|
||||
- `AGENTS.md` 与 `CLAUDE.md` 除标题外正文一致;
|
||||
- Git diff 不混入 wheel-pilot、真实文本、大型实验产物或用户已有改动。
|
||||
|
||||
真实项目数据不是实现正确性的必要条件。批准本文也不授权读取或复制 wheel-pilot 的 `local-data/` 和 `artifacts/`。
|
||||
若用户随后希望确认页面视觉,可以由 wheel-pilot 继续使用自己的已有结果,或在该仓库另行批准改用上游候选 wheel。
|
||||
|
||||
## 14. 风险与代价
|
||||
|
||||
- **wheel 明显变大:** 当前参考 bundle 约 842 KiB,实际实现仍需记录。换来的是项目不再安装 Node.js 或复制页面。
|
||||
- **公共 CLI 需要兼容维护:** `--review-dir` 和只读行为一旦发布就不能随意更名;第一版参数保持最少。
|
||||
- **提交生成资源会增加 diff:** 这是保证 Git direct install 不依赖 npm 的代价,必须用构建和 wheel 测试防止陈旧 bundle。
|
||||
- **解析器公共表面积增加:** 它需要长期跟随 schema,但比每个项目各写一套校验更可控。
|
||||
- **full JSON 占用内存:** 每阶段重复全文,reader 和页面还会产生额外容器;第一版不承诺无限文档或批量规模。
|
||||
- **本机 HTTP 仍有攻击面:** 回环、Host/Origin 校验、无 CORS、CSP、无写接口和明确目录都是必需边界。
|
||||
- **文件名不等于业务身份:** 通用 schema 没有路径和标题;第一版宁可显示保守标签,也不引入项目 manifest。
|
||||
- **页面可能被误当成审核系统:** 它只展示清洗证据,不记录批准、拒绝、责任人或结论。
|
||||
- **wheel-pilot 暂时重复:** 上游实现和发布前,两边 reviewer 会并存。迁移应在消费者仓库单独评审,不能同时删除以制造大爆炸变更。
|
||||
|
||||
## 15. 批准后的实施边界
|
||||
|
||||
用户明确批准本文后,只授权:
|
||||
|
||||
1. 在当前 `mdpolish` 仓库实现第 6 至 11 节的 JSON reader、本机服务、前端、静态资源和 console script;
|
||||
2. 从 wheel-pilot 已提交的页面与服务中参考或迁移项目无关代码,但不修改该仓库,不读取其真实数据与 artifacts;
|
||||
3. 为共享精确编辑语义做必要的私有机械复用,不改变公共清洗结果;
|
||||
4. 新增合成 Python/TypeScript 测试,并完成第 13 节的构建、wheel 和仓库外 smoke test;
|
||||
5. 实现完成后更新 README 当前能力、`review-projection.md`、schema reference 和经实际验证的 guide;
|
||||
6. 把候选包版本更新为 `0.7.0`,报告实际 diff、测试、bundle、wheel 和许可证检查。
|
||||
|
||||
本次批准还授权在全部必需验收通过后:
|
||||
|
||||
1. 提交本文及其实施,使用中文 Git commit subject;
|
||||
2. 把当前分支提交和 `v0.7.0` tag 推送到 `origin`;
|
||||
3. 只把提交前已经验收的同一个 `mdpolish-0.7.0-py3-none-any.whl` 和 SHA-256 校验文件上传到
|
||||
`v0.7.0` GitHub Release,不能在 tag 后重新构建另一份 wheel 冒充已验收产物。
|
||||
|
||||
本次批准不授权:
|
||||
|
||||
- 创建 PR,发布到 PyPI、GitHub Packages 或其他包索引;
|
||||
- 修改 wheel-pilot、其他仓库、真实数据或历史 artifacts;
|
||||
- 增加远程绑定、写接口、上传、认证、数据库、项目 metadata、审核工作流或 Markdown 渲染;
|
||||
- 改变清洗规则、Modifier 顺序、误删容忍度、`RunStatus`、`ReviewDocument` 字段或 schema `1.0` 语义。
|
||||
@@ -18,12 +18,19 @@
|
||||
ReviewDocument
|
||||
├── render_markdown_report() ─────────────► 内存 Markdown 字符串
|
||||
│
|
||||
└── review_document_to_dict(detail=...)
|
||||
└── review_document_to_dict(detail=...)
|
||||
├───────────────────────────► 项目自己的界面或转换层
|
||||
└── render_json_report() ──► 内存 JSON 字符串
|
||||
│
|
||||
▼
|
||||
parse_json_report()
|
||||
│
|
||||
▼
|
||||
本地只读 reviewer 页面
|
||||
```
|
||||
|
||||
文件读取、保存位置、HTML 页面、权限和审核流程仍由调用项目决定。
|
||||
清洗输入读取、报告保存位置、权限和审核流程仍由调用项目决定。项目可以把正式 `full` JSON 保存到自己的目录,再显式启动
|
||||
`mdpolish-reviewer`;通用页面不替项目生成、命名或清理这些文件。
|
||||
|
||||
## 2. 构建过程为什么可以失败关闭
|
||||
|
||||
@@ -111,5 +118,38 @@ modifier 参数、位置和哈希,并在根对象写入 `schema_name=mdpolish.
|
||||
拒绝 NaN / Infinity,不写文件或添加 BOM。Markdown reporter 继续直接读取 `ReviewDocument`:它面向人类排版并包含 diff、
|
||||
动态围栏和 residual 展示限额,不依赖机器 schema。
|
||||
|
||||
机器投影只生产,不提供 JSON 到 `ReviewDocument` 的反序列化,也不能用于重新应用修改。完整字段、坐标、错误代码和兼容规则
|
||||
见 [`review-projection-schema-v1.md`](../reference/review-projection-schema-v1.md)。
|
||||
`parse_json_report()` 读取内存 JSON 字符串,返回新的普通 dict/list 容器。它不是 `ReviewDocument` 反序列化器,也不能用于
|
||||
重新应用修改。三个 detail 都会检查字段、枚举、引用、顺序、计数和正文暴露边界;只有 `full` 带有完整阶段文本,因此还能
|
||||
验证所有正文哈希、阶段链、Change 原文和行列,并使用 `edits.py` 的同一套精确编辑原语重放每个阶段。重放结果不等于
|
||||
stage after 时直接抛出 `ReviewParseError`,不会因为攻击者同时更新正文和声明哈希就接受伪造阶段。
|
||||
|
||||
schema 同一 major 的未知 object 字段不改变已有字段解释;未知 major、detail、状态、坐标契约或 enum 会被拒绝。解析错误只
|
||||
说明字段路径和契约类别,不复制正文、参数、reason 或诊断消息。
|
||||
|
||||
完整字段、坐标、错误代码、读取保证和兼容规则见
|
||||
[`review-projection-schema-v1.md`](../reference/review-projection-schema-v1.md)。
|
||||
|
||||
## 7. 本地页面为什么仍然保持项目无关
|
||||
|
||||
本地 reviewer 只接受用户明确传入的一个目录,并读取其中直属的 `*.review.json`。它从文件名派生保守的页面标签,不读取
|
||||
原始 Markdown 路径、项目 manifest、默认流水线或业务状态。Python 服务负责正式 JSON 校验和码点到 UTF-16 的只读定位,
|
||||
React 页面只消费同源内部 API。
|
||||
|
||||
```text
|
||||
项目保存的 full JSON
|
||||
│
|
||||
▼
|
||||
官方 reader:验证 schema、哈希、阶段和 Change
|
||||
│
|
||||
▼
|
||||
127.0.0.1 上的只读 API
|
||||
│
|
||||
▼
|
||||
文档列表 ── Modifier 时间线 ── 双栏源码比较 ── Change 跳转
|
||||
```
|
||||
|
||||
`editor_range` 只用于 CodeMirror 选择和滚动。正式 span 仍是所属 `stage.before.markdown` 中的 Python 码点半开范围,不写回
|
||||
JSON,也不变成新的审计权威。
|
||||
|
||||
服务只绑定回环地址,只接受 `GET` / `HEAD`,校验 Host 和 Origin,不开放 CORS,也没有写入、重新清洗、上传或 shell 接口。
|
||||
页面不渲染 Markdown 和 HTML,不加载图片或外部资源。它展示的是清洗证据,不记录批准、拒绝、批注或审核结论。
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# 使用本地清洗评审页面
|
||||
|
||||
项目已经生成 `ReviewDocument`,但不希望自己维护 JSON 校验、HTTP 服务和前端时,可以把正式 `full` JSON 保存到一个明确
|
||||
目录,再由 `mdpolish-reviewer` 只读展示。评审器不会读取原始 Markdown 路径,也不会运行 Pipeline 或写回结果。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 已安装 `mdpolish 0.7.0`;普通 wheel 即可,不需要安装 Node.js 或任何第三方 Python 运行依赖;
|
||||
- 调用项目已经显式选择 Modifier、完成 `Pipeline.transform()` 并得到相应输入文本;
|
||||
- 项目已经决定评审 JSON 的保存目录、权限、Git 忽略和保留周期。
|
||||
|
||||
`full` JSON 会重复包含输入、当前文本和各 Modifier 阶段全文。不要把它放进公开目录、提交到 Git,或当作脱敏日志。
|
||||
|
||||
## 1. 保存正式 full JSON
|
||||
|
||||
下面的 `input_markdown` 和 `result` 来自调用项目已有的内存清洗流程:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from mdpolish.review import build_review_document, render_json_report
|
||||
|
||||
review = build_review_document(input_markdown, result)
|
||||
review_path = Path("artifacts/reviews/example.review.json")
|
||||
review_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
review_path.write_text(
|
||||
render_json_report(review, detail="full") + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
```
|
||||
|
||||
一个目录可以放多份直属的 `*.review.json`。评审器不会递归查找子目录,也不会跟随文件或目录符号链接。文件名去掉
|
||||
`.review.json` 后只是页面标签,不代表原始文件路径或业务身份。
|
||||
|
||||
## 2. 启动页面
|
||||
|
||||
```bash
|
||||
mdpolish-reviewer --review-dir artifacts/reviews
|
||||
```
|
||||
|
||||
也可以使用等价入口:
|
||||
|
||||
```bash
|
||||
python -m mdpolish.reviewer --review-dir artifacts/reviews
|
||||
```
|
||||
|
||||
默认由系统选择空闲端口。成功时终端会显示类似结果:
|
||||
|
||||
```text
|
||||
mdpolish 评审器已启动:http://127.0.0.1:43127(1 份文档)
|
||||
```
|
||||
|
||||
在同一台机器的浏览器中打开实际打印的 URL。页面左侧选择文档或 Modifier;总结果比较完整输入与成功输出,Modifier 视图
|
||||
比较该阶段的完整 before/after。点击 Change 会切换到所属阶段并定位左栏原文;零修改阶段仍可选择。
|
||||
|
||||
按 `Ctrl+C` 停止服务。服务只绑定 `127.0.0.1`,停止时不会改动评审目录。
|
||||
|
||||
## 3. 失败时怎么判断
|
||||
|
||||
| 现象 | 含义与处理 |
|
||||
| --- | --- |
|
||||
| `评审目录没有直属 full review JSON` | 检查目录是否正确,以及文件名是否以 `.review.json` 结尾 |
|
||||
| `detail does not match the requested value` | 项目保存的不是 `detail="full"`,重新从可信 `ReviewDocument` 生成 |
|
||||
| `review JSON parsing failed` | JSON 结构、哈希、阶段链、Change 重放或状态不一致;不要绕过校验展示 |
|
||||
| `评审目录不能是符号链接` | 传入真实目录路径,不使用符号链接 |
|
||||
| `无法启动本地评审服务` | 指定端口可能被占用;删除 `--port` 让系统选择,或换一个本机端口 |
|
||||
|
||||
任意一份 JSON 损坏都会阻止整个集合启动。错误只用于定位契约类别;不要把正文、修改片段或绝对目录补进日志。
|
||||
|
||||
## 验证记录
|
||||
|
||||
本流程于 2026-08-28 使用发布候选 wheel 和虚构的 emoji、CRLF、一次修改及一个零修改阶段实际验证:
|
||||
|
||||
- wheel 在无第三方 Python 依赖的全新环境中安装成功;
|
||||
- console script 与模块入口均可用,预期启动错误不产生 traceback;
|
||||
- 集合、文档和 Modifier API 返回正确,Python 码点范围正确转换为 UTF-16;
|
||||
- wheel 内首页和生产 JavaScript 可以通过回环服务读取;
|
||||
- 未读取或复制真实文档,也未写入调用项目目录。
|
||||
|
||||
这次验证覆盖安装、数据校验和服务路径,不替代真实浏览器中的最终视觉、长文滚动和交互人工确认。
|
||||
@@ -1,8 +1,9 @@
|
||||
# ReviewDocument 机器投影 schema 1.0
|
||||
|
||||
本文记录 `mdpolish.review.review_document_to_dict()` 和 `render_json_report()` 当前稳定的跨进程查询口径。公共 Python 类型和
|
||||
运行校验以 `src/mdpolish/review.py` 与测试为准;设计理由和批准边界见
|
||||
[`0012-review-document-machine-projection.md`](../design/0012-review-document-machine-projection.md)。
|
||||
本文记录 `mdpolish.review.review_document_to_dict()`、`render_json_report()` 和 `parse_json_report()` 当前稳定的跨进程查询
|
||||
口径。公共 Python 类型和运行校验以代码与测试为准;生产契约的设计理由见
|
||||
[`0012-review-document-machine-projection.md`](../design/0012-review-document-machine-projection.md),只读解析与本地页面边界见
|
||||
[`0014-generic-local-reviewer.md`](../design/0014-generic-local-reviewer.md)。
|
||||
|
||||
## 1. Schema 身份与入口
|
||||
|
||||
@@ -18,13 +19,15 @@
|
||||
schema 版本独立于 `mdpolish` 包版本和 modifier 版本。公共入口是:
|
||||
|
||||
```python
|
||||
from mdpolish.review import render_json_report, review_document_to_dict
|
||||
from mdpolish.review import parse_json_report, render_json_report, review_document_to_dict
|
||||
|
||||
payload = review_document_to_dict(review, detail="summary")
|
||||
json_text = render_json_report(review, detail="full")
|
||||
parsed = parse_json_report(json_text, expected_detail="full")
|
||||
```
|
||||
|
||||
两者只接受内存中的 `ReviewDocument`。JSON reporter 编码同 detail 的正式 dict,不定义另一套字段,也不读写文件。
|
||||
前两个生产入口只接受内存中的 `ReviewDocument`。JSON reporter 编码同 detail 的正式 dict,不定义另一套字段,也不读写文件。
|
||||
reader 只接受内存字符串,返回新的普通 JSON 基本值容器;它不接收路径,也不恢复 `ReviewDocument`。
|
||||
|
||||
## 2. 顶层字段
|
||||
|
||||
@@ -234,7 +237,40 @@ array,不根据二元组外形猜成 JSON object:
|
||||
|
||||
返回值是 Python `str`。调用方保存或发送时负责 UTF-8 编码、媒体类型、权限和保留周期。库不接收路径或文件对象。
|
||||
|
||||
## 7. 兼容策略
|
||||
## 7. 只读解析保证
|
||||
|
||||
```python
|
||||
from mdpolish.review import ReviewParseError, parse_json_report
|
||||
|
||||
payload = parse_json_report(json_text)
|
||||
full_payload = parse_json_report(json_text, expected_detail="full")
|
||||
```
|
||||
|
||||
`expected_detail` 可以省略,也可以显式指定 `summary`、`changes` 或 `full`。报告自己的 detail 不匹配时失败。返回结果是本次
|
||||
解析新建的 dict/list;修改它不会恢复或改变 producer 侧的 `ReviewDocument`。
|
||||
|
||||
所有 detail 都检查:
|
||||
|
||||
- JSON object key 唯一,字符串可以严格编码为 UTF-8,整数和浮点数满足第 4 节范围;
|
||||
- schema、detail、enum、稳定错误代码、数组顺序、引用位置和计数;
|
||||
- `summary` / `changes` 没有越过各自的正文暴露边界;
|
||||
- 状态、`current_kind`、错误阶段、完成阶段和残留候选相容。
|
||||
|
||||
`summary` 和 `changes` 没有完整阶段正文,reader 不会声称能验证不存在的文本。`full` 另外检查:
|
||||
|
||||
- input、current、所有 stage before/after 的码点长度和 SHA-256;
|
||||
- 第一个阶段从 input 开始,相邻阶段首尾相接,最后一个完成阶段等于 current;
|
||||
- Change span 的原文、位置、哈希、proposal/edit index、冲突和正式报告顺序;
|
||||
- 用核心共用的精确编辑原语重放每个 Change 批次,结果与 stage after 完全相同;
|
||||
- 零修改阶段的 before 和 after 完全相同;
|
||||
- residual edit 的原文、范围、顺序和冲突。
|
||||
|
||||
任一步失败都抛出 `ReviewParseError`。顶层错误消息只包含字段路径和契约类别,不包含正文、modifier 参数、reason 或诊断消息。
|
||||
reader 不调用 Modifier,不修复、截断或猜测损坏结果。
|
||||
|
||||
这是只读解析,不是反序列化。返回 dict 不能重新运行 Pipeline、恢复 Python 模型或获得原始 `TransformResult` 的权威身份。
|
||||
|
||||
## 8. 兼容策略
|
||||
|
||||
`schema_version` 使用 `MAJOR.MINOR`:
|
||||
|
||||
@@ -247,4 +283,5 @@ array,不根据二元组外形猜成 JSON object:
|
||||
同一 major 的消费者必须忽略未知 object 字段,但必须保持 array 顺序;不得把未知状态当成 `success`。消费者应拒绝自己不
|
||||
支持的 schema major。
|
||||
|
||||
schema `1.0` 是单向生产契约。当前没有官方反序列化器、JSON Schema 文件、历史迁移器或数据库 schema。
|
||||
schema `1.0` 仍是单向生产契约。官方 reader 接受同一 major 的已知字段语义,并忽略未知 object 字段的语义;未知状态、
|
||||
detail、enum 或坐标契约仍会失败。当前没有 `ReviewDocument` 反序列化器、JSON Schema 文件、历史迁移器或数据库 schema。
|
||||
|
||||
Reference in New Issue
Block a user