1
0
Files
DiAL/openspec/specs/expect-rule-system/spec.md

16 KiB
Raw Blame History

Purpose

定义共享 expect 断言规则系统的核心概念和基础设施ValueMatcher 统一匹配器、ContentRules 内容规则数组、KeyValueExpect 键值规则、以及相关的启动期校验和失败路径规范。

Requirements

Requirement: ValueMatcher 统一匹配器

系统 SHALL 提供共享 ValueMatcher 作为所有非状态类 expect 的基础匹配结构。ValueMatcher SHALL 支持 equalscontainsregexexistsemptygtgteltlte 字段。equals MUST 支持任意 JSON value并使用深度相等比较。containsregex SHALL 将实际值转换为字符串后匹配。gtgteltlte SHALL 将实际值转换为有限数字后比较,无法转换为有限数字时 SHALL 判定不匹配。一个 ValueMatcher 对象包含多个 matcher 字段时,系统 SHALL 要求全部 matcher 均通过。

所有类型为 ValueMatcher 的 expect 字段 SHALL 同时接受 primitive 原始值string / number / boolean / null作为简写形式。原始值简写 SHALL 等价于 { equals: value }。系统 SHALL 在语义校验入口将 primitive 原始值归一化为 { equals: value } 对象形式,后续 resolve 和运行期逻辑 SHALL 仅处理 ValueMatcher 对象形式。数组和对象 MUST NOT 作为原始值简写;需要对数组或对象执行 equals 匹配时,配置 MUST 显式写成 { equals: value }

Scenario: equals 匹配对象

  • WHEN 实际值为 {status: "ok", count: 1} 且 matcher 为 {equals: {status: "ok", count: 1}}
  • THEN 系统 SHALL 使用深度相等判定该 matcher 通过

Scenario: contains 字符串化匹配

  • WHEN 实际值为 "service ready" 且 matcher 为 {contains: "ready"}
  • THEN 系统 SHALL 判定该 matcher 通过

Scenario: 数字范围组合匹配

  • WHEN 实际值为 50 且 matcher 为 {gte: 0, lte: 100}
  • THEN 系统 SHALL 判定该 matcher 通过

Scenario: 多 matcher 快速失败

  • WHEN 实际值为 "healthy" 且 matcher 为 {contains: "health", regex: "^ready$"}
  • THEN 系统 SHALL 在任一 matcher 不满足时判定该 matcher 对象不通过

Scenario: 字符串原始值简写等价 equals

  • WHEN expect 字段配置为 finishReason: "stop" 且实际值为 "stop"
  • THEN 系统 SHALL 将 "stop" 归一化为 {equals: "stop"} 并判定通过

Scenario: 数字原始值简写等价 equals

  • WHEN expect 字段配置为 rowCount: 1 且实际值为 1
  • THEN 系统 SHALL 将 1 归一化为 {equals: 1} 并判定通过

Scenario: 布尔原始值简写等价 equals

  • WHEN expect 字段配置为 ValueMatcher 类型且值为 true,实际值为 true
  • THEN 系统 SHALL 将 true 归一化为 {equals: true} 并判定通过

Scenario: null 原始值简写等价 equals

  • WHEN expect 字段配置为 ValueMatcher 类型且值为 null,实际值为 null
  • THEN 系统 SHALL 将 null 归一化为 {equals: null} 并判定通过

Scenario: 原始值简写不匹配

  • WHEN expect 字段配置为 finishReason: "stop" 且实际值为 "error"
  • THEN 系统 SHALL 判定不通过并生成 mismatch failure

Requirement: ValueMatcher 启动期校验

系统 SHALL 在启动期对所有 ValueMatcher 字段执行严格的类型和语义校验。校验 SHALL 同时接受 primitive 原始值和 ValueMatcher 对象两种形式。当输入为 primitive 原始值时,系统 SHALL 视为合法配置(等价于 {equals: value}),无需进一步校验 matcher 字段。当输入为 ValueMatcher 对象时,contains MUST 为 string。equals MAY 为任意 JSON value。existsempty MUST 为 boolean。gtgteltlte MUST 为有限数字(Number.isFinite)。regex MUST 为可编译的 string pattern并通过 ReDoS 风险校验。ValueMatcher 对象 MUST 至少包含一个合法 matcher 字段,空对象 {} SHALL 导致启动期配置错误。ValueMatcher 对象 MUST NOT 包含未知字段,任何不属于 equalscontainsregexexistsemptygtgteltlte 的字段 SHALL 导致启动期配置错误。

Scenario: 空 matcher 对象被拒绝

  • WHEN YAML 配置中任一 matcher 对象为空 {}
  • THEN 系统 SHALL 在启动期配置校验失败,提示 matcher 必须包含至少一个合法字段

Scenario: 未知 matcher 字段被拒绝

  • WHEN YAML 配置中任一 matcher 对象包含 foo: "bar" 等未知字段
  • THEN 系统 SHALL 在启动期配置校验失败,提示该字段未知

Scenario: 数值 matcher 非有限数字被拒绝

  • WHEN YAML 配置中任一 matcher 的 gtgteltlte 值为 NaNInfinity 或非数字类型
  • THEN 系统 SHALL 在启动期配置校验失败,提示数值 matcher 必须为有限数字

Scenario: 布尔 matcher 非布尔值被拒绝

  • WHEN YAML 配置中任一 matcher 的 existsempty 值不是布尔类型
  • THEN 系统 SHALL 在启动期配置校验失败,提示该字段必须为布尔值

Scenario: 字符串原始值校验通过

  • WHEN YAML 配置中 ValueMatcher 字段值为字符串 "stop"
  • THEN 系统 SHALL 接受该配置,视为 {equals: "stop"}

Scenario: 数字原始值校验通过

  • WHEN YAML 配置中 ValueMatcher 字段值为数字 5000
  • THEN 系统 SHALL 接受该配置,视为 {equals: 5000}

Scenario: null 原始值校验通过

  • WHEN YAML 配置中 ValueMatcher 字段值为 null
  • THEN 系统 SHALL 接受该配置,视为 {equals: null}

Scenario: 数组原始值被拒绝

  • WHEN YAML 配置中 ValueMatcher 字段值为数组 [1, 2]
  • THEN 系统 SHALL 在启动期配置校验失败,提示必须为 primitive 原始值或 matcher 对象;如需数组 equals 匹配应写成 {equals: [1, 2]}

Scenario: 对象原始值必须显式 equals

  • WHEN YAML 配置中 ValueMatcher 字段值为对象 {foo: "bar"},且 foo 不是合法 matcher 字段
  • THEN 系统 SHALL 在启动期配置校验失败,提示 foo 是未知 matcher如需对象 equals 匹配应写成 {equals: {foo: "bar"}}

Requirement: empty matcher 语义

empty: true SHALL 在以下情况判定通过:实际值为 nullundefined、空字符串 ""、空数组 [] 或空对象 {}empty: false SHALL 在以上条件均不满足时判定通过。数字 0 和布尔 false SHALL NOT 被视为 empty。

Scenario: null 视为 empty

  • WHEN 实际值为 null 且 matcher 为 {empty: true}
  • THEN 系统 SHALL 判定该 matcher 通过

Scenario: 空字符串视为 empty

  • WHEN 实际值为 "" 且 matcher 为 {empty: true}
  • THEN 系统 SHALL 判定该 matcher 通过

Scenario: 空数组视为 empty

  • WHEN 实际值为 [] 且 matcher 为 {empty: true}
  • THEN 系统 SHALL 判定该 matcher 通过

Scenario: 空对象视为 empty

  • WHEN 实际值为 {} 且 matcher 为 {empty: true}
  • THEN 系统 SHALL 判定该 matcher 通过

Scenario: 数字 0 不视为 empty

  • WHEN 实际值为 0 且 matcher 为 {empty: true}
  • THEN 系统 SHALL 判定该 matcher 不通过

Scenario: 布尔 false 不视为 empty

  • WHEN 实际值为 false 且 matcher 为 {empty: true}
  • THEN 系统 SHALL 判定该 matcher 不通过

Requirement: exists 与其他 matcher 的组合语义

ValueMatcher 同时包含 exists: false 和其他非存在性 matchercontainsregexequals 等)时,系统 SHALL 在启动期配置校验失败,提示 exists: false 不能与其他 matcher 组合使用。exists: true MAY 与其他 matcher 组合,语义为先确认存在再执行其他 matcher。

Scenario: exists false 与 contains 组合被拒绝

  • WHEN YAML 配置中 matcher 为 {exists: false, contains: "foo"}
  • THEN 系统 SHALL 在启动期配置校验失败,提示 exists: false 不能与其他 matcher 组合

Scenario: exists true 与 contains 组合允许

  • WHEN 实际值为 "hello foo" 且 matcher 为 {exists: true, contains: "foo"}
  • THEN 系统 SHALL 判定该 matcher 通过

Requirement: regex 字段语义

系统 SHALL 使用 regex 作为唯一正则 matcher 字段。regex 值 MUST 为可编译的字符串 pattern。运行期 SHALL 固定使用无 flags 的 new RegExp(pattern).test(String(actual)) 执行匹配。系统 MUST NOT 支持旧 match 字段。系统 SHALL 在启动期对所有 regex pattern 执行可编译校验和 ReDoS 风险校验。

Scenario: regex 任意位置匹配

  • WHEN 实际值为 "api status ok" 且 matcher 为 {regex: "status"}
  • THEN 系统 SHALL 判定该 matcher 通过,因为无 flags 的 JavaScript 正则仍会搜索整个字符串中的第一次匹配

Scenario: regex 完整匹配由用户声明锚点

  • WHEN 实际值为 "OK\n" 且 matcher 为 {regex: "^OK$"}
  • THEN 系统 SHALL 判定该 matcher 不通过,因为系统 MUST NOT 默认启用 multiline flags

Scenario: match 字段启动失败

  • WHEN YAML 配置中任一 matcher 对象包含 match: "ok"
  • THEN 系统 SHALL 在启动期配置校验失败,提示 match 是未知字段或不支持字段

Scenario: regex ReDoS 风险启动失败

  • WHEN YAML 配置中任一 regex"(a+)+$"
  • THEN 系统 SHALL 在启动期配置校验失败,提示正则存在 ReDoS 风险

Requirement: ContentRules 内容规则数组

系统 SHALL 提供共享 ContentRules 表达返回内容断言。ContentRules MUST 为有序数组,数组项 SHALL 为直接 ValueMatcher,或 jsoncssxpath 三类 extractor 规则之一。系统 SHALL 按数组顺序执行全部规则,任一规则失败时 SHALL 立即停止并返回该规则的 failure。系统 MUST NOT 支持内容字段的非数组对象快捷写法。

Scenario: 直接 matcher 内容规则

  • WHEN 内容字段配置 [{contains: "ready"}, {regex: "listening on \\d+"}] 且原始内容同时满足两条规则
  • THEN 系统 SHALL 判定该内容字段通过

Scenario: 内容规则数组快速失败

  • WHEN 内容字段配置三条规则且第二条规则失败
  • THEN 系统 SHALL 返回第二条规则的 failure并 MUST NOT 执行第三条规则

Scenario: 内容字段必须为数组

  • WHEN YAML 中内容字段配置为 {contains: "ok"} 而不是数组
  • THEN 系统 SHALL 在启动期配置校验失败,提示该内容字段必须为规则数组

Requirement: ContentRule 互斥性约束

一条 ContentRule MUST 为直接 ValueMatcher 或恰好一个 extractorjsoncssxpath 之一)。系统 MUST NOT 允许同一条规则同时包含多个 extractor。直接 ValueMatcher 规则 MUST NOT 包含 jsoncssxpath 字段。系统 SHALL 在启动期对违反互斥性的规则报错。

Scenario: 多 extractor 被拒绝

  • WHEN YAML 中内容规则为 {json: {path: "$.a"}, css: {selector: "div"}}
  • THEN 系统 SHALL 在启动期配置校验失败,提示一条规则不能同时包含多个 extractor

Scenario: 直接 matcher 混入 extractor 被拒绝

  • WHEN YAML 中内容规则为 {contains: "ok", json: {path: "$.a"}}
  • THEN 系统 SHALL 在启动期配置校验失败,提示直接 matcher 不能与 extractor 混用

Requirement: 空 ContentRules 数组语义

ContentRules 空数组 [] SHALL 被系统接受为合法配置。运行期空数组 SHALL 等价于无规则,即该内容字段的断言直接通过。

Scenario: 空 body 数组通过

  • WHEN HTTP target 配置 expect.body: [] 且响应体为任意内容
  • THEN 系统 SHALL 判定 body 阶段通过

Requirement: ContentRules 非字符串值序列化

ContentRules 的观测源为非字符串值(如对象或数组)时,直接 ValueMatchercontainsregex SHALL 先将值 JSON 序列化为字符串后匹配。equals SHALL 直接在原始结构化值上使用深度相等比较,不进行序列化。

Scenario: 对象序列化后 contains 匹配

  • WHEN ContentRules 观测源为 {status: "ok"} 且规则为 {contains: "ok"}
  • THEN 系统 SHALL 将对象 JSON 序列化后执行 contains 匹配

Scenario: 对象 equals 不序列化

  • WHEN ContentRules 观测源为 {status: "ok"} 且规则为 {equals: {status: "ok"}}
  • THEN 系统 SHALL 直接在结构化值上使用深度相等比较

Requirement: ContentRules 提取器

系统 SHALL 支持在 ContentRules 中使用 jsoncssxpath extractor。json.path MUST 使用现有 JSONPath 子集。css.selector MUST 为非空字符串,并 MAY 配置 attr 提取属性值。xpath.path MUST 为非空字符串,并 SHALL 在启动期进行可编译校验。Extractor 内部 MAY 包含任意 ValueMatcher 字段。Extractor 规则未配置任何 matcher 时 SHALL 等价于 exists: true

Scenario: json extractor 数字比较

  • WHEN 原始内容为 JSON 字符串 {"count": 2} 且规则为 {json: {path: "$.count", gte: 1}}
  • THEN 系统 SHALL 解析 JSON、提取 $.count 并判定该规则通过

Scenario: json extractor 存在性默认语义

  • WHEN 原始内容为 JSON 字符串 {"user": {"id": null}} 且规则为 {json: {path: "$.user.id"}}
  • THEN 系统 SHALL 将该规则视为 {json: {path: "$.user.id", exists: true}} 并判定通过

Scenario: css attr 存在性默认语义

  • WHEN 原始内容包含 <meta name="status" content="ok"> 且规则为 {css: {selector: "meta[name=status]", attr: "content"}}
  • THEN 系统 SHALL 在属性存在时判定该规则通过

Scenario: xpath 无匹配节点失败

  • WHEN XML 内容中不存在 XPath 指向的节点,且规则为 {xpath: {path: "/root/status"}}
  • THEN 系统 SHALL 判定该规则不通过并生成 phase 对应内容字段的 mismatch failure

Requirement: KeyValueExpect 键值规则

系统 SHALL 提供共享 KeyValueExpect 表达键值型观测值断言。KeyValueExpect SHALL 为动态键对象,每个键对应的值 MAY 为 ValueMatcher 或 JSON 字面量。字面量值 SHALL 等价于 {equals: <literal>}。调用方 MAY 指定 key 规范化策略HTTP 与 LLM headers MUST 使用大小写不敏感的 key 匹配。

Scenario: headers 字面量快捷写法

  • WHEN 响应 headers 中 content-typeapplication/json,且配置为 headers: {Content-Type: "application/json"}
  • THEN 系统 SHALL 按大小写不敏感 key 匹配并使用 equals 语义判定通过

Scenario: headers matcher 写法

  • WHEN 响应 headers 中 content-typeapplication/json; charset=utf-8,且配置为 headers: {Content-Type: {contains: "application/json"}}
  • THEN 系统 SHALL 判定该 header 规则通过

Scenario: 缺失键 exists false

  • WHEN 观测键值表中不存在 x-debug,且配置为 {x-debug: {exists: false}}
  • THEN 系统 SHALL 判定该键规则通过

Requirement: 结构化失败路径

系统 SHALL 在共享 matcher、content 和 key-value 断言失败时生成结构化 CheckFailure。failure SHALL 包含 kindphasepathmessage,并在 mismatch 场景包含 expectedactual。内容规则 failure path SHALL 包含数组下标key-value failure path SHALL 包含键名extractor failure path SHALL 包含 extractor 类型和 path/selector 信息。

Scenario: ContentRules 失败路径

  • WHEN expect.body[1].json 规则失败
  • THEN failure.path SHALL 指向 body[1].json($.path) 或等价可定位路径failure.phase SHALL 为 body

Scenario: KeyValueExpect 失败路径

  • WHEN expect.headers.Content-Type 不匹配
  • THEN failure.path SHALL 指向 headers.Content-Typefailure.phase SHALL 为 headers

Scenario: actual 截断

  • WHEN matcher 失败时 actual 字符串长度超过 200 字符
  • THEN 系统 SHALL 使用现有截断策略保存 failure.actual避免历史记录写入过长内容