10 KiB
ReviewDocument 机器投影 schema 1.0
本文记录 mdpolish.review.review_document_to_dict()、render_json_report() 和 parse_json_report() 当前稳定的跨进程查询
口径。公共 Python 类型和运行校验以代码与测试为准;生产契约的设计理由见
0012-review-document-machine-projection.md,只读解析与本地页面边界见
0014-generic-local-reviewer.md。
1. Schema 身份与入口
每个投影都包含:
{
"schema_name": "mdpolish.review",
"schema_version": "1.0"
}
schema 版本独立于 mdpolish 包版本和 modifier 版本。公共入口是:
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 哈希
"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 坐标
"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 都至少包含:
{
"sha256": "64 位小写十六进制",
"code_point_length": 123
}
full 增加 markdown。current.markdown 是否为正式输出必须看根字段 current_kind;partial_output 不能当作成功结果。
5.2 Counts
"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:
{
"position": 0,
"modifier_id": "example.normalize",
"version": "1.0.0"
}
changes / full 增加 parameters 和 applicability。parameters 是有序 array-of-pairs;所有内部 tuple 继续投影成
array,不根据二元组外形猜成 JSON object:
"parameters": [
["pattern", " {2,}"],
["replacement", " "]
]
5.4 Stage
所有 detail:
{
"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 出现:
{
"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 出现:
{
"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. 只读解析保证
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。