feat: 增加评审文档机器投影与 JSON 报告

This commit is contained in:
2026-08-28 14:23:30 +08:00
parent dd012f4905
commit 843952b194
7 changed files with 2096 additions and 16 deletions
@@ -0,0 +1,250 @@
# 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)。
## 1. Schema 身份与入口
每个投影都包含:
```json
{
"schema_name": "mdpolish.review",
"schema_version": "1.0"
}
```
schema 版本独立于 `mdpolish` 包版本和 modifier 版本。公共入口是:
```python
from mdpolish.review import render_json_report, review_document_to_dict
payload = review_document_to_dict(review, detail="summary")
json_text = render_json_report(review, detail="full")
```
两者只接受内存中的 `ReviewDocument`。JSON reporter 编码同 detail 的正式 dict,不定义另一套字段,也不读写文件。
## 2. 顶层字段
| 字段 | JSON 类型 | 口径 |
| --- | --- | --- |
| `schema_name` | string | 固定为 `mdpolish.review` |
| `schema_version` | string | 当前固定为 `1.0` |
| `detail` | string | `summary``changes``full` |
| `status` | string | `success``failed``unstable` |
| `current_kind` | string | `success_output``partial_output` |
| `stages_complete` | boolean | 是否能证明全部 modifier transform 阶段完成 |
| `hash_contract` | object | 所有 Markdown 哈希的算法、编码和规范化口径 |
| `coordinate_contract` | object | span、行列和物理换行口径 |
| `input` | object | 输入文本摘要;`full` 增加正文 |
| `current` | object | 当前文本摘要;`full` 增加正文 |
| `counts` | object | modifier、完成阶段、实际修改、错误和残留候选数量 |
| `modifiers` | array | 全部 modifier 的权威顺序 |
| `stages` | array | 已完成 transform 阶段的权威顺序 |
| `errors` | array | Pipeline 记录的错误顺序 |
| `residual_proposals` | array | 只在 `changes` / `full` 出现;候选未应用 |
JSON object 成员顺序不构成语义。所有 array 顺序构成语义,消费者不得重新按 ID、哈希或文本排序后再解释位置。
## 3. 内容暴露等级
| 内容 | `summary` | `changes` | `full` |
| --- | ---: | ---: | ---: |
| 状态、哈希、位置口径和计数 | 是 | 是 | 是 |
| modifier id、版本和位置 | 是 | 是 | 是 |
| modifier 参数和 applicability | 否 | 是 | 是 |
| 阶段前后哈希、长度和修改数 | 是 | 是 | 是 |
| 实际修改的 reason、位置、`before` / `after` | 否 | 是 | 是 |
| 稳定错误代码和错误位置 | 是 | 是 | 是 |
| Python 诊断类型和错误消息 | 否 | 是 | 是 |
| 残留候选的 reason、`expected_text` / `replacement` | 否 | 是 | 是 |
| 输入、当前和阶段完整 Markdown | 否 | 否 | 是 |
默认是 `summary`。被隐藏的内容键不存在;`null` 和空字符串都不是“已隐藏”的替代值。`summary` 不含直接正文,但仍含
modifier identity、哈希和计数,不自动等于匿名、脱敏或适合公开传播。
## 4. 固定口径
### 4.1 哈希
```json
"hash_contract": {
"algorithm": "sha256",
"encoding": "utf-8",
"normalization": "none"
}
```
哈希是精确 Markdown 的 `sha256(markdown.encode("utf-8")).hexdigest()`。不规范化 Unicode、BOM、空白、末尾换行或
CR/LF/CRLF。`Change.before_sha256` / `after_sha256` 是完整阶段快照哈希,不是修改片段哈希。
### 4.2 坐标
```json
"coordinate_contract": {
"offset_unit": "unicode_code_point",
"span_index_base": 0,
"span_end": "exclusive",
"location_index_base": 1,
"physical_line_endings": ["lf", "crlf", "cr"]
}
```
- `span` 相对于所属 `ReviewStage.before_markdown`,使用 0-based Unicode code point 半开范围;
- `location.line` / `column` 指向 `span.start`,使用同一阶段修改前文本中的 1-based Unicode code point 位置;
- CRLF 形成一个物理换行,但在 offset 中占两个 code point
- 不提供最终全文坐标、UTF-16、byte offset 或终端显示宽度。
所有整数都在 `0..2**53-1`;参数中的有符号整数在 `-(2**53-1)..2**53-1`。所有 float 必须有限。
## 5. 嵌套对象
### 5.1 文本摘要
`input``current` 以及 stage 的 `before` / `after` 都至少包含:
```json
{
"sha256": "64 位小写十六进制",
"code_point_length": 123
}
```
`full` 增加 `markdown``current.markdown` 是否为正式输出必须看根字段 `current_kind``partial_output` 不能当作成功结果。
### 5.2 Counts
```json
"counts": {
"modifier_count": 3,
"completed_stage_count": 2,
"change_count": 5,
"error_count": 1,
"residual_proposal_count": 0
}
```
`change_count` 只统计已完成 stage 中的实际 `ReviewChange`,不含 residual proposal edit。零修改的完整 stage 仍计入
`completed_stage_count`
### 5.3 Modifier
所有 detail
```json
{
"position": 0,
"modifier_id": "example.normalize",
"version": "1.0.0"
}
```
`changes` / `full` 增加 `parameters``applicability``parameters` 是有序 array-of-pairs;所有内部 tuple 继续投影成
array,不根据二元组外形猜成 JSON object
```json
"parameters": [
["pattern", " {2,}"],
["replacement", " "]
]
```
### 5.4 Stage
所有 detail
```json
{
"modifier_position": 0,
"before": {"sha256": "...", "code_point_length": 20},
"after": {"sha256": "...", "code_point_length": 18},
"change_count": 1
}
```
`changes` / `full` 增加 `changes` array`full` 还给两个文本摘要增加 `markdown``modifier_position` 引用根
`modifiers[position]`,不复制 modifier 元数据。
### 5.5 实际 Change
只在 `changes` / `full` 出现:
```json
{
"proposal_index": 0,
"edit_index": 0,
"reason": "修改原因",
"location": {"line": 3, "column": 7},
"span": {"start": 24, "end": 31},
"before": "原片段",
"after": "新片段",
"before_sha256": "修改前完整阶段哈希",
"after_sha256": "修改后完整阶段哈希"
}
```
### 5.6 Error
所有 detail 都含 `code``stage`、modifier 位置与身份。`changes` / `full` 再增加诊断用的 `diagnostic_type`
`message`
| `stage` | 稳定 `code` |
| --- | --- |
| `preflight` | `run.preflight_failed` |
| `transform` | `run.transform_failed` |
| `final_review` | `run.final_review_failed` |
代码只稳定表达失败阶段。`diagnostic_type` 是 Python 异常类名,`message` 是人类消息,不能作为稳定程序分支条件。当前
`RunError` 没有保存更细的稳定原因;不能从类名或消息猜造更细代码。
### 5.7 Residual proposal
只在 `changes` / `full` 出现:
```json
{
"modifier_position": 0,
"proposal_index": 0,
"snapshot_sha256": "当前完整快照哈希",
"reason": "候选原因",
"edits": [
{
"edit_index": 0,
"span": {"start": 10, "end": 15},
"expected_text": "原片段",
"replacement": "候选片段"
}
]
}
```
它是 final review 证据,没有应用,不进入 stage 或实际 change count。
## 6. JSON 编码
`render_json_report()` 固定使用标准库 JSON 的以下语义:
- `ensure_ascii=False`
- `allow_nan=False`
- `indent=2`
- 无 UTF-8 BOM
- 返回字符串末尾不额外添加换行。
返回值是 Python `str`。调用方保存或发送时负责 UTF-8 编码、媒体类型、权限和保留周期。库不接收路径或文件对象。
## 7. 兼容策略
`schema_version` 使用 `MAJOR.MINOR`
- 删除、改名、改类型、改变位置/哈希/顺序语义、改变已有 enum 或错误代码含义,需要提升 major;
- 在相同 detail 中新增更高敏感度的正文承载字段,需要提升 major,或增加必须显式请求的新 detail;
- 不改变旧字段解释的非正文可选字段,可以提升 minor;
- 实现修正为重新符合已有契约,不改变 schema 版本;
- schema 版本不跟随 Python 包版本自动变化。
同一 major 的消费者必须忽略未知 object 字段,但必须保持 array 顺序;不得把未知状态当成 `success`。消费者应拒绝自己不
支持的 schema major。
schema `1.0` 是单向生产契约。当前没有官方反序列化器、JSON Schema 文件、历史迁移器或数据库 schema。