跳转至

0.5 升级须知

0.5 是 0.x 阶段的一次不兼容升级:规范文档、Python SDK(dpe-hash / dpe-sdk / dpe-conformance)与 Rust SDK(dpe-hash / dpe-sdk)一起从 0.4.0 升到 0.5.0。本页以已发布的 0.4.0(文档 v0.4.0、py-v0.4.0、rs-v0.4.0)为基线,先按读者说明要做什么,再给出逐条实测的行为变化清单。细节见对应 Issue / PR。

总体结论

  • hash 契约仍是 dpe1,所有 hash 值没有任何变化。 本周期不改 hash 算法、JCS 规范化、URI 规范化或向量期望值。已落库数据、已存的 doc_hash / page_hash / content_hash 照常有效,不需要重算,也不会引发重推或重学。
  • 线上 HTTP 协议面没有变化:没有新增或改动端点、请求/响应字段、错误码或状态码映射。
  • 变化集中在四方面:SDK 的诊断层(异常子类、违例位置)、SDK 对非法输入的拒收时机(以前接受或崩溃的,现在按 DPE_VALIDATION 拒绝)、请求构造阶段的本地失败(新错误类型,不重发,outcome_unknown 为假),以及一致性套件新增的检查。
  • 依赖范围:下游按 >=0.5.0,<0.6 声明依赖(文档与两个 SDK 保持相同的 X.Y)。

安全

  • Python 0.4.x 会在异常消息中原样回显凭证(#207 修复):CredentialProvider 返回的值含控制字符(如 \n、\x00)、不是合法的请求头值时,Python dpe-sdk 0.4.x 抛出的 httpx.LocalProtocolError 消息中带有整条凭证(例如 Illegal header value b'Bearer …')。如果你的服务把这类异常写进了日志、错误上报或监控,应当认为相关凭证可能已经泄露,并按需轮换。
  • 0.5.0 在请求构造阶段就拒绝这类值,抛出 SDK 自己的错误,消息为固定文本,不回显凭证、URL 或主机;发送阶段的其他本地协议错误同样改为固定消息,原错误只保留在 __cause__ / source() 上。Rust SDK 不受影响(它的错误消息不包含头值)。
  • 相关但不在本版本范围:凭证中含非 ASCII 字符时两侧判定不一致,属于规范留白,见 #253(计划 0.6)。

按读者处理

只用 hash 核心的调用方

  • content_hash / page_hash / doc_hash / object_hash / document_hashes / children 的 hash 值与错误码(DPE_VALIDATION 等)不变。Python 侧在输入含孤立代理项、同时还有其他违例时,异常子类与 path 改为第 0 步(InvalidUnicodeError / ""),见行为变化第 2 节。Rust 无变化。
  • Python dpe-hash 新增公开 parse_ijson(与 Rust 对等),是可选能力;替换自写的严格解析器时,注意孤立代理项在解析阶段拒绝(见行为变化第 1 节)。
  • 只按错误码(code)分派的调用方通常无需改代码,把依赖上限放宽到 <0.6 即可;按异常子类、path 或诊断文本写分支的,按下一节核对。

按异常子类或 path 分支的 Python 调用方

  • 逐条核对「行为变化清单」第 1、2、4 节:第 0 步 I-JSON 违例统一为 InvalidUnicodeError / "";多处违例按最早步骤确定位置(core §2.8);超深输入不再抛 RecursionError,改为 DPE_VALIDATION。
  • 请求构造阶段的本地失败改为 dpe_sdk.errors.LocalRequestError;按 idna.IDNAError / httpx.LocalProtocolError 分支的调用方要改(见第 5 节)。
  • Python dpe-sdk 新增直接依赖 idna>=3,<4(原本已是 httpx 的传递依赖)。

Rust 调用方

  • hash 与规范面不变;#246 在 Rust 无可观察变化(文本入口在解析阶段即拒绝)。
  • 新增 ErrorKind::LocalRequest;按 ErrorKind::Transport / outcome_unknown 处理本地失败的调用方要核对(见第 5 节)。
  • 裸 412 后 head 网络失败直到重发耗尽时,outcome_unknown 由 false 改为 true(与 Python 对齐);按它决定是否重读的调用方会多一次重读。

dpe-run 运维方

  • 插件输出超深页 / 文档不再让运行器崩溃:改为该条目失败(DPE_VALIDATION),本轮 partial(见第 4 节)。
  • remote URL 的主机是非法 xn-- 标签,或 remote 凭证含控制字符,都属于配置错误,不会自行恢复。0.5 下运行器每轮都以 remote_unavailable(轮级,retryable: true,退出码 2)结束;调度器按轮重试也不会成功,必须修改实例配置或凭证。这一归类是临时的:0.6 将由 #256 改为专用的实例级错误码(不可重试)。
  • 与 0.4.0 相比:非法凭证本来就报 remote_unavailable,没有变化;非法 xn-- 主机在 0.4.0 中会让运行器以内部错误结束(runner_internal_error,退出码 70),0.5 改为 remote_unavailable。

服务端实现方

  • 规范 core §2.8 收紧(#246):违例分属不同步骤时,位置按校验顺序唯一确定;第 0 步 I-JSON 违例覆盖整个报文,位置为根 ""。线上 HTTP 协议面不变。
  • 新增 9 条组合向量(invalid_objects);消费向量的测试按新的 path 结论更新。
  • dpe-conformance==0.5.* 新增 9 条「不开会话的引用提交」检查(第 3 节)。合规服务端不受影响,不合规的实现会被判红。接入方式不变;需要的身份是 full(含 force 权限)和可选的 scoped,不需要钩子;CI 的执行数基线请按新的计数更新。

行为变化清单

1. Python:dpe_hash.parse_ijson 公开,孤立代理项在解析阶段拒绝(#244 / PR #248)

方法:同一脚本分别在 py-v0.4.0 与新版本上运行,逐条实测。

入口 输入特征 0.4.0 0.5.0
dpe_sdk.models.*.model_validate_json 未定义字段 + 孤立代理项 UndefinedFieldError,path="/bogus" InvalidUnicodeError,path=""(码同为 DPE_VALIDATION)
dpe_sdk.protocol 响应解析(fetch_capabilities / commit 等) 响应体中被忽略的字段含孤立代理项 接受 UnexpectedResponseError
dpe_sdk.protocol problem 解析 problem 体(如 detail)含孤立代理项 按 code 映射为对应错误 problem 视为无效:有 DPE-Error-Code 头时按头映射,没有时回落为 UnexpectedResponseError
dpe_hash.parse_ijson — 不存在 新增公开入口(与 Rust dpe_hash::parse_ijson 对等)
dpe_hash 的 hash 入口 孤立代理项 InvalidUnicodeError / "" 无变化

孤立代理项之外,0.4.0 的解析语义全部保留:重复键、NaN / Infinity 的处理、非 UTF-8、非法 JSON、数值越界不在解析阶段拒绝,都与 0.4.0 相同。删除了私有模块 dpe_sdk._ijson。

2. 规范与 Python:多处违例按最早的校验步骤确定位置(#246 / PR #249)

规范:core §2.8 收紧(部分推翻 #31 的裁决)。违例分属不同步骤时,违例位置按校验顺序唯一确定;第 0 步 I-JSON 违例覆盖整个报文,位置为根 ""。只有同一对象、同一步骤、同一错误码的多处违例,报告哪一处才由实现决定。

Python 对象级入口:输入含孤立代理项、同时还有后续步骤的违例时,统一改判为第 0 步。所有行的错误码都恒为 DPE_VALIDATION。

入口 输入特征(均含孤立代理项) 0.4.0 0.5.0
content_hash 未定义字段(代理项在值中或键名中) UndefinedFieldError / /bogus InvalidUnicodeError / ""
content_hash 缺 category ValidationError / "" InvalidUnicodeError / ""
content_hash category 非字符串 ValidationError / /category InvalidUnicodeError / ""
content_hash 内容字段类型错误 ValidationError / /text InvalidUnicodeError / ""
page_hash 未定义字段 UndefinedFieldError / /bogus InvalidUnicodeError / ""
doc_hash file_type 语法错误 FileTypeInvalidError / /file_type InvalidUnicodeError / ""
doc_hash 未定义字段 UndefinedFieldError / /bogus InvalidUnicodeError / ""
object_hash / children(三种对象) 未定义字段等 UndefinedFieldError 等 InvalidUnicodeError / ""
document_hashes 文档未定义字段 + 元素含代理项 UndefinedFieldError / /zz InvalidUnicodeError / ""

不变的情形:category 未知、坏子 hash 等,0.4.0 已经报第 0 步,行为不变。向量 invalid_objects 新增 9 条组合用例。Rust 无可观察变化(文本入口在解析阶段即拒绝)。

3. 一致性套件:新增「不开会话的引用提交」检查(#250 / PR #252)

dpe-conformance 新增 9 条检查。合规服务端不受影响;不合规的实现会被判红:

  • security 章:
  • 引用本文档已有的页 / 元素,不开会话、不重传 → 必须成功,delta 与读回互相印证;
  • 重复页按出现次数读回,delta 按元素多重集计;
  • 同一 commit 混合引用本文档对象与他文档对象 → 缺失清单恰为他文档对象(两种身份都执行)。
  • commit 章:依赖被他人移除后,带旧前置条件 → 412;改用 force → DPE_MISSING_CONTENT,且文档无变化。
  • large 章:在 max_payload_bytes 内构造页数最多的文档对象 → 不得返回 413。ingress 的 body 上限必须不低于声明值。

接入方式不变;需要的身份是 full(含 force 权限)和可选的 scoped,不需要钩子。CI 的执行数基线请按新的计数更新。

4. Python:超深输入不再抛 RecursionError(#251 / PR #255)

方法:同一脚本分别在 py-v0.4.0 与新版本上运行,Python 3.11 与 3.12 各跑一次。表中深度取各版本实际复现崩溃的最小深度。

入口 输入特征 0.4.0 0.5.0
5 个模型(ElementObject / PageObject / DocumentObject / Page / Document)的 model_validate 与关键字构造 metadata 500 层(3.11)/ 100000 层(3.11、3.12) RecursionError ValidationError,code=DPE_VALIDATION,path=""
*.model_validate_json 700 层(3.11)/ 5000 层(3.12) RecursionError 同上
run 层:插件输出超深页 元素 metadata 500 层 RecursionError 逃逸 DPE_VALIDATION 的 ItemFailure
run 层:插件输出超深文档(connector §6.6 第 4 步) doc_metadata 500 层 RecursionError 逃逸 本轮 partial,并产生 DPE_VALIDATION 的 ItemFailure
客户端响应解析 超深响应体 700 层(3.11)/ 5000 层(3.12) RecursionError 逃逸 UnexpectedResponseError
64 / 65 层边界 恰好 64 层 / 65 层 接受 / DPE_VALIDATION 无变化
  • 3.12 的递归上限更高,所以 0.4.0 在 3.12 上只在更深的输入时崩溃;3.11 约 500 层就会崩溃。
  • 校验顺序不变:多处违例时,元素 0 的 category 错误仍先于元素 1 的深度报出,页层校验仍先于文档层校验。
  • 新增:环状引用的输入(无法由 JSON 文本产生,只可能来自代码直接构造的对象)改报 DPE_VALIDATION;共享子对象照常接受。

Rust 无变化(实测无此缺陷,只补了回归测试)。

5. 两个 SDK:请求构造阶段的本地失败按「确定未发出」报 SDK 自己的错误(#207 / PR #257)

适用于两种情形:remote URL 的主机是非法的 xn-- 标签,以及凭证头值含控制字符(RFC 9110 field-value 不允许的 CTL,HTAB 除外)。两侧各新增一个对等的错误类型:Python dpe_sdk.errors.LocalRequestError(继承 ClientError),Rust ErrorKind::LocalRequest。错误消息为固定文本,原始库错误只经 __cause__ / source() 保留。

方法:同一脚本分别在 py-v0.4.0 / rs-v0.4.0 与新版本上实测。「provider」列是凭证 provider 被调用的次数。

场景 py-v0.4.0 py 0.5.0 rs-v0.4.0 rs 0.5.0
非法 xn-- 主机 · 读 idna.IDNAError;outcome_unknown=true;provider=1 LocalRequestError;false;provider=1 kind=Transport;true;provider=1 kind=LocalRequest;false;provider=1
非法 xn-- 主机 · 写(delete) IDNAError;true;provider=3(重发) LocalRequestError;false;provider=1 Transport;true;provider=3 LocalRequest;false;provider=1
凭证含换行 · 读 httpx.LocalProtocolError;true;消息回显凭证 LocalRequestError;false;固定消息 Transport;true;消息不含凭证 LocalRequest;false;固定消息
凭证含换行 · 写(delete) LocalProtocolError;true;provider=3;建立 3 次连接 LocalRequestError;false;provider=1;0 次连接 Transport;true;provider=3 LocalRequest;false;provider=1
首次响应丢失、续发时凭证非法 · 写 — LocalRequestError;true(先前尝试可能已生效);provider=2 — 同左
合法 A-label(xn--fsqu00a.example) 照常发出 照常发出 照常发出 照常发出
裸 412 后 head 网络失败直到重发耗尽 · 写(commit/delete) true true(无变化) kind=Transport;false kind=Transport;true
  • 最后一行是 Rust 的一个既有缺陷,这次顺带修复:412 响应没有错误码、又没能经 head 查明原因时,结果应当按「可能已生效」处理(与 Python 一致)。按 outcome_unknown 决定是否重读的 Rust 调用方,在这种情形下会多一次重读。
  • 发送阶段的其他本地协议错误(Python httpx.LocalProtocolError)判定不变,仍按「可能已发出」处理,只是消息改为固定文本。
  • 两侧库拒绝的主机集合本来就不同,0.5.0 没有对齐,对齐的只是报错口径。

运维须知(dpe-run):remote URL 的主机是非法 xn-- 标签,或 remote 凭证含控制字符,都属于配置错误,不会自行恢复。0.5 下运行器每轮都以 remote_unavailable(轮级,retryable: true,退出码 2)结束;调度器按轮重试也不会成功,必须修改实例配置或凭证。这一归类是临时的:0.6 将由 #256 改为专用的实例级错误码(不可重试)。与 0.4.0 相比:非法凭证本来就报 remote_unavailable,没有变化;非法 xn-- 主机在 0.4.0 中会让运行器以内部错误结束(runner_internal_error,退出码 70),0.5 改为 remote_unavailable。

hash 不变的证据

PR Issue 证据
#248 #244 vectors/、spec/、conformance/ 零改动;make check-vectors → vectors ok (35 files);Python 与 Rust 的向量测试全绿
#249 #246 未改 hash 算法、JCS 与 URI 规范化;vectors/ 中只有 invalid_objects.json(新增拒绝类用例、补 path)和 manifest.json(摘要随之刷新)变化,其余 33 个向量文件逐字节不变;新增代码只走错误路径
#252 #250 只改一致性套件(检查、桩测试、README);规范、向量、SDK 与参考服务端零改动
#255 #251 spec/、vectors/、conformance/ 零改动;make check-vectors → vectors ok (35 files);只改 Python SDK 的拒收路径,Rust 只加测试
#257 #207 spec/、vectors/ 零改动;只改两侧 SDK 请求构造阶段的错误归类与运行器归类

兼容变更(不影响下游判断)

  • 243 / PR #245:conformance/ci.md 写明服务端的验收标准是「退出码 0 + 执行数基线」,零 skip 只是本仓库参考服务端的自检口径。

  • 68 / PR #247:Git connector 的端到端测试支持外部 remote(仅测试代码)。

依赖声明建议

  • 下游按 >=0.5.0,<0.6 声明(版本规则:同一次版本号内只有兼容变更)。
  • dpe-conformance 0.5.* 校验规范文档 0.5.*。