feat: 增加评审文档机器投影与 JSON 报告
This commit is contained in:
@@ -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。
|
||||
Reference in New Issue
Block a user