Files
mdpolish/research-wiki/reference/review-projection-schema-v1.md

288 lines
10 KiB
Markdown
Raw Permalink 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.
# ReviewDocument 机器投影 schema 1.0
本文记录 `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 身份与入口
每个投影都包含:
```json
{
"schema_name": "mdpolish.review",
"schema_version": "1.0"
}
```
schema 版本独立于 `mdpolish` 包版本和 modifier 版本。公共入口是:
```python
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,不定义另一套字段,也不读写文件。
reader 只接受内存字符串,返回新的普通 JSON 基本值容器;它不接收路径,也不恢复 `ReviewDocument`
## 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. 只读解析保证
```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`
- 删除、改名、改类型、改变位置/哈希/顺序语义、改变已有 enum 或错误代码含义,需要提升 major;
- 在相同 detail 中新增更高敏感度的正文承载字段,需要提升 major,或增加必须显式请求的新 detail;
- 不改变旧字段解释的非正文可选字段,可以提升 minor;
- 实现修正为重新符合已有契约,不改变 schema 版本;
- schema 版本不跟随 Python 包版本自动变化。
同一 major 的消费者必须忽略未知 object 字段,但必须保持 array 顺序;不得把未知状态当成 `success`。消费者应拒绝自己不
支持的 schema major。
schema `1.0` 仍是单向生产契约。官方 reader 接受同一 major 的已知字段语义,并忽略未知 object 字段的语义;未知状态、
detail、enum 或坐标契约仍会失败。当前没有 `ReviewDocument` 反序列化器、JSON Schema 文件、历史迁移器或数据库 schema。