首页
/ claw-code Runtime 报告模式 v1 测试夹具集:验证命令、六大契约覆盖点与源码级实现解读

claw-code Runtime 报告模式 v1 测试夹具集:验证命令、六大契约覆盖点与源码级实现解读

2026-09-04 11:54:20作者:尤辰城Agatha

本篇技术指南围绕 runtime crate 中报告模式 v1(claw.report.v1)的测试夹具集展开:先说明该夹具集的位置、验证命令与覆盖范围,再逐条对应到 report_schema.rs 中的真实实现——规范报告(canonical report)、claim 标签体系、负证据(negative evidence)、字段级差异归因、确定性投影与脱敏溯源、消费方能力协商与降级投影。读完后你应当能够独立运行夹具验证、读懂报告模式的每一条契约语义,并知道每个语义在源码中的落点。

夹具集是什么、如何验证

夹具集位于 rust/crates/runtime/tests/fixtures/report_schema_v1/README.md,官方给出的验证命令是:

cargo test -p runtime report_schema -- --nocapture

需要特别注意一点:这个 fixture 目录本身只承载 README 说明,真正的夹具数据是内嵌在测试代码中的。README 明确指出,实际的夹具由 runtime::report_schema::tests::fixture_report 这一 in-code fixture 提供,它构造并规范化了一份完整的 CanonicalReportV1 报告,然后由 report_schema 测试模块中的四个测试用例分别验证六大覆盖点。也就是说,"读取外部 JSON 夹具"与"内联夹具 + 断言"是两种不同的夹具策略,本 crate 选择了后者,保证夹具与测试断言在同一模块内演进、不会失配。

六大覆盖点(原文档逐条列出)如下,后文逐一展开:

  1. fact / hypothesis / confidence 标签;
  2. 带 checked surfaces 与 query window 的负证据;
  3. 字段级差异归因(field-level delta attribution);
  4. 规范报告 ID 加内容哈希;
  5. 确定性投影/脱敏溯源(provenance);
  6. 消费方能力协商与降级投影。

模块通过 lib.rs 对外导出全部公开 API:canonicalize_reportproject_reportreport_content_hashreport_schema_v1_registry 以及全部相关类型与常量 REPORT_SCHEMA_V1DEFAULT_PROJECTION_POLICY_V1,任何下游 crate(例如 CLI 侧)都可以直接依赖这套契约。

覆盖点一:fact / hypothesis / confidence 标签

报告中的每一条断言(claim)都必须标注"证据类别"和"置信度",而不是以自由文本形式混在报告里。源码中对应两组枚举(report_schema.rs):

pub enum ClaimKind {
    ObservedFact,      // 观察到的事实
    Inference,         // 基于证据的推断
    Hypothesis,        // 假设
    Recommendation,    // 建议
}

pub enum ReportConfidence {
    High, Medium, Low, Unknown,
}

两个枚举都通过 #[serde(rename_all = "snake_case")] 序列化为 observed_facthypothesis 等小写下划线形式。ReportClaim 结构(同文件 L54-L62)还带有 evidence: Vec<String>(支持该 claim 的证据 ID 列表,为空时不序列化)和 sensitivity: SensitivityClass 字段。

夹具报告 fixture_report()report_schema.rs#L407-L458)构造了三条覆盖不同标签组合的 claim:

  • claim-factObservedFact + High 置信度,证据为 event:lane.finished,敏感度 Public
  • claim-hypothesisHypothesis + Medium 置信度,证据为 event:transport,敏感度 Internal
  • claim-secretObservedFact + High 置信度,但敏感度为 Secret——这条专门用于验证后文的脱敏路径。

对应测试 canonical_report_labels_claims_negative_evidence_and_deltas(L490-L504)断言了规范化后 claims[1] 的 kind 是 Hypothesis、confidence 是 Medium,即标签语义在规范化排序后仍然可被逐位验证。

这套"标签 + 置信度 + 证据引用"的设计与项目 G004 事件/报告契约文档中的要求一致:docs/g004-events-reports-contract.md 明确写有"断言必须标注为 facthypothesis 或另声明的证据类别,并附带置信度和来源引用"。

覆盖点二:带 checked surfaces 与 query window 的负证据

"没查到"和"没查过"在自动化消费中是两种完全不同的状态,报告模式把负证据提升为一等公民(first-class)结构:

pub enum NegativeFindingStatus {
    NotObservedInCheckedScope,  // 在已检查范围内确认未出现
    UnknownNotChecked,          // 未检查,状态未知
}

pub struct NegativeEvidence {
    pub id: String,
    pub status: NegativeFindingStatus,
    pub checked_surfaces: Vec<String>,  // 检查过哪些面
    pub query: String,                  // 执行的查询
    pub window: String,                 // 查询时间窗口
    pub sensitivity: SensitivityClass,
}

(定义见 report_schema.rs#L46-L73

夹具中的负证据条目是:

NegativeEvidence {
    id: "neg-blocker",
    status: NegativeFindingStatus::NotObservedInCheckedScope,
    checked_surfaces: vec!["lane_events", "worker_status"],
    query: "current blocker",
    window: "2026-05-14T00:00:00Z/2026-05-14T00:05:00Z",
    sensitivity: SensitivityClass::Public,
}

语义是:"在 lane_eventsworker_status 这两个面上,按 current blocker 查询、在 2026-05-14 前 5 分钟窗口内,确认没有发现 blocker"。checked_surfaces + query + window 三元组让消费方可以判断"未出现"结论的适用边界——这正是 G004 契约文档中"not observedchecked and absentredacted 是不同状态"这条契约在 v1 实现里的最小落地。测试 canonical_report_labels_claims_negative_evidence_and_deltas 断言了该条目状态为 NotObservedInCheckedScope

覆盖点三:字段级差异归因(field-level delta attribution)

当报告要表达"某个状态字段相对上一份报告发生了什么变化"时,不能只说"变了",而要说明变前变后的值、变化类型以及归因来源。FieldDelta 结构(report_schema.rs#L75-L84):

pub enum FieldDeltaState {
    Changed,          // 值改变
    Unchanged,        // 保持不变
    Cleared,         // 被清空
    CarriedForward,   // 从上份报告沿用
}

pub struct FieldDelta {
    pub field: String,
    pub state: FieldDeltaState,
    pub previous_hash: Option<String>,  // 变化前值哈希
    pub current_hash: Option<String>,    // 变化后值哈希
    pub attribution: String,             // 归因说明
}

夹具中的差异条目为 field: "blocker"state: Clearedprevious_hash: Some("prev123")current_hash: Noneattribution: "lane.failed reconciled to lane.finished"——即 blocker 字段从"有"到"清空",归因是 lane 失败事件与 lane 完成事件完成调和后的结果。previous_hash/current_hash 使用哈希而非原始值,配合 Cleared 状态下 current_hashNone 的约定,让"清空"这类没有现值的差异也能被精确描述。

这个设计与 G004 契约文档中"字段差异必须命名字段、前后状态、归因,以及差异来自源内容/投影/降级/脱敏策略中的哪一种"的要求直接对应。

覆盖点四:规范报告 ID 加内容哈希

报告身份由 ReportIdentity 承载:

pub struct ReportIdentity {
    pub report_id: String,
    pub content_hash: String,
}

核心是 canonicalize_report 函数(report_schema.rs#L222-L233),它做了四件事:

  1. 强制 schema_versionclaw.report.v1
  2. claims(按 id)、negative_evidence(按 id)、field_deltas(按 field)做稳定排序——这是内容哈希确定性的前提:同一组数据以任何顺序输入,规范化后都得到同一份报告;
  3. 计算内容哈希;若 report_id 为空,则以 report-{content_hash} 自动填充,保证每份报告拥有稳定且可由内容推导的标识;
  4. 回填 identity.content_hash

哈希本身由 report_content_hash(L236-L241)计算:克隆一份报告、清空 identity.report_ididentity.content_hash 两个自引用字段后,对剩余规范载荷做稳定 JSON 哈希。这个"排除 identity 再哈希"的设计避免了循环依赖(哈希不能包含自己),同时让"内容未变则哈希不变"成立——下游消费方可以只凭 content hash 判断两份报告是否同源。

测试 canonical_report_labels_claims_negative_evidence_and_deltas 验证了这些行为:report_idreport- 开头、content_hash 长度为 16(十六进制表示的 8 字节)。

稳定哈希的底层是 stable_json_hashreport_schema.rs#L372-L396):先经 normalize_json 递归把任意对象重排为按 key 排序的 BTreeMap 结构,再序列化、取 SHA-256 的前 8 字节转成 16 位十六进制。因此哈希与 JSON 字段书写顺序无关,跨语言、跨序列化器只要 key 集合与值相同就能得到同一哈希——这是跨进程比较报告/投影是否一致的基础设施。

覆盖点五:确定性投影与脱敏溯源

project_reportreport_schema.rs#L244-L323)把一份规范报告按消费方能力裁剪为 ReportProjectionV1。投影不是简单删字段,而是"删了必须留痕":

pub struct RedactionProvenance {
    pub field_path: String,   // 如 "claims[2]"、"claims[1].text"
    pub reason: String,       // 省略或转换的原因
    pub policy_id: String,    // 应用的策略(claw.report.projection.v1)
    pub original_hash: String, // 被脱敏原文的哈希
}

pub struct ProjectionProvenance {
    pub policy_id: String,
    pub source_schema_version: String,
    pub source_report_id: String,       // 回溯到源报告 ID
    pub source_content_hash: String,    // 回溯到源内容哈希
    pub consumer: String,
    pub downgraded: bool,               // 本次投影是否发生了降级
    pub omitted_field_families: Vec<String>,
    pub redactions: Vec<RedactionProvenance>,
}

脱敏的具体决策在 redact_claim(L338-L370)中,按 SensitivityClass 与消费方 max_sensitivity 的比较分三级处理(敏感度枚举为 Public < Internal < OperatorOnly < Secret):

  • claim 敏感度 ≤ 消费方上限:原样输出;
  • claim 敏感度低于 Secret 但超限:转换式脱敏——text 替换为 "<redacted>"、清空 evidence,保留 claim 骨架,溯源记为 transformed: sensitivity exceeds consumer policy,字段路径 claims[{i}].text
  • claim 敏感度为 Secret 且超限:整体省略,溯源记为 omitted: sensitivity exceeds consumer policy,字段路径 claims[{i}]

无论转换还是省略,都会附上原文的 original_hash,让有权限的审计方能够确认"这里确实存在一条被隐藏的 Secret 级断言,且其原文是这份哈希对应的内容",而普通消费方只能看到存在性,看不到内容。

投影自身的 projection_id 也是内容哈希:对 {view, provenance, payload} 三元组做稳定 JSON 哈希(L317-L321)。测试 projections_are_deterministic_and_record_redaction_provenance(L506-L535)对同一报告、同一能力集调用两次 project_report,断言:

  • 两次结果完全相等(确定性);
  • provenance.source_report_id / source_content_hash 与源报告一致(血缘可回溯);
  • downgraded 为 true;
  • 恰好产生 2 条脱敏记录,分别落在 claims[1].textclaim-hypothesis 的 Internal 级对 Public 上限的转换)与 claims[2]claim-secret 的整体省略)。

注意夹具中 claim 在规范化后按 id 排序为 claim-factclaim-hypothesisclaim-secret,所以脱敏溯源用的是排序后的下标——这条断言同时隐性地验证了"规范化排序先于投影裁剪"的执行顺序。

覆盖点六:消费方能力协商与降级投影

消费方通过 ConsumerCapabilities 声明自己能消化什么:

pub struct ConsumerCapabilities {
    pub consumer: String,
    pub schema_versions: BTreeSet<String>,   // 支持的报告模式版本
    pub field_families: BTreeSet<String>,    // 支持的字段族:claims / negative_evidence / field_deltas
    pub max_sensitivity: SensitivityClass,   // 可接收的最高敏感度
}

report_schema.rs#L106-L114

字段族的判定规则在 supports_family(L334-L336)里有一条关键约定:field_families 为空集合表示"支持全部字段族",非空集合才做白名单过滤。这让老消费方(尚未实现字段族声明)默认获得完整载荷,新消费方则显式声明需求。测试 capability_negotiation_omits_unsupported_field_families(L537-L551)验证了白名单路径:能力集只声明 claims 后,投影载荷中存在 claimsnegative_evidencefield_deltas 两个字段被整体移除,且 omitted_field_families 精确记录了 ["negative_evidence", "field_deltas"]downgraded 为 true。

downgraded 标志的判定条件是三者任一成立:消费方不支持当前 schema 版本、有字段族被整体省略、或发生过任何脱敏。这意味着"降级"不是可选的元数据,而是每次投影都会诚实计算并记录的状态——G004 契约文档中"生产者只保留一份全保真规范报告,仅携带 downgraded 语义的元数据输出降级投影"的要求正是这样实现的。

自描述模式注册表

除了运行时代码,report_schema_v1_registry()report_schema.rs#L163-L219)返回一份自描述的字段注册表:schema_versionclaw.report.v1,兼容性策略声明为 "additive fields are compatible; missing required fields are breaking"(新增字段向前兼容,缺失必填字段即为破坏性变更),并逐条列出 7 个模式字段:

字段 ID 说明 必填 字段族
identity.report_id 稳定的规范报告身份 identity
identity.content_hash 规范载荷(除 identity 外)的哈希 identity
claims[].kind fact/inference/hypothesis/recommendation 标签 claims
claims[].confidence claim 的置信度分桶 claims
claims[].evidence 支持该 claim 的证据 ID claims
negative_evidence[] 带检查范围的"搜索后未发现"结论 negative_evidence
field_deltas[] 字段级 changed/unchanged/cleared/carried-forward 归因 field_deltas
projection.provenance.redactions[] 投影字段的脱敏策略溯源 projection

测试 report_schema_registry_is_self_describing(L472-L488)验证了注册表版本正确且包含上述关键字段。这份注册表的价值在于把"哪些字段缺失会导致破坏性不兼容"(当前仅 identity.report_ididentity.content_hash,以及 claim 的 kind/confidence 标签)固化成了机器可读的单一事实来源,下游做版本协商时不必去解析散文文档。

运行验证与结果解读

在仓库根目录(rust/ 所在层级)执行:

cargo test -p runtime report_schema -- --nocapture

该命令以 report_schema 为过滤器命中 report_schema.rs 测试模块中的四个测试用例,各自守护一组契约:

测试 守护的契约
report_schema_registry_is_self_describing 注册表版本与关键字段自描述
canonical_report_labels_claims_negative_evidence_and_deltas 规范化排序、report id 前缀、16 位内容哈希、claim/负证据/差异标签语义
projections_are_deterministic_and_record_redaction_provenance 两次投影结果相等、血缘字段回填、2 条脱敏记录的路径
capability_negotiation_omits_unsupported_field_families 字段族白名单过滤、omitted 列表与 downgraded 标志

由于夹具是 in-code 的,运行该命令不需要任何外部文件准备;-- --nocapture 参数则用于在测试输出较多时保留 stdout 可见性,便于直接观察投影载荷内容。

与 G004 契约一致性校验的衔接

报告模式 v1 不是孤立存在的,它还参与 G004 事件/报告契约包(contract bundle)的机器校验。g004_conformance.rs 中的 validate_g004_contract_bundle 对 JSON 形态的 bundle 做跨语言一致性验证,其中 reports[] 部分要求报告携带 schema 身份、内容哈希、投影/脱敏溯源、能力协商、fact/hypothesis 标签与字段级差异归因——与夹具集的六大覆盖点逐条同构。对应的黄金夹具 g004_contract_bundle.valid.jsong004_conformance.rs 测试验证:合法 bundle 零错误通过;非法 bundle(例如 finding 的 kind 写成 guessconfidence 写成 certain)会以机器可读的 JSON 路径形式(如 /reports/0/findings/0/kind)逐条报告缺漏。可以推断,G004 一致性校验面向的是 bundle 序列化形态(camelCase 的 JSON 契约),而 report_schema.rs 面向的是进程内的规范报告类型——两者共享同一套语义契约,前者保证跨实现的可比对性,后者(即本篇的夹具集)保证语义实现本身不退化。

小结

这份夹具集用一份内联规范报告加四个测试用例,把报告模式 v1 的六类契约语义全部钉死:claim 的证据标签与置信度、带检查范围与时间窗的负证据、带前后哈希与归因的字段差异、可推导且排除自引用的内容哈希、留痕完整的确定性投影与脱敏、以及显式的消费方能力协商与降级标记。验证入口始终是同一命令 cargo test -p runtime report_schema -- --nocapture,实现细节则全部集中在 report_schema.rs 单一模块中,配合 docs/g004-events-reports-contract.md 的契约导读,构成"文档 → 类型 → 夹具 → 测试"一条完整的可追溯链路。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384