Files
mdpolish/research-wiki/explanation/review-projection.md
T

116 lines
6.9 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.
# 通用内存评审视图怎样解释一次清洗结果
## 1. 为什么不能直接把所有修改画在最终全文上
`TransformResult.changes` 中的每条范围都属于对应修改器执行前的快照。前一个修改器插入或删除文本后,后一个修改器看到的
位置已经不同;后续修改器也可能再次改写前一阶段生成的内容。因此,把所有 `Change.span` 直接当成最终全文坐标,会产生
错误跳转和错误归属。
当前实现要求调用方同时提供原始 Markdown 和已有 `TransformResult`
```text
原始 Markdown + TransformResult
build_review_document()
│ 验证并重放,不运行 Modifier
ReviewDocument
├── render_markdown_report() ─────────────► 内存 Markdown 字符串
└── review_document_to_dict(detail=...)
├───────────────────────────► 项目自己的界面或转换层
└── render_json_report() ──► 内存 JSON 字符串
```
文件读取、保存位置、HTML 页面、权限和审核流程仍由调用项目决定。
## 2. 构建过程为什么可以失败关闭
`build_review_document()` 从调用方提供的原文开始,先验证输入和当前全文的 SHA-256,再按修改器位置重建批次。每个有实际
修改的阶段都会检查:
- `modifier_id`、版本和位置与记录的修改器元数据一致;
- `before_sha256``proposal_ref` 和每条编辑都指向当前阶段的修改前快照;
- proposal index、edit index 和报告顺序没有缺失、重复或被重排;
- 范围没有越界、重复或冲突,`before` 与原文精确一致;
- 应用后的全文和 `after_sha256` 一致。
核心执行与评审重放调用 `edits.py` 中同一个私有应用原语。排序键和字符串应用没有在评审模块复制一份;现有
`apply_modifier_batch()` 仍负责生成权威 `Change` 审计记录。
任一条件不成立都会抛出 `ReviewBuildError`。错误只说明契约类别和修改器位置,不包含输入、`before``after` 或上下文
片段。构建函数不会通过搜索或 diff 猜测缺失信息,也不会重新调用修改器。
## 3. `stages_complete` 表示什么
`ErrorStage.PREFLIGHT` 用来区分“修改器尚未开始执行”和“已经进入 transform 后失败”。阶段边界如下:
| 结果 | `ReviewStage` 范围 | `stages_complete` |
| --- | --- | --- |
| `success` | 全部修改器,包括零修改阶段 | `True` |
| `unstable` | 全部 transform 阶段;残留候选不应用 | `True` |
| 只有 final review 错误的 `failed` | 全部 transform 阶段 | `True` |
| preflight 失败 | 空 | `False` |
| 修改器位置 `p` 的 transform 失败 | 只包含 `0..p-1` | `False` |
已经返回 proposals 但元数据复核、批次验证或应用失败的修改器,不会被伪装成零修改阶段。它的修改前全文就是
`ReviewDocument.current_markdown`,失败原因保存在 `errors`;失败位置之后的修改器没有运行,也没有阶段。
`stages_complete=True` 只证明 transform 阶段完整,不表示 final review 成功,更不把 `failed``unstable` 提升成成功
输出。
## 4. 位置口径
`ReviewLocation` 指向 `Change.span.start`,并相对于所属 `ReviewStage.before_markdown` 计算。行和列都是 1-based,列宽使用
Python Unicode 码点:
- BOM 和组合字符各占一个码点;
- 补充平面字符占一个码点,不按 UTF-16 的两个 code unit 计算;
- LF、CRLF 和 CR 都是物理换行,CRLF 是一个换行边界但占两个原始码点;
- 计算前不做 Unicode 或换行规范化。
权威范围仍然是 Python 半开区间 `Change.span``ReviewLocation` 只供人阅读,不用于重新应用修改,也不是 JavaScript、LSP
或终端显示列坐标。
## 5. Markdown reporter 的边界
`render_markdown_report()` 是纯函数,只返回 Markdown 源码字符串。报告包含状态、哈希、修改器顺序、完整输入、完整当前
全文、带物理换行标记的统一 diff,以及按 `ReviewStage` 排列的实际修改。`failed``unstable` 的 diff 文件标签带有
`.partial.md`,避免把部分结果误认成正式输出。
完整 residual proposal 仍保留在 `ReviewDocument` 中。reporter 默认最多列出前 20 条,只显示修改器身份、位置、原因、
span 和修改前后长度,不重复输出 `expected_text``replacement` 或正文摘要。调用方可以把限额设为非负整数,`0` 表示
只显示总数。
报告有意包含完整文档、实际修改的 `before` / `after` 和 diff,可能还原敏感内容。库不会自动打印、保存、上传或缓存
报告;持久化后的路径、访问权限、脱敏和保留周期属于调用项目。每个阶段保存完整前后快照,当前实现优先保证可复核性,
没有声称适合无限长度文档或无限修改器链。
## 6. 机器投影为什么不是 `dataclasses.asdict()`
`review_document_to_dict()``ReviewDocument` 的单向公共视图,不是内部 dataclass 的机械展开。它显式转换 enum、tuple、
modifier 参数、位置和哈希,并在根对象写入 `schema_name=mdpolish.review` 与独立的 `schema_version=1.0`。这样内部 Python
结构可以在不改变 schema 的前提下重构,调用项目也不需要猜测 enum、数组和阶段坐标。
投影有三个内容等级:
| detail | 增加的内容 |
| --- | --- |
| `summary` | 状态、哈希、计数、modifier 身份、阶段摘要和稳定错误代码;默认不含正文承载字段 |
| `changes` | modifier 参数和适用说明、实际修改片段、错误诊断、残留候选片段 |
| `full` | 完整输入、当前全文和每个完整阶段的前后全文 |
高等级只增加字段,不改变低等级已有字段的值和顺序。被 detail 隐藏的字段直接不存在,不使用 `null` 或空字符串假装隐藏,
因此空文档在 `full` 中仍能明确表示为 `markdown: ""`
`summary` 不含正文,但仍包含 modifier identity、哈希和计数,只能称为“无正文投影”,不能称为脱敏或公开安全日志。
`changes``full` 都可能还原敏感内容;库不会自动打印、保存或发送任何投影。
`render_json_report()` 只把同 detail 的正式 dict 投影用标准 JSON 编码,不维护第二套字段。它返回内存字符串,使用 Unicode、
拒绝 NaN / Infinity,不写文件或添加 BOM。Markdown reporter 继续直接读取 `ReviewDocument`:它面向人类排版并包含 diff、
动态围栏和 residual 展示限额,不依赖机器 schema。
机器投影只生产,不提供 JSON 到 `ReviewDocument` 的反序列化,也不能用于重新应用修改。完整字段、坐标、错误代码和兼容规则
见 [`review-projection-schema-v1.md`](../reference/review-projection-schema-v1.md)。