交换格式与诊断契约
本页定义首版导出、机器结果、录制和诊断的逻辑 schema。快照使用包含独立 ASPA 地址族范围的版本 2,并接受明确兼容的版本 1 导入;其他接口的交付状态见各自 API 参考。实现须用本页约定的必填、空值和兼容规则测试,不得仅检查 JSON 能解析。
公共序列化
UTF-8 JSON 不含 NaN、Infinity、ANSI 或 Python repr。ASN 为整数,网络为规范化 CIDR,时间为 UTC RFC 3339 字符串;二进制使用带 padding 的标准 base64,SKI 导出为小写 hex。消费者不能依赖 JSON 对象键顺序。
网络文本在支持的 Python 版本间固定。IPv4 使用点分十进制;普通 IPv6 使用小写十六进制和最长、最左连续零组压缩;IPv4 映射 IPv6 使用 ::ffff:a.b.c.d/length,例如 ::ffff:192.0.2.0/120 和 ::ffff:0.0.0.0/96,保留 IPv6 地址族、128 位地址和原前缀长度。该文本选择参考 RFC 5952 第 5、7 节,属于本项目交换和摘要契约;不能直接用随 CPython 补丁版本变化的 str(network) 定义摘要。接口 zone 不属于授权前缀,输入明确拒绝,不能在序列化时静默丢弃。
机器响应统一包含 schema_version、kind、snapshot_id(无快照时 null)、evaluated_at、mode、data、error。正常响应 error=null;执行失败 data=null 且 error 为 ErrorInfo。批次整体成功完成时 data.items 内可以有逐条 error,不能据此丢弃成功条目。
OriginResult 的 data 至少含 prefix、asn、status、reason_codes、evidence;AspaResult 另含 path、context、algorithm_version;BmpAnalysis 使用独立 kind=bmp_analysis 和 assessment。input_id 是调用方关联字段,不能替代库的快照或事件标识。
ErrorInfo 必填 code、message、retryable,可选 source_id、retry_after、details;details 只含已定义的安全 JSON 值。消息可改变,code 和字段语义按兼容策略维护。未知可选字段可忽略,未知必需版本或枚举语义不得猜测为有效。
快照导出
export_snapshot 输出一个完整 JSON 对象:
| 字段 | 约束 |
|---|---|
| format | 固定 rpkiparrot-snapshot |
| schema_version | 整数 2;历史完整快照的整数 1 只用于兼容导入 |
| exported_at | 导出操作时间,只供诊断 |
| payload | 下表定义的完整原始状态 |
| sha256 | payload 规范序列化的十六进制摘要 |
payload 的必要字段为:
| 字段 | 内容 |
|---|---|
| config_revision | 快照发布时固定的配置版本;属于 payload 摘要的一部分 |
| snapshot_id、upstream_id | 发布和上游身份,upstream_id 可为 null |
| published_at、mode、reference_time | 原发布时间;在线 reference_time 为 null,离线必须给出 |
| protocol_versions、policy_id | 采用的固定版本与策略身份 |
| sources | 来源完整状态数组;每项包括 ID、非敏感身份、格式/协议、能力、完整标志、原始时间、session/serial 及各类原始载荷 |
| groups、active_groups | 配置的安全组身份/优先级与各载荷当前选择 |
| aspa_capabilities、aspa_active_groups、aspa_usable_until | 必须包含字符串键 "1" 和 "2",分别保存两 AFI 的能力、活动组(可为 null)和原始最早重算期限(UTC 字符串或 null) |
| completeness | 必须为 full;不把查询子集冒充完整快照 |
| counts | 按来源和载荷的原始条数及支持条数,必须可重算 |
载荷记录按数据契约表达,逐条支持期限不能省略。未知期限以 null 明确表示;只有加载策略可证明有效时才允许在线使用,导出不新增证明。空数组须与能力和完整标志一致。
来源项的必填字段为 id、identity、kind、format_id、protocol_version、connection、reason_code、last_sync_at、generated_at、expires_at、session_id、serial、last_error、invalidated、evaluated_at、capabilities、present、complete、diagnostics、metadata、vrps、aspas、router_keys;可选值使用 null,不能省略必填字段。evaluated_at 是该快照构建时该来源已经达到的有效评价时间下界,包含单调时钟推进的结果,不在每次导出时改成当前时间。capabilities 按这个固定时刻与原始载荷核对。相同快照的 payload 与摘要保持稳定;exported_at 在 payload 之外,可以随导出操作改变。
版本 2 来源还必须包含 aspa_families(无重复的整数 1/2 数组,可为空)和 aspa_capabilities(恰好字符串键 "1"、"2")。ASPA 行必须带 afi,null 表示双族共同授权,整数 1/2 表示单族;不接受 bool、IP 版本号 4/6 或数字字符串。记录范围必须包含于来源覆盖;聚合 ASPA 能力须与两族按数据契约推导的值一致。读取先核对传输值,再构造模型,不能用模型自动推导修复矛盾。
RTR v2 协商降为 v1 时,可保存已撤除 ASPA 的原始保留视图:kind 为 rtr、complete 为 true,format_id 和 protocol_version 仍为 rtr-v2 与 2,原 session/serial、generated_at、last_sync_at、expires_at 必须保留。present 恰为 VRP 与 Router Key,aspas 与 aspa_families 为空,两族及聚合 ASPA 能力为 unsupported;metadata.rtr.incremental_usable 必须为布尔 false。该标记不是新的 EOD,不续期,也不授权 Serial Query;恢复只能使用原期限内的 VRP/Router Key 并以 Reset Query 重新同步。标记若出现必须是布尔值,false 与保留 ASPA 或其他不符结构的组合拒绝读取。完整新 EOD 不再携带 false,才恢复完整事务资格。普通 JSON 生产者的同名 metadata 不获得 RTR 协议语义。
groups 每项严格包含 id、整数 priority、source_ids 数组;组 ID 和优先级分别唯一,每个来源必须且只能归属一个组。导出按 priority、id 排序,各组 source_ids 按 ID 排序。允许空组与空 groups;空 Client 配置和显式空组不同,MemoryStore 则始终导出优先级为 0 的 memory 组。active_groups 必须包含 vrp、aspa、router_key,值为对应已就绪组 ID,无任何已就绪组时为 null。安全组是独立信任范围,完整导出保留未选中组的数据,不能预先跨组求并集。此版本不序列化运行任务、重试状态或 failback 计时器。
ASPA 按 AFI 独立选择组;active_groups.aspa 只在两族选中同一个非空组时保存该 ID,选择不同组时为 null,而非声明两族不可用。每族 aspa_usable_until 是所有组中该族原始来源期限、未来激活或记录失效的最早待重算边界,包括未选组;聚合期限取两族非空边界的最小值。导入按固定来源 evaluated_at 独立重算能力与边界,拒绝不一致的声明。
sources 包括已配置但尚未完成首次同步的来源。这类占位项 complete=false,format_id、last_sync_at、generated_at、expires_at、protocol_version、session_id、serial 必须为 null,载荷和 diagnostics 为空数组、metadata 为空对象。present 中的能力为 not_loaded,已明确 invalidated 时为 unavailable,其他能力为 unsupported;不得声明 ready_empty。来源身份、连接状态、错误与 evaluated_at 仍保留。顶层 completeness=full 表示全部来源状态齐全,不意味着全部来源完成同步;除此精确定义的占位项外,不完整候选数据不能导入。
VRP 行包含 prefix、max_length、asn;ASPA 行包含 customer、afi、按 ASN 升序的唯一 providers;Router Key 行包含 asn、小写十六进制 ski、标准带 padding 的 base64 spki。每行另必含 valid_from、expires_at、upstream_label。ASPA 支持按 customer、afi(null 排在 1/2 前)、providers、时间与标签排序。counts 按来源 ID 和 vrp/aspa/router_key 分组,records 是不同完整载荷值数量,ASPA 的 afi 也是载荷身份的一部分;supports 是原始支持条数;ASPA 另有 provider_edges,逐条计算 provider 数,双族共同记录按原始单条计数,不为了内部双族索引翻倍。重复载荷的独立支持不能丢失。生产者 metadata/CCR 原样保留为未验证信息。
VRP 支持依次按 IP 版本、网络地址整数、前缀长度、max_length、ASN,再按 valid_from、expires_at、upstream_label 排序。时间按规范化后的 UTC 字符串比较,缺失值和空标签的排序键为空字符串;相同键保持原始支持顺序,字段仍分别保留 null 和空字符串。排序复用已验证的网络对象,不为导出重新解析 CIDR。
确定性 payload 序列化规则为:所有时间规范化为 UTC、二进制和 CIDR 按上述形式、载荷和支持按公共确定顺序排序,再用等价于 json.dumps(sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False) 的 UTF-8 字节计算 SHA-256。不包含 exported_at、commit 时间、摘要字段自身或内部索引。
内存/SQL 状态摘要复用同一逻辑映射;实现提供固定输入和固定摘要的独立向量。摘要只检测完整性,不认证数据来源。
导出复用计算摘要时生成的有界 payload 字节,避免为外层文档再次编码完整载荷。max_export_bytes 约束包含外层字段的完整 UTF-8 结果;payload 本身能放入预算不代表整个文档也能放入,超过预算仍抛 ResourceLimitError,不返回部分文档。
导出的 VRP、ASPA 和 Router Key 数组可按保守的编码大小上界分块交给标准库编码,每块上界不超过 64 KiB 及剩余字节预算。只有精确的内置类型、已知记录字段和满足上界的记录进入此路径;较大记录或自定义字符串类型回到逐片编码,不丢弃或拒绝原本合法的数据。metadata、错误详情等自由结构仍使用通用编码,导入的摘要校验也保持独立。分块不改变完整字节、排序、支持条数、摘要或期限;ResourceLimitError 的 observed 是发现超限时的诊断值,可能随编码片段边界变化,但必须大于 maximum。64 KiB 是编码上界,不是 Python Unicode 临时对象或整个导出的 RSS 承诺;完整 payload 映射和输出缓冲仍计入实际内存测量。
导入
使用独立 load_snapshot(data: bytes | str, *, context: EvaluationContext | None = None, limits: Limits | None = None) -> Snapshot,位于 readers。它与单来源生产者 parse_json 分开:不能将含多个来源的导出包装成一个新来源并重新赋予 max_age。导入大小受 max_export_bytes 约束,不能因普通生产者 JSON 的默认限制更小而拒绝本库合法导出。
load_snapshot 检查 schema、必填字段、摘要、计数、来源能力与原始期限,重建索引并使用新导入身份发布 Snapshot,upstream_id 保留导出身份;新导入身份的 config_revision 从 1 开始,原导出版本只作输入来源诊断,不冒充可继续修改的运行中配置。默认在线且不接受离线导出直接提升为在线可信状态;宿主通过明确的可信来源加载流程建立信任后才可在线使用。
已知的旧映射 IPv6 十六进制拼写(例如 ::ffff:c000:200/120、::ffff:0:0/96)允许显式导入。先用文件中的原始 payload 文本验证 SHA-256,再解析为相同 IPv6 网络;新导出使用上述混合表示。大写、展开零组、主机位或接口 zone 不因这条兼容路径获准。原始记录期限和来源评价时间保持不变;此规则不允许数据库以另一种摘要重试或自动迁移旧状态。
导入先按各来源固定 evaluated_at 验证原能力与活动组声明,再按请求的评价上下文逐组、逐 AFI 重建。每类载荷或 ASPA 地址族优先保留原选中且仍就绪的组;该组已不可用时,选择优先级数值最小的就绪组,无可用组则保留不可用状态。重建不跨组混合载荷,也不假定已等待运行中的 failback_delay。未选中组的未来激活和到期边界同样限制返回快照的 usable_until。未加载占位项保持未加载或时钟失效,不能因重新读取而成为可信空数据。
版本 1 先按原始 payload、旧版本表与旧固定摘要完成验证,再明确解释为所有 ASPA 记录均 afi=None、present 含 ASPA 时覆盖两族、旧能力和活动组同时适用于两族;不得在 schema 1 中夹带版本 2 的 AFI 扩展字段。新导出始终使用 schema 2,不保持旧摘要,但旧固定测试向量的原始摘要保持不变。版本 2 的 protocol_versions 在旧表上追加 rtr_v2_legacy=draft-ietf-sidrops-8210bis-10、aspa_profile_legacy=draft-ietf-sidrops-aspa-profile-07、rtr_v2_legacy13=draft-ietf-sidrops-8210bis-13、aspa_profile_legacy13=draft-ietf-sidrops-aspa-profile-18、aspa_afi_semantics=rpkiparrot-afi-1。最后一项表示本库按 AFI 隔离输入与上下文后分别执行固定 verification-28 的适配语义,不声称 verification-28 自身定义了旧草案的 AFI 字段。
首版具体规则是:online 导出可在显式选择该文件为可信输入后保持原期限加载;offline 导出只允许指定 offline context。文件读取动作本身不是证书验证。离线参考时间不能早于数据的已知生成时间;这只评价所给数据,不声称重建该时刻全网真实状态。
显式调用 load_snapshot 并选择输入字节,或 CLI 显式选择快照文件,就是宿主的输入信任选择;不新增隐式全局信任或 trusted 开关。摘要仍只证明完整性。独立 Snapshot 到边界后可重载原导出以重新计算有效视图,或重新通过 MemoryStore 接纳原始完整来源及原期限;重载需解析和重建索引,不向旧对象回写状态或把 max_age 改从当前时刻计算。
在线导入保留每源 evaluated_at,下界不会因新进程的单调时钟从零开始而回退;新的时间锚取当前墙钟与该下界的较大值。若当前墙钟比已记录的评价时间或导出观察时间早超过 5 秒,来源按时钟不可信撤销,不能借恢复重新获得寿命。离线显式再评价按 reference_time、原始生成时间和记录区间计算,不把旧能力诊断当成当前能力;已经明确 invalidated 的来源仍不可用。单调计数本身不跨进程序列化,数据库提交观察时间另由持久化后端保存,不进入固定 payload。
不开放 pickle 恢复,不将录制文件视为快照;除上述明确的 schema 1 和已知映射前缀拼写兼容读取外,不自动迁移未知格式。过滤后的查询导出使用 kind=query_result,load_snapshot 必须拒绝。
状态与指标
StatusReport 包含 sampled_at、lifecycle、当前 snapshot_id/config_revision、persisted_snapshot_id/persisted_config_revision、recovered_from、各载荷 availability/usable_until、活动组、各来源 SourceInfo、最近持久化错误、configured_limits、各功能规范版本。另含 reconfiguring、retiring_source_ids、cleanup_overdue、cleanup_elapsed 和清理中的资源类别;不暴露注入对象或凭据。观察时间不替代同步时间;health 存活和 required 能力就绪分别表示。
MetricsReport 包含 sampled_at、publisher identity、counters、gauges、durations,返回不可变采样。每个管理器或显式收集器使用自己的 store_id/epoch;新实例从零开始,不能跨身份直接相减。counters 是累计整数,gauges 是当前值,durations 的单位为秒;除下表明确的最近一次测量外,耗时按已完成操作累计,并发操作各自贡献其经过时间。
| 类别 | 首版指标 |
|---|---|
| 同步 | full_sync_total、incremental_sync_total、sync_failure_total、unchanged_sync_total;sync_duration_seconds、full_sync_duration_seconds、incremental_sync_duration_seconds |
| 连接 | connection_total、connection_failure_total、reconnect_total;connection_duration_seconds |
| 数据 | records/source/<编码后的 ID>/kind/ |
| 持久化 | commit_total、commit_failure_total、commit_duration_seconds、persisted_generation |
| 事件 | subscriber_count、queue_depth、resync_required_total、coalesced_updates_total |
| 验证 | validation_total、validation_error_total、validation_item_total、validation_item_error_total、validation_interrupted_total,以及五种固定操作的调用/错误数;可选 validation_duration_seconds 与各操作累计耗时 |
Client 在接纳来源提交且至少一类载荷仍有效时计全量或增量;相同 JSON 文件仍完整解析并计有效的全量接纳,但不延长原始期限,已到期文件不会因成功解析而增加成功同步计数。只有 HTTP 304 计 unchanged,失败计 sync_failure。RTR 连接失败单独计 connection_failure,尚未开始查询的连接失败不虚构一次同步;第一次之后已完成的连接尝试计 reconnect,无论成功或失败。取消尚未完成的动作不计数;已经原子接纳的成功不会因随后取消而撤回。删除或替换来源后的迟到观察被隔离。sync_duration_seconds 包含已完成的成功、失败和未改变输入;JSON 从轮询准入前到接纳或 304 报告,RTR 从查询到接纳,后续资源关闭不计同步耗时。
source ID 使用 UTF-8 百分号编码,不发生分隔符碰撞。来源 gauge 计当前配置保留的原始记录,包括已到期记录;ASPA 在此按记录数计,资源准入仍按支持边计。移除来源即移除其 gauge,不累计历史标签。generation 还必须携带身份;默认标签只使用受配置数量约束的 source_id、kind 和固定操作,不把 prefix、ASN、任意错误文本变为标签。active_builds、staging_wire_bytes 和 event_history_bytes 反映当前占用。
持久化完成成功或失败时分别计 commit_total 或 commit_failure_total,commit_duration_seconds 累计这两类操作耗时;persistence_commit_total、persistence_failure_total 是兼容同义名,persistence_commit_seconds 保留最近一次尝试耗时。SharedSnapshotClient 不运行同步或写入,这些计数和耗时为零;database_load_total 计接纳的新持久化 head,coalesced_updates_total 只计同一写入者 epoch 内实际接纳的 generation 跳跃。事件 resync_required_total 为累计次数,subscriber_count、queue_depth 为当前占用。
纯验证函数不修改全局指标。显式 rpkiparrot.metrics.MetricsCollector(timing=False) 提供与 validation 模块相同的五种验证方法及 get_metrics,可跨受管理线程共享。一个批量调用计一次 validation_total,返回的已知条目另计 validation_item_total;执行异常、逐项错误或不完整批次计调用错误,正常 invalid/notfound/unknown 不计错误。批量整体抛异常不虚构条目结果。BaseException 中断只计 validation_interrupted_total。各操作固定名称为 origin、origins、aspa、aspa_batch、bmp,对应 validation_<操作>total、validation<操作>error_total。开启 timing 才采样计时并返回总计及 validation<操作>_duration_seconds;默认 durations 为空。收集器不保留输入、结果或快照,不需要 close;可执行指标示例演示调用数与条目数的区别。
HTTP 服务为领域验证显式使用一个收集器,/v1/metrics 在管理器的 publisher identity 下合并本 lifespan 的验证指标。请求准入和结构解析失败不进入领域收集器;直接调用 Client 之外的纯函数不会出现在服务指标中。
日志使用 rpkiparrot 命名空间,附 source_id、snapshot_id、error_code 等结构化上下文,不配置根处理器。只有宿主显式开启 debug 才输出额外协议诊断;凭据、私钥、HTTP Authorization 和完整机密配置始终不进入日志或事件。
协议录制
Recorder 通过显式传输装饰器记录应用层字节,不抓取 TLS 密钥和握手秘密。每个实例对应一个 RTR 逻辑会话及其串行重连,不交错记录两个独立会话。UTF-8 JSON Lines schema 1 的字段为:
| kind | 必填字段与含义 |
|---|---|
| header | format=rpkiparrot-rtr-recording、schema_version=1、started_at(UTC)、initial_state=empty、integrity=sha256-jsonl-prefix、session、limits、protocol_versions |
| connection | connection_id(从 1 连续增长)、sequence、offset、action=open/close、protocol_version、reason;传输层没有可靠协商状态时 protocol_version=null,版本由回放中的实际协议帧确定 |
| bytes | connection_id、sequence、offset、direction=received/sent、data(标准带 padding 的 base64);保留完成收发操作的实际分片边界 |
| gap | sequence、offset、reason、dropped;指出首个不能完整保留的位置,后续停止采样 |
| trailer | complete、reason、frames、events、sha256;frames 计应用字节块而非 PDU,events 计 connection/bytes/gap 行 |
所有 connection/bytes/gap 行使用全局从 1 连续递增的 sequence;offset 是相对录制开始的非负单调秒数。close 的 reason 仅为 closed、end_of_stream、transport_error,不保存异常原始文本。trailer 的 SHA-256 覆盖它之前每一行的原始 UTF-8 字节及换行,不重新序列化 JSON 后计算。回放还检查连接配对、计数与 trailer 后无多余行。
close 可带 failure_offset:null 表示没有单独观测的错误时刻;数字表示包装流实际观察到 EOF/I/O 异常的相对单调时刻。它必须不早于同连接上一条记录、不晚于 close 的 offset,不能用于 reason=closed。回放先在 failure_offset 处理断线,再推进至关闭完成时刻,防止慢速资源关闭把 retry 的起点推迟。早期 schema 1 文件可省略此字段;此时只能使用 close 时刻,不能猜测此前的失败时间。
session 固定保存 source_id、min_version、max_version、v2_profile、check_order、query_timeout;limits 保存 max_records_per_source、max_staging_wire_bytes、max_pdu_bytes_v1、max_pdu_bytes_v2_legacy。protocol_versions 保存 rtr_v1=RFC8210 和 rtr_v2 的显式 profile 字符串。不得保存端点主机名、TLS 文件路径、SSLContext、凭据或机密配置。原始协议字节可能包含上游 Error Report 文本,用户开启录制即选择保存这些协议输入。
公开构造器为 Recorder(path, *, limits=None, rtr_config=None)。构造和 wrap 不打开文件;进入 async context 后,以新建且不覆盖模式打开文件,在支持的平台请求仅文件所有者读写。rtr_config 只提取上述安全标量;省略时明确采用默认 v1、初始 Reset 的会话参数,绝不从 wire 自动猜测历史 ASPA 草案。包装器在 RtrSession 进入和配置变更校验时核对 header 的会话字段与四个协议资源限制;不一致在开始该配置前以 ConfigurationError 拒绝,不能用不匹配的回放基线录制。录制期间这些参数固定,调整时应开启新的录制;其他应用热配置不由传输装饰器捕获。
Recorder.wrap(factory=None) 返回遵守相同连接所有权、reconnectable 及显式 prepare/aclose 委托的 TransportFactory,可经 Client 的 transports 注入。省略 factory 时必须在构造 Recorder 时显式提供 rtr_config,使用其内置 TCP/TLS/SSH 传输;构造和 wrap 仍不连接或读取凭据,完整配置只在运行时私有保留,不进入录制 header。内置传输、ConnectedTransport 和录制包装器显式参与 Session 的准备/关闭生命周期;普通自定义工厂仍由宿主管理,不能仅凭同名 prepare/aclose 方法取得其所有权。未进入录制上下文或关闭后 connect 明确失败;先关闭会话,再关闭 Recorder 才能得到完整连接轨迹。已交付流仍归会话关闭,未交付的 owned ConnectedTransport 在 Recorder 退出时释放,borrowed 原始流保持宿主所有权。关闭失败保留句柄以供重试,不能丢弃后声称关闭。
每个 Recorder 只有一个固定非 daemon 文件线程。协议收发路径只使用非阻塞入队,不等待磁盘;队列按 recording_queue_size 与 max_recording_queue_bytes 限制,字节保守计入 base64/JSON 放大和正在写入的项目。第一次拥堵停止采样,并在已排队前缀后保存 gap。max_recording_bytes 包含完整文件;预留 1024 字节给 gap/trailer,预算小于 header 和预留空间时只报告失败。写入失败可表现为缺失 trailer;不回写 header 假装成功。
get_status() 返回不可变 RecorderStatus,含 lifecycle、complete、bytes_written、frames、queued_bytes、dropped、last_error。文件已存在、超限、写入错误及结果不确定的发送均不终止正常协议同步;它们使录制不完整并报告稳定 recording_* 错误。Recorder 主体异常/取消、或尚有活动连接时关闭,也明确标记不完整。aclose() 可重复调用,屏蔽必要清理的取消,等待已接纳写入和线程真正结束;不能把取消等待视为线程停止。底层文件系统调用必须最终返回。
正常完成只在 trailer.complete=true、无 gap、计数/sequence/连接关系正确且哈希匹配时成立。完整字节录制不等于协议有效、不证明实际 sink 接纳,也不代表所有互操作条件已通过。
回放
replay(path, *, reference_time, allow_incomplete=False, limits=None, max_events=10000, max_report_bytes=16777216) -> ReplayReport 是同步离线入口。它流式读取选定文件,用 reference_time + offset 驱动现有无 I/O RTR State,处理刷新、到期、总查询期限、分片、协商、错误及重连;不创建事件循环、不真实等待、不联网或写在线数据库。reference_time 必须带时区并规范化为 UTC。
回放按 header 的明确 profile 使用相同 codec/transaction,不猜测 -10/-13/-27,ASPA 的 AFI 原样保留。完整 initial_state=empty 以 Reset 开始;缺少原始恢复基态却录到初始 Serial 的文件会报告发送差异,不能猜测恢复成功。记录的协议预算不能超过调用者提供的 Limits;调用方可进一步收紧。输入总字节受 max_recording_bytes 限制。
报告逐字节比较虚拟状态机预期输出与实际 sent 字节,允许发送分片但不能丢字节、添加字节或改变顺序。ReplayEvent 保存状态、虚拟提交、错误、断连和未完成事务;提交只返回诊断计数、session/serial、虚拟原始时间及含 afi 的 ASPA 行,不返回 Snapshot、SourceUpdate 或完整 VRP/RouterKey 导出。虚拟提交按“立即成功的 sink 确认”模型推进,不声称捕获了实际应用发布。
ReplayReport 含 schema_version、protocol_versions、整个文件的 input_sha256、started_at、reference_time、recording_complete、complete、matched、events、differences、unverified。recording_complete 仅代表输入完整性;complete 还要求在报告预算内完成求值;matched 进一步要求无发送/状态差异。协议错误可被准确回放,因此 matched 不代表上游数据被接纳。差异保留最多 64 字节预期/实际片段,不保存任意异常文本。ReplayDifference.reason 是可空的稳定原因码;recording_incomplete 区分已核对完整 trailer 的 recorded_gap/<reason>(例如 recorded_gap/queue_full)、损坏摘要的 invalid_trailer,以及 duplicate_keys 等输入损坏原因,不能把摘要损坏的前缀当作已证实的普通丢帧。
事件和差异合计受 max_events、max_report_bytes 约束,后者至少为 1024,并为最终截断诊断预留 1024 字节。达到预算后停止协议求值,继续流式核对文件完整性,返回 complete=false 和 report_limit;不能无限累积报告对象,也不静默删减事件后声称完整。
缺少 trailer、gap、截断、摘要或 sequence 失败默认抛 InputError;allow_incomplete=True 只允许 complete=false 的有限前缀诊断。未知 header/schema/profile/初始状态语义始终拒绝,不因 allow_incomplete 开启而猜测。输入过大抛 ResourceLimitError;报告过大返回明确截断结果。
unverified 明确列出 actual_sink_acknowledgement、initial_restore_state、host_configuration_and_trust_changes、wall_clock_jumps、query_admission_and_timer_anchors。传输装饰器不能观测这些宿主行为,不能把完整录制解释为已验证。虚拟查询从 open/实际触发帧时刻计时,虚拟 EOD 从完整帧的 received 时刻计时;实际宿主接纳、调度和回调可能使 State 真正处理时刻不同,边界差异必须保留,不能自动添加容差抹平。原始 started_at 与 reference_time 明确记录时间映射;虚拟 EOD 的期限只用于离线说明,不向在线状态授予新寿命。公开可执行示例见 diagnostics.py。