- 合并 20+ 细粒度 spec 为粗粒度主题规范:dashboard、data-store、probe-engine、probe-api、probe-config 等 - 删除完全冗余规范:data-retention(被 probe-engine+data-store 覆盖)、backend-code-quality(DEVELOPMENT.md 已记录) - 补充 http-checker 规范至完整标准(配置+执行+expect+校验+observation),匹配代码 440 行实现 - 清理 tcp/udp/llm checker 规范中已废弃 defaults 配置段的残留 Scenario - 清理 checker-cohesion-structure 中的实现路径引用(src/server/...) - 统一所有 spec 格式(## Purpose 开头,去除 # Capability/Title 形式) - 更新 prompt-spec-review.md 审查提示文档
22 KiB
Purpose
定义共享 expect 断言规则系统的核心概念和基础设施:ValueMatcher 统一匹配器、ContentExpectations 内容断言数组、KeyedExpectations 键控断言数组、HTTP 场景特有规则(状态码范围匹配、body 运行期失败结构化)、以及相关的启动期校验和失败路径规范。
Requirements
Requirement: ValueMatcher 统一匹配器
系统 SHALL 提供共享 ValueMatcher 作为所有非状态类 value expectation 的基础匹配结构。ValueMatcher SHALL 支持 equals、contains、regex、exists、empty、gt、gte、lt 和 lte 字段。equals MUST 支持任意 JSON value,并使用深度相等比较。contains 和 regex SHALL 将实际值转换为字符串后匹配。gt、gte、lt 和 lte SHALL 将实际值转换为有限数字后比较,无法转换为有限数字时 SHALL 判定不匹配。一个 ValueMatcher 对象包含多个 matcher 字段时,系统 SHALL 要求全部 matcher 均通过。
所有类型为 Authoring RawValueExpectation 的 expect 字段 SHALL 同时接受 primitive 原始值(string / number / boolean / null)作为简写形式。原始值简写 SHALL 等价于 { equals: value }。系统 SHALL 在 Normalized 阶段将 primitive 原始值归一化为 { equals: value } 对象形式。Normalized Config、checker 语义校验、checker.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: 字符串原始值简写在 Normalized 阶段等价 equals
- WHEN Authoring Config 中 expect 字段配置为
finishReason: "stop" - THEN Normalized Config SHALL 将
"stop"归一化为{equals: "stop"},运行期实际值为"stop"时判定通过
Scenario: 数字原始值简写在 Normalized 阶段等价 equals
- WHEN Authoring Config 中 expect 字段配置为
rowCount: 1 - THEN Normalized Config SHALL 将
1归一化为{equals: 1},运行期实际值为1时判定通过
Scenario: 布尔原始值简写在 Normalized 阶段等价 equals
- WHEN Authoring Config 中 RawValueExpectation 类型字段值为
true - THEN Normalized Config SHALL 将
true归一化为{equals: true},运行期实际值为true时判定通过
Scenario: null 原始值简写在 Normalized 阶段等价 equals
- WHEN Authoring Config 中 RawValueExpectation 类型字段值为
null - THEN Normalized Config SHALL 将
null归一化为{equals: null},运行期实际值为null时判定通过
Scenario: 原始值简写不匹配
- WHEN expect 字段配置为
finishReason: "stop"且实际值为"error" - THEN 系统 SHALL 判定不通过并生成 mismatch failure
Requirement: ValueMatcher 启动期校验
系统 SHALL 在启动期对所有 normalized ValueMatcher 字段执行严格的类型和语义校验。Authoring primitive 简写 MUST 在语义校验前由 Normalized 阶段转换为 {equals: value},因此语义 validator SHALL 校验 ValueMatcher 对象而不是 Raw primitive 输入。当输入为 ValueMatcher 对象时,contains MUST 为 string。equals MAY 为任意 JSON value。exists 和 empty MUST 为 boolean。gt、gte、lt 和 lte MUST 为有限数字(Number.isFinite)。regex MUST 为可编译的 string pattern,并通过 ReDoS 风险校验。ValueMatcher 对象 MUST 至少包含一个合法 matcher 字段,空对象 {} SHALL 导致启动期配置错误。ValueMatcher 对象 MUST NOT 包含未知字段,任何不属于 equals、contains、regex、exists、empty、gt、gte、lt、lte 的字段 SHALL 导致启动期配置错误。
Scenario: 空 matcher 对象被拒绝
- WHEN YAML 配置中任一 matcher 对象为空
{} - THEN 系统 SHALL 在启动期配置校验失败,提示 matcher 必须包含至少一个合法字段
Scenario: 未知 matcher 字段被拒绝
- WHEN YAML 配置中任一 matcher 对象包含
foo: "bar"等未知字段 - THEN 系统 SHALL 在启动期配置校验失败,提示该字段未知
Scenario: 数值 matcher 非有限数字被拒绝
- WHEN YAML 配置中任一 matcher 的
gt、gte、lt或lte值为NaN、Infinity或非数字类型 - THEN 系统 SHALL 在启动期配置校验失败,提示数值 matcher 必须为有限数字
Scenario: 布尔 matcher 非布尔值被拒绝
- WHEN YAML 配置中任一 matcher 的
exists或empty值不是布尔类型 - THEN 系统 SHALL 在启动期配置校验失败,提示该字段必须为布尔值
Scenario: 字符串原始值在 Normalized schema 中被拒绝
- WHEN Normalized schema 校验 RawValueExpectation 字段值为字符串
"stop" - THEN schema 校验 SHALL 失败,因为 Normalized Config 只接受
ValueMatcher对象
Scenario: 数字原始值在 Normalized schema 中被拒绝
- WHEN Normalized schema 校验 RawValueExpectation 字段值为数字
5000 - THEN schema 校验 SHALL 失败,因为 Normalized Config 只接受
ValueMatcher对象
Scenario: null 原始值在 Normalized schema 中被拒绝
- WHEN Normalized schema 校验 RawValueExpectation 字段值为
null - THEN schema 校验 SHALL 失败,因为 Normalized Config 只接受
ValueMatcher对象
Scenario: 数组原始值被拒绝
- WHEN YAML 配置中 RawValueExpectation 字段值为数组
[1, 2] - THEN 系统 SHALL 在启动期配置校验失败,提示必须为 primitive 原始值或 matcher 对象;如需数组 equals 匹配应写成
{equals: [1, 2]}
Scenario: 对象原始值必须显式 equals
- WHEN YAML 配置中 RawValueExpectation 字段值为对象
{foo: "bar"},且foo不是合法 matcher 字段 - THEN 系统 SHALL 在启动期配置校验失败,提示
foo是未知 matcher;如需对象 equals 匹配应写成{equals: {foo: "bar"}}
Requirement: empty matcher 语义
empty: true SHALL 在以下情况判定通过:实际值为 null、undefined、空字符串 ""、空数组 [] 或空对象 {}。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 和其他非存在性 matcher(如 contains、regex、equals 等)时,系统 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: ContentExpectations 内容断言数组
系统 SHALL 提供共享 ContentExpectations 表达返回内容断言。Authoring ContentExpectations MUST 为有序数组,数组项 SHALL 为直接 ValueMatcher,或 json、css、xpath 三类 extractor expectation 之一。系统 SHALL 在 Normalized 阶段将 Authoring content DSL 解析为带 kind 字段的 Normalized ContentExpectation 执行计划,并按数组顺序执行全部 expectation,任一 expectation 失败时 SHALL 立即停止并返回该 expectation 的 failure。系统 MUST NOT 支持内容字段的非数组对象快捷写法。
Scenario: 直接 matcher 内容 expectation
- WHEN 内容字段配置
[{contains: "ready"}, {regex: "listening on \\d+"}]且原始内容同时满足两条 expectation - THEN 系统 SHALL 判定该内容字段通过
Scenario: 内容 expectation 数组快速失败
- WHEN 内容字段配置三条 expectation 且第二条 expectation 失败
- THEN 系统 SHALL 返回第二条 expectation 的 failure,并 MUST NOT 执行第三条 expectation
Scenario: 内容字段必须为数组
- WHEN YAML 中内容字段配置为
{contains: "ok"}而不是数组 - THEN 系统 SHALL 在启动期配置校验失败,提示该内容字段必须为 expectation 数组
Scenario: Normalized content expectation 使用 kind
- WHEN Authoring 内容字段包含直接 matcher、json extractor、css extractor 和 xpath extractor
- THEN Normalized 阶段 SHALL 分别生成
kind="value"、kind="json"、kind="css"和kind="xpath"的ContentExpectation
Requirement: ContentExpectation 互斥性约束
一条 Raw ContentExpectation MUST 为直接 ValueMatcher 或恰好一个 extractor(json、css、xpath 之一)。系统 MUST NOT 允许同一条 Raw expectation 同时包含多个 extractor。直接 ValueMatcher expectation MUST NOT 包含 json、css、xpath 字段。系统 SHALL 在启动期对违反互斥性的 Raw expectation 报错。
Scenario: 多 extractor 被拒绝
- WHEN YAML 中内容 expectation 为
{json: {path: "$.a"}, css: {selector: "div"}} - THEN 系统 SHALL 在启动期配置校验失败,提示一条 expectation 不能同时包含多个 extractor
Scenario: 直接 matcher 混入 extractor 被拒绝
- WHEN YAML 中内容 expectation 为
{contains: "ok", json: {path: "$.a"}} - THEN 系统 SHALL 在启动期配置校验失败,提示直接 matcher 不能与 extractor 混用
Requirement: 空 ContentExpectations 数组语义
ContentExpectations 空数组 [] SHALL 被系统接受为合法配置。运行期空数组 SHALL 等价于无内容 expectation,即该内容字段的断言直接通过。
Scenario: 空 body 数组通过
- WHEN HTTP target 配置
expect.body: []且响应体为任意内容 - THEN 系统 SHALL 判定 body 阶段通过
Requirement: ContentExpectations 非字符串值序列化
当 ContentExpectations 的观测源为非字符串值(如对象或数组)时,直接 ValueMatcher 的 contains 和 regex SHALL 先将值 JSON 序列化为字符串后匹配。equals SHALL 直接在原始结构化值上使用深度相等比较,不进行序列化。
Scenario: 对象序列化后 contains 匹配
- WHEN ContentExpectations 观测源为
{status: "ok"}且 expectation 为{contains: "ok"} - THEN 系统 SHALL 将对象 JSON 序列化后执行 contains 匹配
Scenario: 对象 equals 不序列化
- WHEN ContentExpectations 观测源为
{status: "ok"}且 expectation 为{equals: {status: "ok"}} - THEN 系统 SHALL 直接在结构化值上使用深度相等比较
Requirement: ContentExpectations 提取器
系统 SHALL 支持在 Authoring ContentExpectations 中使用 json、css 和 xpath extractor。json.path MUST 使用现有 JSONPath 子集。css.selector MUST 为非空字符串,并 MAY 配置 attr 提取属性值。xpath.path MUST 为非空字符串,并 SHALL 在启动期进行可编译校验。Extractor 内部 MAY 包含任意 ValueMatcher 字段。Extractor expectation 未配置任何 matcher 时,Normalized 阶段 SHALL 将其 matcher 物化为 { exists: true }。
Scenario: json extractor 数字比较
- WHEN 原始内容为 JSON 字符串
{"count": 2}且 expectation 为{json: {path: "$.count", gte: 1}} - THEN 系统 SHALL 解析 JSON、提取
$.count并判定该 expectation 通过
Scenario: json extractor 存在性默认语义
- WHEN 原始内容为 JSON 字符串
{"user": {"id": null}}且 expectation 为{json: {path: "$.user.id"}} - THEN Normalized 阶段 SHALL 将该 expectation 的 matcher 物化为
{exists: true}并在运行期判定通过
Scenario: css attr 存在性默认语义
- WHEN 原始内容包含
<meta name="status" content="ok">且 expectation 为{css: {selector: "meta[name=status]", attr: "content"}} - THEN Normalized 阶段 SHALL 将该 expectation 的 matcher 物化为
{exists: true}并在属性存在时判定通过
Scenario: xpath 无匹配节点失败
- WHEN XML 内容中不存在 XPath 指向的节点,且 expectation 为
{xpath: {path: "/root/status"}} - THEN 系统 SHALL 判定该 expectation 不通过并生成 phase 对应内容字段的 mismatch failure
Requirement: KeyedExpectations 键控断言数组
系统 SHALL 提供共享 KeyedExpectations 表达键值型观测值断言。Authoring KeyedExpectations SHALL 为动态键对象,每个键对应的值 MUST 为 Authoring RawValueExpectation;数组和对象简写 MUST 被拒绝,数组或对象 equals 匹配 MUST 显式写为 {equals: <value>}。Normalized KeyedExpectations SHALL 为有序数组,每个元素包含原始 key 和已归一化的 ValueExpectation matcher。调用方 MAY 指定 key 规范化策略;HTTP 与 LLM headers MUST 使用大小写不敏感的 key 匹配,并 MUST 在启动期校验或 Normalized 阶段拒绝归一化后重复的 header key。DB rows 不做 key 归一化,按查询结果列名大小写敏感匹配。
Scenario: headers 字面量快捷写法
- WHEN 响应 headers 中
content-type为application/json,且配置为headers: {Content-Type: "application/json"} - THEN Normalized 阶段 SHALL 将该项解析为 keyed expectation
{key: "Content-Type", matcher: {equals: "application/json"}},运行期按大小写不敏感 key 匹配并判定通过
Scenario: headers matcher 写法
- WHEN 响应 headers 中
content-type为application/json; charset=utf-8,且配置为headers: {Content-Type: {contains: "application/json"}} - THEN 系统 SHALL 判定该 header expectation 通过
Scenario: 缺失键 exists false
- WHEN 观测键值表中不存在
x-debug,且配置为{x-debug: {exists: false}} - THEN 系统 SHALL 判定该 keyed expectation 通过
Scenario: keyed 对象值必须显式 equals
- WHEN Raw keyed expectation 的某个值是对象
{foo: "bar"}且未写在equals下 - THEN 系统 SHALL 在启动期配置校验失败,提示对象 equals 必须显式写成
{equals: {foo: "bar"}}
Scenario: keyed 数组值必须显式 equals
- WHEN Raw keyed expectation 的某个值是数组
["a"]且未写在equals下 - THEN 系统 SHALL 在启动期配置校验失败,提示数组 equals 必须显式写成
{equals: ["a"]}
Scenario: header 归一化重复 key 被拒绝
- WHEN HTTP 或 LLM
expect.headers同时配置Content-Type和content-type - THEN 系统 SHALL 在启动期配置校验失败,提示 header key 归一化后重复
Requirement: 结构化失败路径
系统 SHALL 在共享 matcher、content 和 keyed expectation 断言失败时生成结构化 CheckFailure。failure SHALL 包含 kind、phase、path、message,并在 mismatch 场景包含 expected 和 actual。内容 expectation failure path SHALL 包含数组下标,keyed expectation failure path SHALL 包含键名,extractor failure path SHALL 包含 extractor 类型和 path/selector 信息。failure.expected SHOULD 使用用户可理解的 matcher 或 expectation 片段,MUST NOT 直接暴露 Resolved kind 执行计划;单字段 equals 包装 SHOULD 展示为原始 expected 值。
Scenario: ContentExpectations 失败路径
- WHEN
expect.body[1].jsonexpectation 失败 - THEN failure.path SHALL 指向
body[1].json($.path)或等价可定位路径,failure.phase SHALL 为body
Scenario: KeyedExpectations 失败路径
- WHEN
expect.headers.Content-Type不匹配 - THEN failure.path SHALL 指向
headers.Content-Type,failure.phase SHALL 为headers
Scenario: actual 截断
- WHEN matcher 失败时 actual 字符串长度超过 200 字符
- THEN 系统 SHALL 使用现有截断策略保存 failure.actual,避免历史记录写入过长内容
Requirement: 状态码范围匹配
系统 SHALL 支持在 expect.status 数组中使用范围模式字符串("1xx"、"2xx"、"3xx"、"4xx"、"5xx"),与精确数字混合使用。范围模式 SHALL 匹配对应百位段内的所有状态码。其他范围模式 SHALL 在启动期配置校验失败。
Scenario: 1xx 范围匹配
- WHEN HTTP target 配置
expect.status: ["1xx"],且响应状态码为 101 - THEN 系统 SHALL 判定状态码匹配
Scenario: 2xx 范围匹配
- WHEN HTTP target 配置
expect.status: ["2xx"],且响应状态码为 200 - THEN 系统 SHALL 判定状态码匹配
Scenario: 2xx 范围匹配 204
- WHEN HTTP target 配置
expect.status: ["2xx"],且响应状态码为 204 - THEN 系统 SHALL 判定状态码匹配
Scenario: 2xx 范围不匹配 301
- WHEN HTTP target 配置
expect.status: ["2xx"],且响应状态码为 301 - THEN 系统 SHALL 判定状态码不匹配
Scenario: 混合精确值与范围模式
- WHEN HTTP target 配置
expect.status: ["2xx", 301],且响应状态码为 301 - THEN 系统 SHALL 判定状态码匹配(精确值 301 匹配)
Scenario: 混合精确值与范围模式范围命中
- WHEN HTTP target 配置
expect.status: ["2xx", 301],且响应状态码为 204 - THEN 系统 SHALL 判定状态码匹配(2xx 范围命中)
Scenario: 5xx 范围匹配
- WHEN HTTP target 配置
expect.status: ["5xx"],且响应状态码为 503 - THEN 系统 SHALL 判定状态码匹配
Scenario: 非 HTTP 范围模式启动失败
- WHEN HTTP target 配置
expect.status: ["6xx"] - THEN 系统 SHALL 在启动期配置校验失败
Requirement: HTTP body 运行期失败结构化
系统 SHALL 将 HTTP body 运行期失败记录为结构化 CheckFailure,并保留与具体 expectation 相关的 phase 和 path。响应内容不符合配置 SHALL 记录为 mismatch;响应内容无法按配置解析或解码 SHALL 记录为 error。
Scenario: JSON 响应不是合法 JSON
- WHEN HTTP target 配置 json body expectation,但响应体不是合法 JSON
- THEN 系统 SHALL 记录
failure.kind="error"、failure.phase="body",且 failure.path SHALL 指向对应 json expectation
Scenario: CSS selector 无匹配元素
- WHEN HTTP target 配置 css body expectation,但响应 HTML 中无匹配元素
- THEN 系统 SHALL 记录
failure.kind="mismatch"、failure.phase="body",且 failure.path SHALL 指向对应 css expectation
Scenario: XPath 无匹配节点
- WHEN HTTP target 配置 xpath body expectation,但响应 XML/HTML 中无匹配节点
- THEN 系统 SHALL 记录
failure.kind="mismatch"、failure.phase="body",且 failure.path SHALL 指向对应 xpath expectation