feat: 重构配置生命周期为 Authoring/Normalized/Resolved 三层
将变量替换和 expect 简写展开统一放入 Normalized 阶段, 运行时 AJV 使用 Normalized schema,导出 schema 面向 Authoring Config。 主要变更: - 新增 normalizer.ts 实现 normalizeAuthoringConfig() - 拆分 Authoring/Normalized 双 schema,checker 接口支持 authoring/normalized 片段 - config-loader 流程:normalize → Normalized AJV → semantic → resolve - validator 兼容层自动分派 raw/normalized expect 形态 - 删除 rawExpect,store.expect 列写入 null - Authoring schema 对 integer/boolean/enum 字段接受变量引用 - 修复 DB/HTTP validate 入口守卫和 LLM options integer 变量引用 - 优化 compact() 避免 undefined 覆盖隐患 - 移除 content.ts 恒为 true 的前置条件 - 同步 5 个主规范并归档 change
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: Checker 配置契约片段
|
||||
系统 SHALL 支持 checker 提供自身 TypeBox 配置契约片段,用于描述该 checker 的 target 领域分组和 expect 分组。公共配置加载模块 SHALL 通过 registry 获取已注册 checker 的契约片段,并组合为启动期 Ajv 契约校验流程和外部 `probe-config.schema.json` 导出流程。
|
||||
系统 SHALL 支持 checker 提供自身 TypeBox 配置契约片段,用于描述该 checker 的 target 领域分组和 expect 分组。checker 契约 SHALL 区分 Authoring schema 与 Normalized schema。Authoring schema SHALL 描述用户 YAML 可书写形式,包括变量引用和 expect 简写;Normalized schema SHALL 描述 `normalizeAuthoringConfig()` 输出形式,不接受变量引用、不接受 expect primitive 简写。公共配置加载模块 SHALL 通过 registry 获取已注册 checker 的契约片段,并按用途组合为运行时 Ajv 契约校验流程和外部 `probe-config.schema.json` 导出流程。
|
||||
|
||||
#### Scenario: HTTP checker 提供契约片段
|
||||
- **WHEN** HTTP checker 被注册
|
||||
@@ -21,7 +21,11 @@
|
||||
|
||||
#### Scenario: 外部 schema 通过 registry 生成
|
||||
- **WHEN** 系统生成 `probe-config.schema.json`
|
||||
- **THEN** 生成流程 SHALL 从 registry 获取已注册 checker 的契约片段,并将其组合进完整配置 schema
|
||||
- **THEN** 生成流程 SHALL 从 registry 获取已注册 checker 的 Authoring 契约片段,并将其组合进完整配置 schema
|
||||
|
||||
#### Scenario: 运行时 schema 通过 registry 生成
|
||||
- **WHEN** config-loader 执行运行时 AJV 契约校验
|
||||
- **THEN** 校验流程 SHALL 从 registry 获取已注册 checker 的 Normalized 契约片段,并将其组合进完整配置 schema
|
||||
|
||||
#### Scenario: 契约组装不依赖全局 singleton
|
||||
- **WHEN** 测试或 schema 生成流程需要组装配置契约
|
||||
@@ -50,11 +54,11 @@
|
||||
- **THEN** checker SHALL 返回 `ConfigValidationIssue`,而不是直接抛出最终用户错误字符串
|
||||
|
||||
### Requirement: Checker 接口定义
|
||||
系统 SHALL 在 `src/server/checker/runner/types.ts` 中定义面向扩展的泛型 `CheckerDefinition<TResolved extends ResolvedTargetBase = ResolvedTargetBase>`,包含 `type`、`configKey`、TypeBox 配置契约、启动期语义校验、`resolve`、`execute`、`serialize`、`buildDetail` 成员。泛型参数 SHALL 约束 `execute` 和 `serialize` 方法的 target 参数类型,使 checker 实现内部获得编译期类型安全。默认泛型参数 `= ResolvedTargetBase` 保证中间层(registry、engine、config-loader)无需指定泛型。
|
||||
系统 SHALL 在 `src/server/checker/runner/types.ts` 中定义面向扩展的泛型 `CheckerDefinition<TResolved extends ResolvedTargetBase = ResolvedTargetBase>`,包含 `type`、`configKey`、Authoring/Normalized TypeBox 配置契约、启动期语义校验、`resolve`、`execute`、`serialize`、`buildDetail` 成员。泛型参数 SHALL 约束 `execute` 和 `serialize` 方法的 target 参数类型,使 checker 实现内部获得编译期类型安全。默认泛型参数 `= ResolvedTargetBase` 保证中间层(registry、engine、config-loader)无需指定泛型。
|
||||
|
||||
#### Scenario: Checker 接口包含必要方法
|
||||
- **WHEN** 开发者实现一个新的 Checker
|
||||
- **THEN** 该实现 MUST 提供 `type`(字符串标识)、`configKey`(配置分组名)、TypeBox 配置契约、启动期语义校验、`resolve(target, context)`(解析配置并填充默认值)、`execute(target, ctx)`(执行探测返回 CheckResult)、`serialize(target)`(返回 target 展示文本和 config JSON)和 `buildDetail(observation)`(从 observation 构造人可读摘要)
|
||||
- **THEN** 该实现 MUST 提供 `type`(字符串标识)、`configKey`(配置分组名)、Authoring/Normalized TypeBox 配置契约、启动期语义校验、`resolve(target, context)`(解析配置并填充默认值)、`execute(target, ctx)`(执行探测返回 CheckResult)、`serialize(target)`(返回 target 展示文本和 config JSON)和 `buildDetail(observation)`(从 observation 构造人可读摘要)
|
||||
|
||||
#### Scenario: CheckerContext 注入 signal
|
||||
- **WHEN** 引擎调用 `checker.execute(target, ctx)`
|
||||
@@ -62,7 +66,11 @@
|
||||
|
||||
#### Scenario: resolve 不承担通用契约校验
|
||||
- **WHEN** config-loader 调用 checker.resolve()
|
||||
- **THEN** checker.resolve() SHALL 假定配置已经通过 TypeBox/Ajv 契约校验和启动期语义校验,只负责默认值填充、路径解析和领域配置转换
|
||||
- **THEN** checker.resolve() SHALL 假定配置已经通过 Normalized TypeBox/Ajv 契约校验和启动期语义校验,只负责默认值填充、路径解析和领域配置转换
|
||||
|
||||
#### Scenario: resolve 接收 Normalized target
|
||||
- **WHEN** config-loader 调用 checker.resolve()
|
||||
- **THEN** 传入的 target SHALL 已通过 Normalized schema 和语义校验,且不包含变量引用、Authoring expect primitive 简写或 Raw content/keyed DSL
|
||||
|
||||
#### Scenario: type 与 configKey 默认一致
|
||||
- **WHEN** checker 定义 `type: "tcp"`
|
||||
@@ -125,6 +133,14 @@
|
||||
- **WHEN** config-loader 校验配置文件
|
||||
- **THEN** config-loader SHALL 从 `checkerRegistry` 获取已注册 checker 的契约片段,并用于校验 defaults 与 targets 中对应 checker 的配置形状
|
||||
|
||||
#### Scenario: Authoring 契约通过 registry 组合
|
||||
- **WHEN** 系统导出用户配置 JSON Schema
|
||||
- **THEN** 配置 builder SHALL 从 `checkerRegistry` 获取已注册 checker 的 Authoring 契约片段
|
||||
|
||||
#### Scenario: Normalized 契约通过 registry 组合
|
||||
- **WHEN** config-loader 校验 normalized 配置对象
|
||||
- **THEN** config-loader SHALL 从 `checkerRegistry` 获取已注册 checker 的 Normalized 契约片段
|
||||
|
||||
#### Scenario: 配置解析委托 checker
|
||||
- **WHEN** config-loader 解析一个 type 为 "cmd" 的 target
|
||||
- **THEN** config-loader SHALL 调用 `checkerRegistry.get("cmd")` 获取对应 checker,并委托该 checker 执行语义校验和 resolve
|
||||
@@ -146,22 +162,22 @@
|
||||
- **THEN** HttpChecker 的语义校验 SHALL 抛出校验错误,提示 URL 格式不合法
|
||||
|
||||
### Requirement: 存储序列化通过 registry 获取展示格式
|
||||
系统 SHALL 在 `ProbeStore.syncTargets()` 中通过 `checkerRegistry.get(t.type).serialize(t)` 获取每个 target 的展示摘要(`target` 列)和配置 JSON(`config` 列),替代 `buildTargetDisplay()` / `buildTargetConfig()` 中的类型分支。系统 SHALL 将 `targets.expect` 持久化为 resolved target 上的 Raw expect 快照,而不是运行期 Resolved expect 执行计划。
|
||||
系统 SHALL 在 `ProbeStore.syncTargets()` 中通过 `checkerRegistry.get(t.type).serialize(t)` 获取每个 target 的展示摘要(`target` 列)和配置 JSON(`config` 列),替代 `buildTargetDisplay()` / `buildTargetConfig()` 中的类型分支。系统 SHALL 将 `targets.expect` 持久化为 null,不依赖 Raw expect 或 Resolved expect。
|
||||
|
||||
#### Scenario: 序列化委托 checker
|
||||
- **WHEN** store 同步 targets 表
|
||||
- **THEN** store SHALL 对每个 target 调用对应 checker 的 `serialize()` 方法获取 `{ target, config }`
|
||||
|
||||
#### Scenario: expect 持久化使用 rawExpect
|
||||
#### Scenario: expect 持久化不依赖 rawExpect
|
||||
- **WHEN** store 同步带 expect 的 target 到 targets 表
|
||||
- **THEN** store SHALL 将 `rawExpect` 序列化写入 `targets.expect`,MUST NOT 将包含 `kind` 的 Resolved content expectation 写入该列
|
||||
- **THEN** store SHALL 将 `targets.expect` 写入 NULL,MUST NOT 依赖 `rawExpect` 或 Raw expect 快照
|
||||
|
||||
### Requirement: Checker resolve 生成 Raw 与 Resolved expect
|
||||
每个 checker 的 `resolve()` SHALL 在解析 checker 专属 target 配置时,同时保留变量替换后的 Raw expect 快照并生成运行期 Resolved expect 执行计划。Raw expect SHALL 用于配置快照持久化;Resolved expect SHALL 用于 checker `execute()`。`config-loader` SHALL 继续通过 registry 委托 checker resolve,MUST NOT 在中间层理解 checker 专属 expect 字段。
|
||||
### Requirement: Checker resolve 只接收已去糖配置
|
||||
每个 checker 的 `resolve()` SHALL 接收已通过 Normalized schema 和语义校验的配置,不再包含变量引用、Authoring expect primitive 简写或 Raw content/keyed DSL。`config-loader` SHALL 继续通过 registry 委托 checker resolve,MUST NOT 在中间层理解 checker 专属 expect 字段。
|
||||
|
||||
#### Scenario: resolve 输出双 expect 模型
|
||||
- **WHEN** config-loader 解析一个带 `expect.durationMs: 1000` 的 target
|
||||
- **THEN** 对应 checker 的 resolved target SHALL 包含 Raw expect 中的 `durationMs: 1000`,并在 Resolved expect 中包含 `{equals: 1000}` 形式的运行期 matcher
|
||||
#### Scenario: resolve 不再展开 Raw expect
|
||||
- **WHEN** config-loader 解析一个带 `expect.durationMs: {equals: 1000}` 的 target
|
||||
- **THEN** 对应 checker 的 resolved target SHALL 直接使用 Normalized expect 中的 `{equals: 1000}`,resolve 只负责默认值和运行期配置转换
|
||||
|
||||
#### Scenario: 中间层不感知 checker expect 字段
|
||||
- **WHEN** 新增 checker 定义自己的 Raw/Resolved expect 字段
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
- **THEN** 系统 SHALL 将该字段替换为 string 类型的空字符串 `""`,MUST NOT 推断为 number 0
|
||||
|
||||
### Requirement: 替换范围限制
|
||||
变量替换 SHALL 作用于 `server`、`probes` 和 `targets` 段中的字符串值。`variables` 段自身 MUST NOT 参与变量替换。系统 SHALL 递归遍历支持范围内对象树中所有字符串 value 进行替换,包括嵌套对象和数组元素中的字符串。系统 MUST NOT 替换对象 key。`targets[].id` 和 `targets[].type` 字段 MUST NOT 参与变量替换;target 内部其他路径上名为 `id` 或 `type` 的字段 SHALL 正常参与变量替换。顶层 `defaults` 不再是合法配置段,因此不属于变量替换范围。
|
||||
变量替换 SHALL 作用于 Authoring Config 的 `server`、`probes` 和 `targets` 段中的字符串值。`variables` 段自身 MUST NOT 参与变量替换。系统 SHALL 递归遍历支持范围内对象树中所有字符串 value 进行替换,包括嵌套对象和数组元素中的字符串。系统 MUST NOT 替换对象 key。`targets[].id` 和 `targets[].type` 字段 MUST NOT 参与变量替换;target 内部其他路径上名为 `id` 或 `type` 的字段 SHALL 正常参与变量替换。顶层 `defaults` 不再是合法配置段,因此不属于变量替换范围。变量替换完成后,Normalized Config MUST NOT 保留顶层 `variables` 段。
|
||||
|
||||
#### Scenario: target 嵌套对象中的变量替换
|
||||
- **WHEN** target 配置 `http.headers.Authorization: "${token}"` 且 variables 中定义 `token: "Bearer abc"`
|
||||
@@ -179,6 +179,10 @@
|
||||
- **WHEN** 配置文件声明顶层 `defaults`
|
||||
- **THEN** 系统 SHALL 在契约校验阶段拒绝该未知字段,而不是尝试变量替换
|
||||
|
||||
#### Scenario: Normalized Config 移除 variables 段
|
||||
- **WHEN** Authoring Config 包含顶层 `variables` 段且变量替换成功
|
||||
- **THEN** Normalized Config SHALL 不包含顶层 `variables` 字段
|
||||
|
||||
### Requirement: 变量替换错误报告
|
||||
变量替换阶段的错误 SHALL 作为 `ConfigValidationIssue` 输出,code 为 `unresolved-variable`。错误信息 SHALL 包含字段路径和变量名。对于 `targets[i]` 内的错误,错误信息还 SHALL 包含 target 索引、target id 和 target 展示名上下文。
|
||||
|
||||
@@ -199,7 +203,7 @@
|
||||
- **THEN** 系统 SHALL 收集所有缺失变量错误后统一输出,而非遇到第一个就退出
|
||||
|
||||
### Requirement: 变量替换执行时机
|
||||
变量替换 SHALL 在 YAML 解析之后、schema 契约校验(AJV)之前执行。替换完成后的配置对象 SHALL 传入后续校验流程。
|
||||
变量替换 SHALL 在 YAML 解析之后、Normalized schema 契约校验(AJV)之前执行。变量替换 SHALL 是 `normalizeAuthoringConfig()` 的一部分,替换完成后的配置对象 SHALL 继续执行 expect 简写展开并形成 Normalized Config。Normalized Config SHALL 传入后续契约校验和语义校验流程。
|
||||
|
||||
#### Scenario: target 替换后通过 schema 校验
|
||||
- **WHEN** target 配置 `http.maxRedirects: "${MAX_REDIRECTS}"` 且环境变量 `MAX_REDIRECTS=5`
|
||||
@@ -215,4 +219,8 @@
|
||||
|
||||
#### Scenario: probes 替换后通过 schema 校验
|
||||
- **WHEN** probes 配置 `execution.maxConcurrentChecks: "${MAX_CHECKS}"` 且 variables 中定义 `MAX_CHECKS: 20`
|
||||
- **THEN** 系统 SHALL 先将该字段替换为 number 20,再进入 AJV 校验(期望 integer),校验通过
|
||||
- **THEN** 系统 SHALL 先将 probes.execution.maxConcurrentChecks 替换为 number 20,再进入 Normalized schema 校验(期望 integer),校验通过
|
||||
|
||||
#### Scenario: 变量替换后继续展开 expect 简写
|
||||
- **WHEN** Authoring Config 配置 `expect.durationMs: "${MAX_MS}"` 且 variables 中定义 `MAX_MS: 1000`
|
||||
- **THEN** Normalized Config SHALL 包含 `expect.durationMs: { equals: 1000 }`
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
### 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 均通过。
|
||||
|
||||
所有类型为 `RawValueExpectation` 的 expect 字段 SHALL 同时接受 primitive 原始值(string / number / boolean / null)作为简写形式。原始值简写 SHALL 等价于 `{ equals: value }`。系统 SHALL 在 resolve 阶段将 primitive 原始值归一化为 `{ equals: value }` 对象形式,运行期逻辑 SHALL 仅处理 `ValueMatcher` 对象形式。数组和对象 MUST NOT 作为原始值简写;需要对数组或对象执行 equals 匹配时,配置 MUST 显式写成 `{ equals: value }`。
|
||||
所有类型为 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}}`
|
||||
@@ -25,28 +25,28 @@
|
||||
- **WHEN** 实际值为 `"healthy"` 且 matcher 为 `{contains: "health", regex: "^ready$"}`
|
||||
- **THEN** 系统 SHALL 在任一 matcher 不满足时判定该 matcher 对象不通过
|
||||
|
||||
#### Scenario: 字符串原始值简写等价 equals
|
||||
- **WHEN** expect 字段配置为 `finishReason: "stop"` 且实际值为 `"stop"`
|
||||
- **THEN** 系统 SHALL 在 resolve 阶段将 `"stop"` 归一化为 `{equals: "stop"}` 并判定通过
|
||||
#### Scenario: 字符串原始值简写在 Normalized 阶段等价 equals
|
||||
- **WHEN** Authoring Config 中 expect 字段配置为 `finishReason: "stop"`
|
||||
- **THEN** Normalized Config SHALL 将 `"stop"` 归一化为 `{equals: "stop"}`,运行期实际值为 `"stop"` 时判定通过
|
||||
|
||||
#### Scenario: 数字原始值简写等价 equals
|
||||
- **WHEN** expect 字段配置为 `rowCount: 1` 且实际值为 `1`
|
||||
- **THEN** 系统 SHALL 在 resolve 阶段将 `1` 归一化为 `{equals: 1}` 并判定通过
|
||||
#### Scenario: 数字原始值简写在 Normalized 阶段等价 equals
|
||||
- **WHEN** Authoring Config 中 expect 字段配置为 `rowCount: 1`
|
||||
- **THEN** Normalized Config SHALL 将 `1` 归一化为 `{equals: 1}`,运行期实际值为 `1` 时判定通过
|
||||
|
||||
#### Scenario: 布尔原始值简写等价 equals
|
||||
- **WHEN** expect 字段配置为 RawValueExpectation 类型且值为 `true`,实际值为 `true`
|
||||
- **THEN** 系统 SHALL 在 resolve 阶段将 `true` 归一化为 `{equals: true}` 并判定通过
|
||||
#### Scenario: 布尔原始值简写在 Normalized 阶段等价 equals
|
||||
- **WHEN** Authoring Config 中 RawValueExpectation 类型字段值为 `true`
|
||||
- **THEN** Normalized Config SHALL 将 `true` 归一化为 `{equals: true}`,运行期实际值为 `true` 时判定通过
|
||||
|
||||
#### Scenario: null 原始值简写等价 equals
|
||||
- **WHEN** expect 字段配置为 RawValueExpectation 类型且值为 `null`,实际值为 `null`
|
||||
- **THEN** 系统 SHALL 在 resolve 阶段将 `null` 归一化为 `{equals: null}` 并判定通过
|
||||
#### 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 在启动期对所有 `RawValueExpectation` 字段执行严格的类型和语义校验。校验 SHALL 同时接受 primitive 原始值和 `ValueMatcher` 对象两种形式,但 MUST NOT 修改输入对象。当输入为 primitive 原始值时,系统 SHALL 视为合法配置(等价于 `{equals: value}`),无需进一步校验 matcher 字段。当输入为 `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 导致启动期配置错误。
|
||||
系统 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 对象为空 `{}`
|
||||
@@ -64,17 +64,17 @@
|
||||
- **WHEN** YAML 配置中任一 matcher 的 `exists` 或 `empty` 值不是布尔类型
|
||||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示该字段必须为布尔值
|
||||
|
||||
#### Scenario: 字符串原始值校验通过
|
||||
- **WHEN** YAML 配置中 RawValueExpectation 字段值为字符串 `"stop"`
|
||||
- **THEN** 系统 SHALL 接受该配置,但 SHALL NOT 在语义校验阶段修改输入对象
|
||||
#### Scenario: 字符串原始值在 Normalized schema 中被拒绝
|
||||
- **WHEN** Normalized schema 校验 RawValueExpectation 字段值为字符串 `"stop"`
|
||||
- **THEN** schema 校验 SHALL 失败,因为 Normalized Config 只接受 `ValueMatcher` 对象
|
||||
|
||||
#### Scenario: 数字原始值校验通过
|
||||
- **WHEN** YAML 配置中 RawValueExpectation 字段值为数字 `5000`
|
||||
- **THEN** 系统 SHALL 接受该配置,但 SHALL NOT 在语义校验阶段修改输入对象
|
||||
#### Scenario: 数字原始值在 Normalized schema 中被拒绝
|
||||
- **WHEN** Normalized schema 校验 RawValueExpectation 字段值为数字 `5000`
|
||||
- **THEN** schema 校验 SHALL 失败,因为 Normalized Config 只接受 `ValueMatcher` 对象
|
||||
|
||||
#### Scenario: null 原始值校验通过
|
||||
- **WHEN** YAML 配置中 RawValueExpectation 字段值为 `null`
|
||||
- **THEN** 系统 SHALL 接受该配置,但 SHALL NOT 在语义校验阶段修改输入对象
|
||||
#### Scenario: null 原始值在 Normalized schema 中被拒绝
|
||||
- **WHEN** Normalized schema 校验 RawValueExpectation 字段值为 `null`
|
||||
- **THEN** schema 校验 SHALL 失败,因为 Normalized Config 只接受 `ValueMatcher` 对象
|
||||
|
||||
#### Scenario: 数组原始值被拒绝
|
||||
- **WHEN** YAML 配置中 RawValueExpectation 字段值为数组 `[1, 2]`
|
||||
@@ -142,7 +142,7 @@
|
||||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示正则存在 ReDoS 风险
|
||||
|
||||
### Requirement: ContentExpectations 内容断言数组
|
||||
系统 SHALL 提供共享 `ContentExpectations` 表达返回内容断言。`ContentExpectations` MUST 为有序数组,数组项 SHALL 为直接 `ValueMatcher`,或 `json`、`css`、`xpath` 三类 extractor expectation 之一。系统 SHALL 在 resolve 阶段将 Raw content DSL 解析为带 `kind` 字段的 Resolved `ContentExpectation` 执行计划,并按数组顺序执行全部 expectation,任一 expectation 失败时 SHALL 立即停止并返回该 expectation 的 failure。系统 MUST NOT 支持内容字段的非数组对象快捷写法。
|
||||
系统 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
|
||||
@@ -156,9 +156,9 @@
|
||||
- **WHEN** YAML 中内容字段配置为 `{contains: "ok"}` 而不是数组
|
||||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示该内容字段必须为 expectation 数组
|
||||
|
||||
#### Scenario: Resolved content expectation 使用 kind
|
||||
- **WHEN** Raw 内容字段包含直接 matcher、json extractor、css extractor 和 xpath extractor
|
||||
- **THEN** resolve 阶段 SHALL 分别生成 `kind="value"`、`kind="json"`、`kind="css"` 和 `kind="xpath"` 的 Resolved `ContentExpectation`
|
||||
#### 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 报错。
|
||||
@@ -190,7 +190,7 @@
|
||||
- **THEN** 系统 SHALL 直接在结构化值上使用深度相等比较
|
||||
|
||||
### Requirement: ContentExpectations 提取器
|
||||
系统 SHALL 支持在 `ContentExpectations` 中使用 `json`、`css` 和 `xpath` extractor。`json.path` MUST 使用现有 JSONPath 子集。`css.selector` MUST 为非空字符串,并 MAY 配置 `attr` 提取属性值。`xpath.path` MUST 为非空字符串,并 SHALL 在启动期进行可编译校验。Extractor 内部 MAY 包含任意 `ValueMatcher` 字段。Extractor expectation 未配置任何 matcher 时,resolve 阶段 SHALL 将其 Resolved matcher 物化为 `{ exists: true }`。
|
||||
系统 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}}`
|
||||
@@ -198,22 +198,22 @@
|
||||
|
||||
#### Scenario: json extractor 存在性默认语义
|
||||
- **WHEN** 原始内容为 JSON 字符串 `{"user": {"id": null}}` 且 expectation 为 `{json: {path: "$.user.id"}}`
|
||||
- **THEN** resolve 阶段 SHALL 将该 expectation 的 matcher 物化为 `{exists: true}` 并在运行期判定通过
|
||||
- **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** resolve 阶段 SHALL 将该 expectation 的 matcher 物化为 `{exists: true}` 并在属性存在时判定通过
|
||||
- **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` 表达键值型观测值断言。Raw `KeyedExpectations` SHALL 为动态键对象,每个键对应的值 MUST 为 `RawValueExpectation`;数组和对象简写 MUST 被拒绝,数组或对象 equals 匹配 MUST 显式写为 `{equals: <value>}`。Resolved `KeyedExpectations` SHALL 为有序数组,每个元素包含原始 key 和已归一化的 `ValueExpectation` matcher。调用方 MAY 指定 key 规范化策略;HTTP 与 LLM headers MUST 使用大小写不敏感的 key 匹配,并 MUST 在启动期校验或 resolve 阶段拒绝归一化后重复的 header key。DB rows 不做 key 归一化,按查询结果列名大小写敏感匹配。
|
||||
系统 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** resolve 阶段 SHALL 将该项解析为 keyed expectation `{key: "Content-Type", matcher: {equals: "application/json"}}`,运行期按大小写不敏感 key 匹配并判定通过
|
||||
- **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"}}`
|
||||
|
||||
@@ -98,15 +98,11 @@
|
||||
- **THEN** 系统 SHALL 以错误退出并提示文件不存在
|
||||
|
||||
### Requirement: 配置校验
|
||||
系统 SHALL 在启动时对 YAML 配置进行完整校验,校验失败时以非零状态退出并输出清晰的错误信息。系统 SHALL 使用 TypeBox 定义配置契约和 raw config TypeScript 类型,由 Ajv 校验 TypeBox 生成的 JSON Schema,再执行启动期语义 validator。配置加载流程 SHALL 明确区分 `RawProbeConfig`、`ValidatedProbeConfig`、`ResolvedConfig` 三段生命周期,并在 YAML 解析之后、AJV 校验之前执行变量替换阶段。JSON Schema 契约 SHALL 覆盖业务无关的结构规则,包括字段类型、必填字段、枚举、数组与对象形状、数值范围和未知字段。语义 validator SHALL 覆盖契约不适合表达的业务规则,包括 target id 唯一性、id 命名规则校验、checker type 注册状态、时长和大小解析、HTTP URL、正则可编译、JSONPath 子集和 XPath 可编译。
|
||||
系统 SHALL 在启动时对 YAML 配置进行完整校验,校验失败时以非零状态退出并输出清晰的错误信息。系统 SHALL 使用 TypeBox 定义 Authoring Config 和 Normalized Config 的配置契约,并使用 Ajv 校验 TypeBox 生成的 JSON Schema。配置加载流程 SHALL 明确区分 `AuthoringProbeConfig`、`NormalizedProbeConfig`、`ValidatedProbeConfig`、`ResolvedConfig` 生命周期,并在 YAML 解析之后、Normalized schema 校验之前执行 `normalizeAuthoringConfig()`。该 normalizer SHALL 只负责去糖:变量替换、expect primitive/keyed/content 简写展开、移除 `variables` 段。该 normalizer MUST NOT 注入默认值、解析 duration/size/path/env,或合并 `cmd.env`。
|
||||
|
||||
契约校验和语义 validator SHALL 统一产出 `ConfigValidationIssue`,最终由配置加载流程统一渲染为中文错误信息。
|
||||
JSON Schema 契约 SHALL 覆盖业务无关的结构规则,包括字段类型、必填字段、枚举、数组与对象形状、数值范围和未知字段。Authoring schema SHALL 用于导出的 `probe-config.schema.json`,描述用户 YAML 可书写形式,包括变量引用和 expect 简写。Normalized schema SHALL 用于运行时 AJV 校验,描述 normalizer 输出结果,不接受变量引用、不接受 expect primitive 简写、不包含 `variables` 段。语义 validator SHALL 覆盖契约不适合表达的业务规则,包括 target id 唯一性、id 命名规则校验、checker type 注册状态、时长和大小解析、HTTP URL、正则可编译、JSONPath 子集和 XPath 可编译。
|
||||
|
||||
系统 SHALL 导出完整 `probe-config.schema.json`,该文件 SHALL 与运行期 TypeBox fragments 生成的 JSON Schema 保持一致,用于用户配置引用和编辑器提示。
|
||||
|
||||
除 `headers`、`env`、`variables` 等明确声明为动态键值表的对象外,配置中的未知字段 SHALL 导致启动期配置错误。系统 MUST NOT 静默忽略未知字段。
|
||||
|
||||
所有 `RawValueExpectation` 类型的 expect 字段 SHALL 在 JSON Schema 契约中声明为 `anyOf: [primitiveValue, matcherObject]` 联合类型,同时接受 primitive 原始值(string / number / boolean / null)和 `ValueMatcher` 对象。语义 validator SHALL 在不修改输入的前提下校验 `RawValueExpectation`;primitive 原始值 SHALL 被视为合法 Raw 输入,并在 checker resolve 阶段转换为 `{equals: value}` 对象形式。数组和对象 MUST NOT 作为 `RawValueExpectation` 原始值简写;需要对数组或对象执行 equals 匹配时,配置 MUST 显式写成 `{equals: value}`。`RawKeyedExpectations` 的动态键值 schema SHALL 复用 `RawValueExpectation`,MUST NOT 通过额外 JSON value 分支接受数组或对象简写。
|
||||
契约校验、normalizer 和语义 validator SHALL 统一产出 `ConfigValidationIssue`,最终由配置加载流程统一渲染为中文错误信息。除 `headers`、`env`、Authoring `variables` 等明确声明为动态键值表的对象外,配置中的未知字段 SHALL 导致启动期配置错误。系统 MUST NOT 静默忽略未知字段。
|
||||
|
||||
#### Scenario: target 缺少必填字段
|
||||
- **WHEN** YAML 中某个 target 缺少 id 或 type 字段
|
||||
@@ -145,7 +141,7 @@
|
||||
- **THEN** 系统 SHALL 以错误退出并提示格式错误
|
||||
|
||||
#### Scenario: maxConcurrentChecks 非法
|
||||
- **WHEN** probes.execution.maxConcurrentChecks 不是正整数
|
||||
- **WHEN** Normalized Config 中 probes.execution.maxConcurrentChecks 不是正整数
|
||||
- **THEN** 系统 SHALL 以错误退出并提示 probes.execution.maxConcurrentChecks 格式错误
|
||||
|
||||
#### Scenario: interval 或 timeout 解析结果非法
|
||||
@@ -161,7 +157,7 @@
|
||||
- **THEN** 系统 SHALL 以错误退出并提示必须为非负安全整数字节数
|
||||
|
||||
#### Scenario: HTTP method 非法
|
||||
- **WHEN** YAML 中某个 HTTP target 的 `http.method` 不是 GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 之一
|
||||
- **WHEN** Normalized Config 中某个 HTTP target 的 `http.method` 不是 GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 之一
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 method 不合法
|
||||
|
||||
#### Scenario: HTTP method 小写非法
|
||||
@@ -177,7 +173,7 @@
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 maxRedirects 必须为非负整数
|
||||
|
||||
#### Scenario: maxRedirects 非整数非法
|
||||
- **WHEN** YAML 中某个 HTTP target 的 `http.maxRedirects` 不是非负整数(如 `1.5` 或 `"5"`)
|
||||
- **WHEN** Normalized Config 中某个 HTTP target 的 `http.maxRedirects` 不是非负整数(如 `1.5` 或 `"5"`)
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 maxRedirects 必须为非负整数
|
||||
|
||||
#### Scenario: ignoreSSL 类型非法
|
||||
@@ -205,28 +201,28 @@
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 status 数字不合法
|
||||
|
||||
#### Scenario: durationMs matcher 非法
|
||||
- **WHEN** YAML 中某个 target 的 `expect.durationMs` 不是合法 `RawValueExpectation`
|
||||
- **WHEN** Normalized Config 中某个 target 的 `expect.durationMs` 不是合法 `ValueMatcher` 对象
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 expect.durationMs 格式错误
|
||||
|
||||
#### Scenario: durationMs 原始值简写合法
|
||||
- **WHEN** YAML 中某个 target 配置 `expect.durationMs: 5000`
|
||||
- **THEN** 语义校验 SHALL 接受该 Raw primitive 简写且 MUST NOT 修改输入;checker resolve 阶段 SHALL 将其解析为 `{equals: 5000}`
|
||||
#### Scenario: durationMs 原始值简写在 Authoring schema 合法
|
||||
- **WHEN** 使用 Authoring schema 校验配置文件中 `expect.durationMs: 5000`
|
||||
- **THEN** JSON Schema 校验 SHALL 通过,因为 Authoring schema 接受 primitive 简写
|
||||
|
||||
#### Scenario: ValueMatcher 字段字符串简写合法
|
||||
- **WHEN** YAML 中某个 target 配置 `expect.finishReason: "stop"`
|
||||
- **THEN** 语义校验 SHALL 接受该 Raw primitive 简写且 MUST NOT 修改输入;checker resolve 阶段 SHALL 将其解析为 `{equals: "stop"}`
|
||||
#### Scenario: durationMs 原始值简写在 Normalized schema 非法
|
||||
- **WHEN** 使用 Normalized schema 校验配置对象中 `expect.durationMs: 5000`
|
||||
- **THEN** JSON Schema 校验 SHALL 失败,因为 Normalized schema 只接受 `ValueMatcher` 对象
|
||||
|
||||
#### Scenario: ValueMatcher 字段 null 简写合法
|
||||
- **WHEN** YAML 中某个 target 配置 ValueMatcher 字段值为 `null`
|
||||
- **THEN** 语义校验 SHALL 接受该 Raw primitive 简写且 MUST NOT 修改输入;checker resolve 阶段 SHALL 将其解析为 `{equals: null}`
|
||||
#### Scenario: 变量引用在 Authoring schema 合法
|
||||
- **WHEN** 使用 Authoring schema 校验配置文件中 `server.listen.port: "${PORT|3000}"`
|
||||
- **THEN** JSON Schema 校验 SHALL 通过,因为 Authoring schema 面向用户可书写 YAML
|
||||
|
||||
#### Scenario: ValueMatcher 字段数组简写非法
|
||||
- **WHEN** YAML 中某个 target 配置 ValueMatcher 字段值为数组 `[1, 2]`
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该字段必须为 primitive 原始值或 matcher 对象;如需数组 equals 匹配应写成 `{equals: [1, 2]}`
|
||||
#### Scenario: 变量引用在 Normalized schema 非法
|
||||
- **WHEN** 使用 Normalized schema 校验配置对象中 `server.listen.port: "${PORT|3000}"`
|
||||
- **THEN** JSON Schema 校验 SHALL 失败,因为 Normalized schema 只接受变量替换后的 integer
|
||||
|
||||
#### Scenario: ValueMatcher 字段对象简写非法
|
||||
- **WHEN** YAML 中某个 target 配置 ValueMatcher 字段值为对象 `{foo: "bar"}`,且 `foo` 不是合法 matcher 字段
|
||||
- **THEN** 系统 SHALL 以错误退出,提示 `foo` 是未知 matcher;如需对象 equals 匹配应写成 `{equals: {foo: "bar"}}`
|
||||
#### Scenario: Authoring schema 对 integer/boolean/enum 字段接受变量引用
|
||||
- **WHEN** 使用 Authoring schema 校验配置文件中 `http.maxRedirects: "${MAX_REDIRECTS|5}"` 或 `http.ignoreSSL: "${IGNORE_SSL|false}"` 或 `llm.provider: "${PROVIDER|openai}"`
|
||||
- **THEN** JSON Schema 校验 SHALL 通过,因为 Authoring schema 对支持变量替换的 integer/boolean/enum/pattern-string 字段使用 `anyOf: [originalType, {type: "string", pattern: "^\\$\\{[^}]+\\}$"}]` 额外接受完整变量引用字符串
|
||||
|
||||
#### Scenario: icmp target 缺少 host
|
||||
- **WHEN** YAML 中某个 target 配置 `type: icmp` 但缺少 `icmp.host`
|
||||
@@ -292,37 +288,25 @@
|
||||
- **WHEN** 系统执行 JSON Schema 契约校验
|
||||
- **THEN** 系统 MUST NOT 通过契约校验器强制转换类型、注入默认值或删除未知字段
|
||||
|
||||
#### Scenario: 变量替换后字段超长由 Normalized schema 的 maxLength 校验拦截
|
||||
- **WHEN** Authoring Config 中 target 的 `description` 通过 `${...}` 变量替换后超过 500 个字符
|
||||
- **THEN** Normalized schema SHALL 在 AJV 校验阶段以错误退出,提示 description 字段长度错误
|
||||
|
||||
#### Scenario: 配置生命周期分离
|
||||
- **WHEN** 系统加载配置文件
|
||||
- **THEN** 系统 SHALL 按 `unknown -> 变量替换 -> RawProbeConfig -> ValidatedProbeConfig -> ResolvedConfig` 的顺序执行变量替换、契约校验、语义校验和运行期配置解析
|
||||
- **THEN** 系统 SHALL 按 `unknown -> AuthoringProbeConfig -> NormalizedProbeConfig -> ValidatedProbeConfig -> ResolvedConfig` 的顺序执行 YAML 解析、配置去糖、契约校验、语义校验和运行期配置解析
|
||||
|
||||
#### Scenario: Normalized 不补默认值
|
||||
- **WHEN** Authoring Config 中 HTTP target 未配置 `http.method` 和 `expect.status`
|
||||
- **THEN** Normalized Config SHALL 仍不包含这些默认值,checker.resolve() SHALL 在 ResolvedConfig 阶段物化默认 method 和 status 语义
|
||||
|
||||
#### Scenario: 结构化校验 issue
|
||||
- **WHEN** 契约校验、语义 validator 或变量替换阶段发现非法配置
|
||||
- **WHEN** 契约校验、normalizer、语义 validator 或变量替换阶段发现非法配置
|
||||
- **THEN** 系统 SHALL 先生成包含 code、path、message 和可选 targetName 的结构化 `ConfigValidationIssue`,再统一渲染为中文错误信息
|
||||
|
||||
#### Scenario: 导出配置 JSON Schema
|
||||
- **WHEN** 仓库生成或检查配置契约
|
||||
- **THEN** 根目录 SHALL 存在 draft-07 `probe-config.schema.json`,且其内容 SHALL 与当前公共 fragments 和已注册 checker fragments 组装出的完整 schema 一致(包含 variables 段和 target 的 id/name 字段,且不包含顶层 defaults)。所有 `RawValueExpectation` 字段的 schema SHALL 声明为 `anyOf: [primitiveValue, matcherObject]` 联合类型,`RawKeyedExpectations` 的 dynamic value schema SHALL 复用 `RawValueExpectation`
|
||||
|
||||
#### Scenario: JSON Schema RawValueExpectation 接受原始值
|
||||
- **WHEN** 使用 JSON Schema 校验配置文件中 RawValueExpectation 字段值为数字 `5000`
|
||||
- **THEN** JSON Schema 校验 SHALL 通过,因为 RawValueExpectation schema 声明为 `anyOf: [primitiveValue, matcherObject]`
|
||||
|
||||
#### Scenario: JSON Schema RawValueExpectation 接受 matcher 对象
|
||||
- **WHEN** 使用 JSON Schema 校验配置文件中 RawValueExpectation 字段值为 `{lte: 5000}`
|
||||
- **THEN** JSON Schema 校验 SHALL 通过
|
||||
|
||||
#### Scenario: JSON Schema RawValueExpectation 拒绝数组原始值
|
||||
- **WHEN** 使用 JSON Schema 校验配置文件中 RawValueExpectation 字段值为数组 `[1, 2]`
|
||||
- **THEN** JSON Schema 校验 SHALL 失败,因为数组不属于 primitive 原始值或 matcher 对象
|
||||
|
||||
#### Scenario: JSON Schema RawValueExpectation 接受 equals 数组对象
|
||||
- **WHEN** 使用 JSON Schema 校验配置文件中 RawValueExpectation 字段值为 `{equals: [1, 2]}` 或 `{equals: {status: "ok"}}`
|
||||
- **THEN** JSON Schema 校验 SHALL 通过,因为 `equals` 支持任意 JSON value
|
||||
|
||||
#### Scenario: JSON Schema RawKeyedExpectations 拒绝数组对象简写
|
||||
- **WHEN** 使用 JSON Schema 校验配置文件中 `expect.headers.X` 或 DB row 列值为数组 `["a"]` 或对象 `{nested: "ok"}`
|
||||
- **THEN** JSON Schema 校验 SHALL 失败,除非该值显式写在 `{equals: ...}` 下
|
||||
- **THEN** 根目录 SHALL 存在 draft-07 `probe-config.schema.json`,且其内容 SHALL 与当前 Authoring fragments 和已注册 checker Authoring fragments 组装出的完整 schema 一致(包含 variables 段和 target 的 id/name/description 字段,且不包含顶层 defaults)
|
||||
系统 SHALL 支持使用单位字符串配置读取上限,单位包括 `B`、`KB`、`MB` 和 `GB`。
|
||||
|
||||
#### Scenario: 解析 MB
|
||||
@@ -352,21 +336,21 @@
|
||||
- **THEN** 系统 SHALL 调用 `Bun.YAML.parse()` 将内容解析为配置对象
|
||||
|
||||
### Requirement: expect 配置增强
|
||||
系统 SHALL 支持 typed target 的领域专用 expect 配置,并通过共享 `ValueMatcher`、`ContentExpectations` 和 `KeyedExpectations` 表达可复用断言能力。状态类字段 SHALL 保持枚举或布尔语义,包括 HTTP/LLM 的 `status`(支持精确数字和范围模式)、cmd 的 `exitCode`、tcp 的 `connected`、icmp 的 `alive` 和 udp 的 `responded`。value 类指标字段 SHALL 使用 `RawValueExpectation` 输入,并在 resolve 阶段归一化为运行期 `ValueExpectation`,包括通用 `durationMs`、db 的 `rowCount`、udp 的 `responseSize`/`sourceHost`/`sourcePort`、icmp 的 `packetLossPercent`/`avgLatencyMs`/`maxLatencyMs`、llm 的 usage token 与 stream 首 token 耗时。内容类字段 MUST 使用 `RawContentExpectations` 数组表达配置顺序,包括 HTTP `body`、cmd `stdout`/`stderr`、tcp `banner`、udp `response`、llm `output` 和 db `result`。LLM `finishReason` 和 `rawFinishReason` SHALL 使用 `RawValueExpectation`(非 ContentExpectations),因为它们是单值字符串元数据。键值类字段 SHALL 使用 `RawKeyedExpectations`,包括 HTTP/LLM `headers` 和 db `rows` 中的列值断言(db `rows` 的类型为 `Array<RawKeyedExpectations>`,外层数组按行索引,内层每个元素表达该行的列值断言)。
|
||||
系统 SHALL 支持 typed target 的领域专用 expect 配置,并通过共享 `ValueMatcher`、`ContentExpectations` 和 `KeyedExpectations` 表达可复用断言能力。状态类字段 SHALL 保持枚举或布尔语义,包括 HTTP/LLM 的 `status`(支持精确数字和范围模式)、cmd 的 `exitCode`、tcp 的 `connected`、icmp 的 `alive` 和 udp 的 `responded`。Authoring value 类指标字段 SHALL 使用 `RawValueExpectation` 输入,并在 Normalized 阶段归一化为运行期 `ValueExpectation`,包括通用 `durationMs`、db 的 `rowCount`、udp 的 `responseSize`/`sourceHost`/`sourcePort`、icmp 的 `packetLossPercent`/`avgLatencyMs`/`maxLatencyMs`、llm 的 usage token 与 stream 首 token 耗时。Authoring 内容类字段 MUST 使用 `RawContentExpectations` 数组表达配置顺序,并在 Normalized 阶段转换为带 `kind` 的 `ContentExpectation` 数组,包括 HTTP `body`、cmd `stdout`/`stderr`、tcp `banner`、udp `response`、llm `output` 和 db `result`。Authoring 键值类字段 SHALL 使用动态对象,并在 Normalized 阶段转换为 `KeyedExpectations` 数组,包括 HTTP/LLM `headers` 和 db `rows` 中的列值断言。
|
||||
|
||||
配置加载流程 SHALL 保留变量替换后的 Raw expect 作为用户配置快照,同时生成 Resolved expect 作为运行期执行计划。语义校验 SHALL 只读取 Raw expect 并报告问题,MUST NOT 原地归一化或修改 Raw expect。Store 持久化 SHALL 写入 Raw expect;checker execute SHALL 只消费 Resolved expect。
|
||||
配置加载流程 MUST NOT 保留变量替换后的 Raw expect 作为执行路径依赖。语义校验 SHALL 读取 Normalized expect 并报告问题。Store 持久化 MUST NOT 依赖 Raw expect;checker execute SHALL 只消费 Resolved expect。
|
||||
|
||||
#### Scenario: 解析 HTTP expect 配置
|
||||
- **WHEN** YAML 配置文件中 HTTP target 的 expect 包含 status、headers、body expectation 数组和 durationMs matcher
|
||||
- **THEN** 系统 SHALL 保留 Raw HTTP expect 快照,并生成包含默认 status、resolved keyed headers、resolved content body 和 resolved durationMs 的 HTTP Resolved expect
|
||||
- **THEN** Normalized Config SHALL 包含 normalized keyed headers、normalized content body 和 normalized durationMs,checker.resolve() SHALL 生成包含默认 status 的 HTTP Resolved expect
|
||||
|
||||
#### Scenario: 解析 cmd expect 配置
|
||||
- **WHEN** YAML 配置文件中 cmd target 的 expect 包含 exitCode、stdout、stderr 和 durationMs matcher
|
||||
- **THEN** 系统 SHALL 保留 Raw cmd expect 快照,并生成包含默认 exitCode、resolved stdout/stderr content expectations 和 resolved durationMs 的 cmd Resolved expect
|
||||
- **THEN** Normalized Config SHALL 包含 normalized stdout/stderr content expectations 和 normalized durationMs,checker.resolve() SHALL 生成包含默认 exitCode 的 cmd Resolved expect
|
||||
|
||||
#### Scenario: 解析 db expect 配置
|
||||
- **WHEN** YAML 配置文件中 db target 的 expect 包含 durationMs、rowCount、rows 和 result
|
||||
- **THEN** 系统 SHALL 保留 Raw db expect 快照,并生成包含 resolved rowCount、rows keyed expectations 和 result content expectations 的 db Resolved expect
|
||||
- **THEN** Normalized Config SHALL 包含 normalized rowCount、rows keyed expectations 和 result content expectations
|
||||
|
||||
#### Scenario: 解析 tcp expect 配置
|
||||
- **WHEN** YAML 配置文件中 tcp target 的 expect 包含 connected、banner expectation 数组和 durationMs matcher
|
||||
@@ -386,11 +370,11 @@
|
||||
|
||||
#### Scenario: 解析有序 ContentExpectations 数组
|
||||
- **WHEN** YAML 中任一内容类 expect 配置 contains、json、regex 三个数组项
|
||||
- **THEN** 系统 SHALL 在 Raw expect 中保留数组顺序,并在 Resolved expect 中保留执行顺序,供执行阶段按配置顺序快速失败
|
||||
- **THEN** 系统 SHALL 在 Normalized expect 中保留执行顺序,供执行阶段按配置顺序快速失败
|
||||
|
||||
#### Scenario: 不配置 HTTP status
|
||||
- **WHEN** HTTP target 未配置 `expect.status`
|
||||
- **THEN** 系统 SHALL 在 Resolved HTTP expect 中物化默认 `status: [200]` 语义
|
||||
- **THEN** Normalized Config SHALL 不注入 status,checker.resolve() SHALL 在 Resolved HTTP expect 中物化默认 `status: [200]` 语义
|
||||
|
||||
#### Scenario: 配置 HTTP status 范围模式
|
||||
- **WHEN** HTTP target 配置 `expect.status: ["2xx"]`
|
||||
@@ -398,15 +382,15 @@
|
||||
|
||||
#### Scenario: 不配置 cmd exitCode
|
||||
- **WHEN** cmd target 未配置 `expect.exitCode`
|
||||
- **THEN** 系统 SHALL 在 Resolved cmd expect 中物化默认 `exitCode: [0]` 语义
|
||||
- **THEN** Normalized Config SHALL 不注入 exitCode,checker.resolve() SHALL 在 Resolved cmd expect 中物化默认 `exitCode: [0]` 语义
|
||||
|
||||
#### Scenario: 不配置 expect
|
||||
- **WHEN** target 未配置任何 expect 规则
|
||||
- **THEN** 系统 SHALL 正常处理,Raw expect 快照为 undefined,Resolved expect 由各 checker 物化自身默认状态语义
|
||||
- **THEN** 系统 SHALL 正常处理,Normalized Config 不包含 expect,Resolved expect 由各 checker 物化自身默认状态语义
|
||||
|
||||
#### Scenario: Raw expect 不被语义校验修改
|
||||
#### Scenario: Raw expect 不再保留
|
||||
- **WHEN** YAML 中配置 `expect.durationMs: 1000`
|
||||
- **THEN** 语义校验 SHALL 接受该 Raw primitive 简写且 MUST NOT 将 Raw 输入原地修改为 `{equals: 1000}`
|
||||
- **THEN** Normalized Config SHALL 包含 `expect.durationMs: {equals: 1000}`,ResolvedTarget MUST NOT 携带 `rawExpect`
|
||||
|
||||
#### Scenario: 旧 maxDurationMs 字段不再支持
|
||||
- **WHEN** YAML 中任一 target 配置 `expect.maxDurationMs`
|
||||
@@ -426,16 +410,12 @@
|
||||
|
||||
#### Scenario: ContentExpectations 字段必须为数组
|
||||
- **WHEN** YAML 中任一内容类 expect 字段配置为非数组
|
||||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示该字段必须为 expectation 数组
|
||||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示该内容字段必须为 expectation 数组
|
||||
|
||||
#### Scenario: regex 字段非法
|
||||
- **WHEN** YAML 中任一 `regex` 不是字符串、不是可编译正则或存在 ReDoS 风险
|
||||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示对应 regex 不合法
|
||||
|
||||
#### Scenario: Store 持久化 Raw expect
|
||||
- **WHEN** 系统同步已解析 target 到 targets 表
|
||||
- **THEN** `targets.expect` SHALL 存储变量替换后的 Raw expect JSON,而不是包含 `kind` 或 resolved matcher 的运行期执行计划
|
||||
|
||||
### Requirement: 数据保留配置字段
|
||||
配置 schema 的 `server.storage` 段 SHALL 支持 `retention` 字段,类型为字符串,格式为 `<数字><单位>`(单位:`d` 天、`h` 小时、`m` 分钟),用于指定历史数据保留时长。
|
||||
|
||||
@@ -510,16 +490,20 @@
|
||||
- **THEN** 系统 SHALL 在契约校验阶段以错误退出,提示 description 字段长度错误
|
||||
|
||||
### Requirement: 配置 schema 导出包含 target 元信息约束
|
||||
系统 SHALL 在导出的 `probe-config.schema.json` 中包含 target `id`、`name` 和 `description` 的长度约束和可空类型,用于编辑器提示和外部校验。
|
||||
系统 SHALL 在导出的 Authoring `probe-config.schema.json` 中包含 target `id`、`name` 和 `description` 的长度约束和可空类型,用于编辑器提示和外部校验。导出 schema SHALL 面向用户可书写规则文件,因此还 SHALL 接受支持变量替换字段中的完整变量引用字符串和 expect 简写。
|
||||
|
||||
#### Scenario: schema 导出 description
|
||||
- **WHEN** 系统导出 `probe-config.schema.json`
|
||||
- **THEN** target schema SHALL 包含可选的 `description` 字段,类型为 string 或 null,字符串最大长度为 500
|
||||
- **THEN** target schema SHALL 包含可选的 `description` 字段,类型为 string 或 null,字符串最大长度为 500,并允许完整变量引用字符串
|
||||
|
||||
#### Scenario: schema 导出 id 和 name
|
||||
- **WHEN** 系统导出 `probe-config.schema.json`
|
||||
- **THEN** target schema SHALL 声明 `id` 的 minLength 为 1、maxLength 为 30,并声明 `name` 为可选字段,类型为 string 或 null,字符串的 minLength 为 1、maxLength 为 30
|
||||
|
||||
#### Scenario: schema 导出面向 Authoring Config
|
||||
- **WHEN** 系统导出 `probe-config.schema.json`
|
||||
- **THEN** 导出 schema SHALL 接受 `server.listen.port: "${PORT|3000}"` 和 `expect.durationMs: 5000` 这类 Authoring 写法
|
||||
|
||||
### Requirement: TCP 配置校验
|
||||
系统 SHALL 在启动期对 tcp checker 的配置契约和语义执行严格校验。Tcp target 的 `tcp` 分组 SHALL 只允许 `host`、`port`、`readBanner`、`bannerReadTimeout` 和 `maxBannerBytes` 字段;Tcp expect SHALL 只允许 `connected`、`durationMs` 和 `banner` 字段。`banner` MUST 为 `RawContentExpectations` 数组,`durationMs` SHALL 为 `RawValueExpectation`。未知字段、非法类型、非法端口、非法 size、非法 ContentExpectations 和不可编译正则 MUST 导致启动期配置错误。语义校验 MUST NOT 修改 Raw tcp expect 输入。
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
- **THEN** check_results 表的外键约束 SHALL 使用 `ON DELETE RESTRICT`,确保删除 target 时数据库层面阻止操作而非级联删除关联记录
|
||||
|
||||
### Requirement: targets 表同步
|
||||
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示名称元信息、展示摘要、领域配置、调度配置、变量替换后的 Raw expect 配置快照、分组信息和目标说明。配置中不存在的 target SHALL 被标记为非活跃而非删除。`targets.expect` SHALL 存储 Raw expect JSON;系统 MUST NOT 将 Resolved expect 执行计划、`ContentExpectation.kind` union 或已归一化 matcher 包装结构写入该列。
|
||||
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示名称元信息、展示摘要、领域配置、调度配置、分组信息和目标说明。`targets.expect` 列当前实现写入 NULL,不持久化 expect 快照。配置中不存在的 target SHALL 被标记为非活跃而非删除。系统不需要保存原始用户输入的 Authoring expect 写法;`targets.expect` MUST NOT 被用作恢复用户 YAML 的数据源。
|
||||
|
||||
#### Scenario: 首次同步目标
|
||||
- **WHEN** 数据库为空且 YAML 中定义了 N 个 typed target
|
||||
- **THEN** 系统 SHALL 将所有目标插入 targets 表,包含 name、description、type、target、config、interval_ms、timeout_ms、expect、grp 和 active=1,其中 name 和 description 均可为 NULL,expect 列保存变量替换后的 Raw expect JSON
|
||||
- **THEN** 系统 SHALL 将所有目标插入 targets 表,包含 name、description、type、target、config、interval_ms、timeout_ms、expect、grp 和 active=1,其中 name 和 description 均可为 NULL,expect 列写入 NULL
|
||||
|
||||
#### Scenario: 配置变更后重新同步
|
||||
- **WHEN** YAML 配置发生变更(新增、删除或修改目标)后重启
|
||||
@@ -64,9 +64,9 @@
|
||||
- **WHEN** YAML target 配置 `description: null`
|
||||
- **THEN** targets 表 SHALL 将该目标的 description 存储为 NULL
|
||||
|
||||
#### Scenario: expect 列保存 Raw expect
|
||||
#### Scenario: expect 列不保存原始 Authoring 写法
|
||||
- **WHEN** target 配置 `expect.body: [{json: {path: "$.status"}}]` 和 `expect.durationMs: 1000`
|
||||
- **THEN** targets 表的 expect 列 SHALL 保存变量替换后的 Raw JSON,包含原始 `json.path` 和 `durationMs: 1000`,MUST NOT 保存 resolved `kind` 字段或 `{equals: 1000}` 执行计划
|
||||
- **THEN** targets 表的 expect 列 MUST NOT 保存原始 Authoring JSON 中的 `durationMs: 1000` 简写,当前实现写入 NULL
|
||||
|
||||
#### Scenario: 未配置 expect 写入 NULL
|
||||
- **WHEN** target 未配置任何 expect
|
||||
|
||||
Reference in New Issue
Block a user