685 lines
47 KiB
Markdown
685 lines
47 KiB
Markdown
## Purpose
|
||
|
||
定义拨测工具的 YAML 配置文件格式、解析校验规则和 CLI 启动流程。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: YAML 配置文件格式
|
||
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、runtime 配置、可选的 variables 段、checker 默认值和 typed target 列表(含可选 group 字段)。target MUST 使用 `id` 字段作为唯一标识符,MUST 使用 `type` 字段声明 checker 类型,SHALL 支持可选的 `name` 字段作为展示名称元信息,SHALL 支持可选的 `description` 字段作为目标说明。`name` 和 `description` 均 SHALL 允许省略或显式配置为 `null`;省略或显式 null 时解析结果 SHALL 保留为 null。HTTP 领域字段 MUST 放在 `http` 分组,cmd 领域字段 MUST 放在 `cmd` 分组,db 领域字段 MUST 放在 `db` 分组,tcp 领域字段 MUST 放在 `tcp` 分组,icmp 领域字段 MUST 放在 `icmp` 分组,udp 领域字段 MUST 放在 `udp` 分组,LLM 领域字段 MUST 放在 `llm` 分组。HTTP target 的 `http` 分组 SHALL 支持可选的 `ignoreSSL`(布尔值)和 `maxRedirects`(非负整数)字段。Db target 的 `db` 分组 SHALL 支持 `url`(必填)和 `query`(可选)字段。Tcp target 的 `tcp` 分组 SHALL 支持 `host`(必填)、`port`(必填)、`readBanner`(可选)、`bannerReadTimeout`(可选)和 `maxBannerBytes`(可选)字段。Icmp target 的 `icmp` 分组 SHALL 支持 `host`(必填)、`count`(可选,默认 3)和 `packetSize`(可选,默认 56)字段。Udp target 的 `udp` 分组 SHALL 支持 `host`(必填)、`port`(必填)、`payload`(可选,默认空字符串)、`encoding`(可选,默认 `text`)、`responseEncoding`(可选,默认 `text`)和 `maxResponseBytes`(可选,默认 4096)字段。LLM target 的 `llm` 分组 SHALL 支持 `provider`(必填)、`url`(必填)、`model`(必填)、`prompt`(必填)、`mode`(可选,默认 `http`)、`key`(可选,默认空字符串)、`authToken`(可选)、`headers`(可选)、`ignoreSSL`(可选,默认 `false`)、`options`(可选)和 `providerOptions`(可选)字段。
|
||
|
||
`defaults.http` 分组 SHALL 仅支持 `headers`(可选)和 `maxBodyBytes`(可选)字段。`defaults.http` 分组 MUST NOT 支持 `method` 字段。`defaults.tcp` 分组 SHALL 仅支持 `bannerReadTimeout`(可选)和 `maxBannerBytes`(可选)字段。`defaults.icmp` 分组 SHALL 仅支持空对象。`defaults.udp` 分组 SHALL 仅支持 `encoding`(可选)、`responseEncoding`(可选)和 `maxResponseBytes`(可选)字段。`defaults.llm` 分组 SHALL 仅支持 `mode`(可选)、`headers`(可选)、`ignoreSSL`(可选)、`options`(可选)和 `providerOptions`(可选)字段。
|
||
|
||
#### Scenario: 完整配置文件解析
|
||
- **WHEN** 系统启动并读取包含 server、runtime、variables、defaults、targets(含 id、group 字段)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 正确解析所有字段并用于初始化服务、调度引擎和对应 checker runner
|
||
|
||
#### Scenario: 最简 HTTP 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: http` target(含 `id` 和 `http.url`)的 YAML 配置文件(省略 server、runtime、variables、defaults 和 expect)
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(host=127.0.0.1, port=3000, dir=./data, interval=30s, timeout=10s, runtime.maxConcurrentChecks=20, http.method=GET, http.maxBodyBytes=100MB, http.ignoreSSL=false, http.maxRedirects=0, group="default"),并保留 name=null、description=null
|
||
|
||
#### Scenario: 最简 cmd 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: cmd` target(含 `id` 和 `cmd.exec`)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, cmd.cwd 为配置文件所在目录, cmd.maxOutputBytes=100MB),并保留 name=null、description=null
|
||
|
||
#### Scenario: per-target 配置覆盖全局默认值
|
||
- **WHEN** 某个 target 指定 interval、timeout 或对应领域分组中的默认字段
|
||
- **THEN** 该 target SHALL 使用其自身的值,不受 defaults 中对应字段影响
|
||
|
||
#### Scenario: HTTP target 配置 ignoreSSL
|
||
- **WHEN** YAML 配置中 HTTP target 设置 `http.ignoreSSL: true`
|
||
- **THEN** 系统 SHALL 解析该字段并在执行时跳过 SSL 证书校验
|
||
|
||
#### Scenario: HTTP target 配置 maxRedirects
|
||
- **WHEN** YAML 配置中 HTTP target 设置 `http.maxRedirects: 5`
|
||
- **THEN** 系统 SHALL 解析该字段并在执行时允许最多跟随 5 次重定向
|
||
|
||
#### Scenario: 最简 db 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: db` target(含 `id` 和 `db.url`)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, group="default"),并保留 name=null、description=null
|
||
|
||
#### Scenario: 最简 tcp 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: tcp` target(含 `id`、`tcp.host` 和 `tcp.port`)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, group="default", tcp.readBanner=false, tcp.bannerReadTimeout=2000, tcp.maxBannerBytes=4096),并保留 name=null、description=null
|
||
|
||
#### Scenario: defaults.tcp 配置 banner 默认值
|
||
- **WHEN** YAML 配置中 defaults.tcp 设置 `bannerReadTimeout` 和 `maxBannerBytes`
|
||
- **THEN** 未显式覆盖对应字段的 tcp target SHALL 使用 defaults.tcp 中的值
|
||
|
||
#### Scenario: defaults.http.method 触发校验错误
|
||
- **WHEN** 配置文件中出现 `defaults.http.method` 字段
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 defaults.http 中存在未知字段 method
|
||
|
||
#### Scenario: per-target http.method 仍然有效
|
||
- **WHEN** HTTP target 配置 `http.method: POST`
|
||
- **THEN** 系统 SHALL 使用 POST 作为该 target 的请求方法
|
||
|
||
#### Scenario: 未配置 http.method 使用内置默认值
|
||
- **WHEN** HTTP target 未配置 `http.method` 且 defaults.http 中无 method 字段
|
||
- **THEN** 系统 SHALL 使用内置默认值 GET 作为该 target 的请求方法
|
||
|
||
#### Scenario: 最简 icmp 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: icmp` target(含 `id` 和 `icmp.host`)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, group="default", icmp.count=3, icmp.packetSize=56),并保留 name=null、description=null
|
||
|
||
#### Scenario: 最简 udp 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: udp` target(含 `id`、`udp.host` 和 `udp.port`)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, group="default", udp.payload="", udp.encoding="text", udp.responseEncoding="text", udp.maxResponseBytes=4096),并保留 name=null、description=null
|
||
|
||
#### Scenario: defaults.udp 配置默认值
|
||
- **WHEN** YAML 配置中 defaults.udp 设置 `encoding`、`responseEncoding` 和 `maxResponseBytes`
|
||
- **THEN** 未显式覆盖对应字段的 udp target SHALL 使用 defaults.udp 中的值
|
||
|
||
#### Scenario: 最简 llm 配置文件解析
|
||
- **WHEN** 系统读取只包含一个 `type: llm` target(含 `id`、`llm.provider`、`llm.url`、`llm.model` 和 `llm.prompt`)的 YAML 配置文件
|
||
- **THEN** 系统 SHALL 使用内置默认值填充未指定字段(interval=30s, timeout=10s, group="default", llm.mode="http", llm.key="", llm.ignoreSSL=false, llm.options.maxOutputTokens=16, llm.options.temperature=0),并保留 name=null、description=null
|
||
|
||
#### Scenario: defaults.llm 配置默认值
|
||
- **WHEN** YAML 配置中 defaults.llm 设置 `mode`、`headers`、`ignoreSSL`、`options` 或 `providerOptions`
|
||
- **THEN** 未显式覆盖对应字段的 llm target SHALL 使用 defaults.llm 中的值
|
||
|
||
### Requirement: CLI 参数
|
||
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
|
||
|
||
#### Scenario: 指定配置文件启动
|
||
- **WHEN** 用户执行 `./dial-server ./probes.yaml`
|
||
- **THEN** 系统 SHALL 读取并解析指定路径的 YAML 文件作为配置
|
||
|
||
#### Scenario: 未提供配置文件路径
|
||
- **WHEN** 用户启动程序时未提供任何命令行参数
|
||
- **THEN** 系统 SHALL 以错误退出并提示需要指定配置文件路径
|
||
|
||
#### Scenario: 配置文件不存在
|
||
- **WHEN** 用户指定的配置文件路径不存在
|
||
- **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 可编译。
|
||
|
||
契约校验和语义 validator SHALL 统一产出 `ConfigValidationIssue`,最终由配置加载流程统一渲染为中文错误信息。
|
||
|
||
系统 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 分支接受数组或对象简写。
|
||
|
||
#### Scenario: target 缺少必填字段
|
||
- **WHEN** YAML 中某个 target 缺少 id 或 type 字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示哪个 target 缺少哪个字段
|
||
|
||
#### Scenario: HTTP target 缺少 url
|
||
- **WHEN** YAML 中某个 target 配置 `type: http` 但缺少 `http.url`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 http.url 字段
|
||
|
||
#### Scenario: cmd target 缺少 exec
|
||
- **WHEN** YAML 中某个 target 配置 `type: cmd` 但缺少 `cmd.exec`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 cmd.exec 字段
|
||
|
||
#### Scenario: target type 非法
|
||
- **WHEN** YAML 中某个 target 的 type 不是已注册 checker 类型
|
||
- **THEN** 系统 SHALL 以错误退出,提示不支持的 target type 和当前支持的 type 列表
|
||
|
||
#### Scenario: target id 重复
|
||
- **WHEN** YAML 中存在两个 id 相同的 target
|
||
- **THEN** 系统 SHALL 以错误退出,提示重复的 id
|
||
|
||
#### Scenario: target id 不合法
|
||
- **WHEN** YAML 中某个 target 的 id 不符合 `[a-zA-Z0-9][a-zA-Z0-9_-]*` 规则
|
||
- **THEN** 系统 SHALL 以错误退出,提示 id 命名不合法
|
||
|
||
#### Scenario: group 字段类型校验
|
||
- **WHEN** YAML 中某个 target 的 `group` 字段不是字符串
|
||
- **THEN** 系统 SHALL 以错误退出并提示 group 字段类型错误
|
||
|
||
#### Scenario: interval 格式非法
|
||
- **WHEN** interval 或 timeout 值不是有效的时长格式(如 `30s`、`5m`、`500ms`)
|
||
- **THEN** 系统 SHALL 以错误退出并提示格式错误
|
||
|
||
#### Scenario: maxConcurrentChecks 非法
|
||
- **WHEN** runtime.maxConcurrentChecks 不是正整数
|
||
- **THEN** 系统 SHALL 以错误退出并提示 runtime.maxConcurrentChecks 格式错误
|
||
|
||
#### Scenario: interval 或 timeout 解析结果非法
|
||
- **WHEN** interval 或 timeout 解析结果不是正整数毫秒(如 `0ms` 或 `1.5ms`)
|
||
- **THEN** 系统 SHALL 以错误退出并提示必须为正整数毫秒
|
||
|
||
#### Scenario: size 格式非法
|
||
- **WHEN** maxBodyBytes 或 maxOutputBytes 值不是有效的 size 格式
|
||
- **THEN** 系统 SHALL 以错误退出并提示支持 B、KB、MB、GB 格式
|
||
|
||
#### Scenario: size 解析结果非法
|
||
- **WHEN** maxBodyBytes 或 maxOutputBytes 解析结果不是非负安全整数字节数(如 `1.5B`)
|
||
- **THEN** 系统 SHALL 以错误退出并提示必须为非负安全整数字节数
|
||
|
||
#### Scenario: HTTP method 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.method` 不是 GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 之一
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 method 不合法
|
||
|
||
#### Scenario: HTTP method 小写非法
|
||
- **WHEN** YAML 中某个 HTTP target 配置 `http.method: get`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 method 必须为大写枚举值
|
||
|
||
#### Scenario: URL 格式非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.url` 不是合法 URL,或协议不是 `http:` / `https:`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 URL 格式不合法
|
||
|
||
#### Scenario: maxRedirects 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.maxRedirects` 为负数
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 maxRedirects 必须为非负整数
|
||
|
||
#### Scenario: maxRedirects 非整数非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.maxRedirects` 不是非负整数(如 `1.5` 或 `"5"`)
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 maxRedirects 必须为非负整数
|
||
|
||
#### Scenario: ignoreSSL 类型非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.ignoreSSL` 不是布尔值
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 ignoreSSL 必须为布尔值
|
||
|
||
#### Scenario: HTTP headers 类型非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.headers` 不是对象,或任一 header 值不是字符串
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 http.headers 格式错误
|
||
|
||
#### Scenario: HTTP body 类型非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.body` 已配置但不是字符串
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 http.body 必须为字符串
|
||
|
||
#### Scenario: maxBodyBytes 数字非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `http.maxBodyBytes` 或 defaults.http.maxBodyBytes 是负数、非整数或非安全整数
|
||
- **THEN** 系统 SHALL 以错误退出,提示 maxBodyBytes 必须为非负安全整数字节数或合法 size 字符串
|
||
|
||
#### Scenario: status 模式非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.status` 包含不符合 `1xx` 到 `5xx` 格式的字符串(如 `"abc"`、`"2x"`、`"6xx"`)
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 status 模式不合法
|
||
|
||
#### Scenario: status 数字非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.status` 包含非整数或不在 100-599 范围内的数字
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 status 数字不合法
|
||
|
||
#### Scenario: durationMs matcher 非法
|
||
- **WHEN** YAML 中某个 target 的 `expect.durationMs` 不是合法 `RawValueExpectation`
|
||
- **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: ValueMatcher 字段字符串简写合法
|
||
- **WHEN** YAML 中某个 target 配置 `expect.finishReason: "stop"`
|
||
- **THEN** 语义校验 SHALL 接受该 Raw primitive 简写且 MUST NOT 修改输入;checker resolve 阶段 SHALL 将其解析为 `{equals: "stop"}`
|
||
|
||
#### Scenario: ValueMatcher 字段 null 简写合法
|
||
- **WHEN** YAML 中某个 target 配置 ValueMatcher 字段值为 `null`
|
||
- **THEN** 语义校验 SHALL 接受该 Raw primitive 简写且 MUST NOT 修改输入;checker resolve 阶段 SHALL 将其解析为 `{equals: null}`
|
||
|
||
#### Scenario: ValueMatcher 字段数组简写非法
|
||
- **WHEN** YAML 中某个 target 配置 ValueMatcher 字段值为数组 `[1, 2]`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该字段必须为 primitive 原始值或 matcher 对象;如需数组 equals 匹配应写成 `{equals: [1, 2]}`
|
||
|
||
#### Scenario: ValueMatcher 字段对象简写非法
|
||
- **WHEN** YAML 中某个 target 配置 ValueMatcher 字段值为对象 `{foo: "bar"}`,且 `foo` 不是合法 matcher 字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示 `foo` 是未知 matcher;如需对象 equals 匹配应写成 `{equals: {foo: "bar"}}`
|
||
|
||
#### Scenario: icmp target 缺少 host
|
||
- **WHEN** YAML 中某个 target 配置 `type: icmp` 但缺少 `icmp.host`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 icmp.host 字段
|
||
|
||
#### Scenario: icmp expect 未知字段
|
||
- **WHEN** YAML 中 icmp target 的 expect 包含非 icmp expect 字段
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect 包含未知字段
|
||
|
||
#### Scenario: HTTP expect headers 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.headers` 不是对象,或某个 header 期望不是合法 `RawValueExpectation`
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 expect.headers 格式错误
|
||
|
||
#### Scenario: HTTP expect body 必须为数组
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.body` 已配置但不是数组
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 expect.body 必须为数组
|
||
|
||
#### Scenario: HTTP body expectation 缺少支持字段
|
||
- **WHEN** YAML 中某个 HTTP target 的 `expect.body` 数组项未包含 contains、regex、json、css、xpath 任一支持字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body expectation 缺少支持的 expectation 类型
|
||
|
||
#### Scenario: HTTP body expectation 同时配置多个支持字段
|
||
- **WHEN** YAML 中某个 HTTP target 的同一条 body expectation 同时包含 contains、regex、json、css、xpath 中的多个支持字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示每条 body expectation 只能配置一种 expectation 类型
|
||
|
||
#### Scenario: HTTP body regex 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body regex expectation 不是字符串或不是可编译正则表达式
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body regex 不合法
|
||
|
||
#### Scenario: HTTP body json path 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body json expectation 缺少 path,或 path 不符合系统支持的 JSONPath 子集
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body json path 不合法
|
||
|
||
#### Scenario: HTTP body css selector 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body css expectation 缺少 selector,或 selector 不是非空字符串
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body css selector 不合法
|
||
|
||
#### Scenario: HTTP body xpath path 非法
|
||
- **WHEN** YAML 中某个 HTTP target 的 body xpath expectation 缺少 path,或 path 不是非空字符串,或可被现有 XPath 库静态判定为语法错误
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 body xpath path 不合法
|
||
|
||
#### Scenario: expect matcher 类型非法
|
||
- **WHEN** YAML 中某个 expect matcher 的 regex 不是可编译正则字符串,empty/exists 不是布尔值,或 gt/gte/lt/lte 不是有限数字
|
||
- **THEN** 系统 SHALL 以错误退出,提示对应 matcher 配置不合法
|
||
|
||
#### Scenario: expect match 字段不再支持
|
||
- **WHEN** YAML 中某个 expect matcher 配置 `match` 字段
|
||
- **THEN** 系统 SHALL 以错误退出,提示 `match` 是未知字段,请使用 `regex`
|
||
|
||
#### Scenario: unknown 字段失败
|
||
- **WHEN** YAML 中任一结构化配置对象包含契约未声明的字段,且该对象不是明确允许动态键的对象
|
||
- **THEN** 系统 SHALL 以错误退出,提示未知字段所在路径
|
||
|
||
#### Scenario: 动态 headers 字段允许
|
||
- **WHEN** YAML 中 `http.headers`、`defaults.http.headers` 或 `expect.headers` 包含任意 header 名称,且对应值符合契约
|
||
- **THEN** 系统 SHALL 接受这些动态 header 名称
|
||
|
||
#### Scenario: 动态 env 字段允许
|
||
- **WHEN** YAML 中 `cmd.env` 包含任意环境变量名称,且对应值为字符串
|
||
- **THEN** 系统 SHALL 接受这些动态 env 名称
|
||
|
||
#### Scenario: JSON Schema 不修改输入
|
||
- **WHEN** 系统执行 JSON Schema 契约校验
|
||
- **THEN** 系统 MUST NOT 通过契约校验器强制转换类型、注入默认值或删除未知字段
|
||
|
||
#### Scenario: 配置生命周期分离
|
||
- **WHEN** 系统加载配置文件
|
||
- **THEN** 系统 SHALL 按 `unknown -> 变量替换 -> RawProbeConfig -> ValidatedProbeConfig -> ResolvedConfig` 的顺序执行变量替换、契约校验、语义校验和运行期配置解析
|
||
|
||
#### Scenario: 结构化校验 issue
|
||
- **WHEN** 契约校验、语义 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 字段)。所有 `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: ...}` 下
|
||
系统 SHALL 支持使用单位字符串配置读取上限,单位包括 `B`、`KB`、`MB` 和 `GB`。
|
||
|
||
#### Scenario: 解析 MB
|
||
- **WHEN** YAML 中配置 `maxBodyBytes: "100MB"`
|
||
- **THEN** 系统 SHALL 将其解析为 104857600 bytes
|
||
|
||
#### Scenario: 解析 KB
|
||
- **WHEN** YAML 中配置 `maxOutputBytes: "512KB"`
|
||
- **THEN** 系统 SHALL 将其解析为 524288 bytes
|
||
|
||
### Requirement: runtime 并发配置
|
||
系统 SHALL 支持 `runtime.maxConcurrentChecks` 配置全局最大并发检查数。
|
||
|
||
#### Scenario: 使用默认并发限制
|
||
- **WHEN** YAML 中未配置 runtime.maxConcurrentChecks
|
||
- **THEN** 系统 SHALL 使用默认值 20
|
||
|
||
#### Scenario: 配置并发限制
|
||
- **WHEN** YAML 中配置 `runtime.maxConcurrentChecks: 5`
|
||
- **THEN** 系统 SHALL 将全局最大并发检查数设置为 5
|
||
|
||
### Requirement: YAML 配置使用 Bun 内置解析
|
||
系统 SHALL 使用 Bun 内置的 `Bun.YAML.parse()` 解析配置文件,不引入外部 YAML 解析库。
|
||
|
||
#### Scenario: 解析 YAML 内容
|
||
- **WHEN** 系统读取 YAML 文件内容
|
||
- **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 保留变量替换后的 Raw expect 作为用户配置快照,同时生成 Resolved expect 作为运行期执行计划。语义校验 SHALL 只读取 Raw expect 并报告问题,MUST NOT 原地归一化或修改 Raw expect。Store 持久化 SHALL 写入 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
|
||
|
||
#### 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
|
||
|
||
#### 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
|
||
|
||
#### Scenario: 解析 tcp expect 配置
|
||
- **WHEN** YAML 配置文件中 tcp target 的 expect 包含 connected、banner expectation 数组和 durationMs matcher
|
||
- **THEN** 系统 SHALL 保留 Raw tcp expect 快照,并生成包含默认 connected、resolved banner content expectations 和 resolved durationMs 的 tcp Resolved expect
|
||
|
||
#### Scenario: 解析 icmp expect 配置
|
||
- **WHEN** YAML 配置文件中 icmp target 的 expect 包含 alive、packetLossPercent、avgLatencyMs、maxLatencyMs 和 durationMs matcher
|
||
- **THEN** 系统 SHALL 保留 Raw icmp expect 快照,并生成包含默认 alive 和 resolved 数值 expectations 的 icmp Resolved expect
|
||
|
||
#### Scenario: 解析 udp expect 配置
|
||
- **WHEN** YAML 配置文件中 udp target 的 expect 包含 responded、response、responseSize、sourceHost、sourcePort 和 durationMs matcher
|
||
- **THEN** 系统 SHALL 保留 Raw udp expect 快照,并生成包含默认 responded、resolved response content expectations 和 resolved value expectations 的 udp Resolved expect
|
||
|
||
#### Scenario: 解析 llm expect 配置
|
||
- **WHEN** YAML 配置文件中 llm target 的 expect 包含 status、headers、output、finishReason、rawFinishReason、usage、stream 和 durationMs matcher
|
||
- **THEN** 系统 SHALL 保留 Raw llm expect 快照,并生成包含默认 status、resolved headers、output、finishReason、rawFinishReason、usage、stream 和 durationMs 的 llm Resolved expect,并保留 output 内容 expectation 数组顺序
|
||
|
||
#### Scenario: 解析有序 ContentExpectations 数组
|
||
- **WHEN** YAML 中任一内容类 expect 配置 contains、json、regex 三个数组项
|
||
- **THEN** 系统 SHALL 在 Raw expect 中保留数组顺序,并在 Resolved expect 中保留执行顺序,供执行阶段按配置顺序快速失败
|
||
|
||
#### Scenario: 不配置 HTTP status
|
||
- **WHEN** HTTP target 未配置 `expect.status`
|
||
- **THEN** 系统 SHALL 在 Resolved HTTP expect 中物化默认 `status: [200]` 语义
|
||
|
||
#### Scenario: 配置 HTTP status 范围模式
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx"]`
|
||
- **THEN** 系统 SHALL 在执行 expect 时匹配所有 200-299 状态码
|
||
|
||
#### Scenario: 不配置 cmd exitCode
|
||
- **WHEN** cmd target 未配置 `expect.exitCode`
|
||
- **THEN** 系统 SHALL 在 Resolved cmd expect 中物化默认 `exitCode: [0]` 语义
|
||
|
||
#### Scenario: 不配置 expect
|
||
- **WHEN** target 未配置任何 expect 规则
|
||
- **THEN** 系统 SHALL 正常处理,Raw expect 快照为 undefined,Resolved expect 由各 checker 物化自身默认状态语义
|
||
|
||
#### Scenario: Raw expect 不被语义校验修改
|
||
- **WHEN** YAML 中配置 `expect.durationMs: 1000`
|
||
- **THEN** 语义校验 SHALL 接受该 Raw primitive 简写且 MUST NOT 将 Raw 输入原地修改为 `{equals: 1000}`
|
||
|
||
#### Scenario: 旧 maxDurationMs 字段不再支持
|
||
- **WHEN** YAML 中任一 target 配置 `expect.maxDurationMs`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示该字段未知,并要求使用 `expect.durationMs`
|
||
|
||
#### Scenario: 旧 match 字段不再支持
|
||
- **WHEN** YAML 中任一 matcher 或内容 expectation 配置 `match`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示该字段未知或不支持,并要求使用 `regex`
|
||
|
||
#### Scenario: durationMs matcher 配置
|
||
- **WHEN** YAML 中任一 target 配置 `expect.durationMs: {lte: 1000}`
|
||
- **THEN** 系统 SHALL 接受该配置并在运行期使用完整执行耗时进行 matcher 校验
|
||
|
||
#### Scenario: 动态 headers 字段允许
|
||
- **WHEN** YAML 中 `http.headers`、`defaults.http.headers`、`llm.headers`、`defaults.llm.headers` 或 `expect.headers` 包含任意 header 名称,且对应值符合契约
|
||
- **THEN** 系统 SHALL 接受这些动态 header 名称
|
||
|
||
#### Scenario: ContentExpectations 字段必须为数组
|
||
- **WHEN** YAML 中任一内容类 expect 字段配置为非数组
|
||
- **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 的 `runtime` 段 SHALL 支持 `retention` 字段,类型为字符串,格式为 `<数字><单位>`(单位:`d` 天、`h` 小时、`m` 分钟),用于指定历史数据保留时长。
|
||
|
||
#### Scenario: retention 字段校验通过
|
||
- **WHEN** 配置文件中 `runtime.retention` 为合法格式(如 `"7d"`、`"24h"`、`"30m"`)
|
||
- **THEN** 配置校验 SHALL 通过
|
||
|
||
#### Scenario: retention 字段格式非法
|
||
- **WHEN** 配置文件中 `runtime.retention` 为非法格式(如 `"abc"`、`"7x"`、`""`)
|
||
- **THEN** 配置校验 SHALL 失败并报告格式错误
|
||
|
||
#### Scenario: retention 字段缺省
|
||
- **WHEN** 配置文件中未指定 `runtime.retention`
|
||
- **THEN** 系统 SHALL 使用默认值 `"7d"`
|
||
|
||
### Requirement: 数据目录路径解析
|
||
配置加载流程 SHALL 将 `server.dataDir` 相对路径基于配置文件所在目录(configDir)解析为绝对路径。绝对路径 SHALL 保持不变。
|
||
|
||
#### Scenario: dataDir 为相对路径
|
||
- **WHEN** 配置文件位于 `/opt/dial/probes.yaml`,且 `server.dataDir` 配置为 `./data`
|
||
- **THEN** 系统 SHALL 将 dataDir 解析为 `/opt/dial/data`,而非依赖进程 cwd
|
||
|
||
#### Scenario: dataDir 为绝对路径
|
||
- **WHEN** `server.dataDir` 配置为 `/var/lib/dial/data`
|
||
- **THEN** 系统 SHALL 直接使用该绝对路径,不做额外解析
|
||
|
||
#### Scenario: dataDir 使用默认值
|
||
- **WHEN** 未配置 `server.dataDir`(使用默认值 `./data`)
|
||
- **THEN** 系统 SHALL 将默认值 `./data` 基于 configDir 解析为绝对路径
|
||
|
||
### Requirement: target 通用元信息字段约束
|
||
系统 SHALL 在 YAML target 通用字段中支持 `description` 字段,并对 `id`、`name` 和 `description` 执行契约校验。`id` MUST 为 1 到 30 个字符。`name` MUST 为 null 或 1 到 30 个字符的字符串,且语义校验 SHALL 拒绝仅包含空白字符的 name。`description` MUST 为 null 或不超过 500 个字符的字符串,且 MAY 为空字符串。
|
||
|
||
#### Scenario: description 字段解析
|
||
- **WHEN** 系统读取包含 `description: "检查生产 API 健康状态"` 的 target
|
||
- **THEN** 系统 SHALL 将该字段解析为 target 的目标说明
|
||
|
||
#### Scenario: name 为 null 通过校验
|
||
- **WHEN** 系统读取包含 `name: null` 或省略 `name` 的 target
|
||
- **THEN** 系统 SHALL 接受该配置
|
||
|
||
#### Scenario: name 仅包含空白字符报错
|
||
- **WHEN** 系统读取包含 `name: " "` 的 target
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 name 不能为空白
|
||
|
||
#### Scenario: description 为空字符串
|
||
- **WHEN** 系统读取包含 `description: ""` 的 target
|
||
- **THEN** 系统 SHALL 接受该配置,且不触发长度错误
|
||
|
||
#### Scenario: description 为 null 通过校验
|
||
- **WHEN** 系统读取包含 `description: null` 或省略 `description` 的 target
|
||
- **THEN** 系统 SHALL 接受该配置
|
||
|
||
#### Scenario: description 类型非法
|
||
- **WHEN** YAML 中某个 target 的 `description` 字段不是字符串也不是 null
|
||
- **THEN** 系统 SHALL 以错误退出,提示 description 字段类型错误
|
||
|
||
#### Scenario: description 超过最大长度
|
||
- **WHEN** YAML 中某个 target 的 `description` 字段超过 500 个字符
|
||
- **THEN** 系统 SHALL 以错误退出,提示 description 字段长度错误
|
||
|
||
#### Scenario: id 超过最大长度
|
||
- **WHEN** YAML 中某个 target 的 `id` 字段超过 30 个字符
|
||
- **THEN** 系统 SHALL 以错误退出,提示 id 字段长度错误
|
||
|
||
#### Scenario: name 超过最大长度
|
||
- **WHEN** YAML 中某个 target 的 `name` 字段超过 30 个字符
|
||
- **THEN** 系统 SHALL 以错误退出,提示 name 字段长度错误
|
||
|
||
#### Scenario: 变量替换后 description 超长
|
||
- **WHEN** target 的 `description` 通过变量替换后超过 500 个字符
|
||
- **THEN** 系统 SHALL 在契约校验阶段以错误退出,提示 description 字段长度错误
|
||
|
||
### Requirement: 配置 schema 导出包含 target 元信息约束
|
||
系统 SHALL 在导出的 `probe-config.schema.json` 中包含 target `id`、`name` 和 `description` 的长度约束和可空类型,用于编辑器提示和外部校验。
|
||
|
||
#### Scenario: schema 导出 description
|
||
- **WHEN** 系统导出 `probe-config.schema.json`
|
||
- **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
|
||
|
||
### 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 输入。
|
||
|
||
#### Scenario: tcp host 类型非法
|
||
- **WHEN** YAML 中 tcp target 的 `tcp.host` 不是非空字符串
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 tcp.host 必须为非空字符串
|
||
|
||
#### Scenario: tcp port 类型非法
|
||
- **WHEN** YAML 中 tcp target 的 `tcp.port` 不是整数
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 tcp.port 必须为整数端口
|
||
|
||
#### Scenario: tcp readBanner 类型非法
|
||
- **WHEN** YAML 中 tcp target 的 `tcp.readBanner` 不是布尔值
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 tcp.readBanner 必须为布尔值
|
||
|
||
#### Scenario: tcp bannerReadTimeout 非法
|
||
- **WHEN** YAML 中 tcp target 或 defaults.tcp 的 `bannerReadTimeout` 不是非负有限数字
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 bannerReadTimeout 格式错误
|
||
|
||
#### Scenario: tcp maxBannerBytes 非法
|
||
- **WHEN** YAML 中 tcp target 或 defaults.tcp 的 `maxBannerBytes` 不是合法 size 值
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 maxBannerBytes 格式错误
|
||
|
||
#### Scenario: tcp expect connected 类型非法
|
||
- **WHEN** YAML 中 tcp target 的 `expect.connected` 不是布尔值
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.connected 必须为布尔值
|
||
|
||
#### Scenario: tcp expect banner 非法
|
||
- **WHEN** YAML 中 tcp target 的 `expect.banner` 不是合法 `RawContentExpectations` 数组
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.banner 格式错误
|
||
|
||
#### Scenario: tcp expect banner regex 正则非法
|
||
- **WHEN** YAML 中 tcp target 配置 `expect.banner: [{ regex: "[invalid" }]`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,而不是延迟到运行期抛错
|
||
|
||
#### Scenario: tcp 分组未知字段失败
|
||
- **WHEN** YAML 中 tcp target 的 `tcp` 分组包含 `tls: true` 等未知字段
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 tcp 分组包含未知字段
|
||
|
||
#### Scenario: defaults.tcp 未知字段失败
|
||
- **WHEN** YAML 中 defaults.tcp 包含 `host` 或其他非默认字段
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 defaults.tcp 包含未知字段
|
||
|
||
### Requirement: LLM 配置校验
|
||
系统 SHALL 在启动期对 llm checker 的配置契约和语义执行严格校验。LLM target 的 `llm` 分组 SHALL 只允许 `provider`、`url`、`model`、`prompt`、`mode`、`key`、`authToken`、`headers`、`ignoreSSL`、`options` 和 `providerOptions` 字段。`defaults.llm` 分组 SHALL 只允许 `mode`、`headers`、`ignoreSSL`、`options` 和 `providerOptions` 字段。LLM expect SHALL 只允许 `status`、`headers`、`output`、`finishReason`、`rawFinishReason`、`usage`、`stream` 和 `durationMs` 字段。`expect.output` MUST 为 `RawContentExpectations` 数组。`expect.finishReason`、`expect.rawFinishReason`、`expect.usage.*`、`expect.stream.firstTokenMs` 和 `expect.durationMs` SHALL 使用 `RawValueExpectation`。未知字段、非法 provider、非法 URL、非法 mode、非法认证组合、非法 options、非法 output expectation 和 `mode: http` 下配置 `expect.stream` MUST 导致启动期配置错误。语义校验 MUST NOT 修改 Raw llm expect 输入。
|
||
|
||
#### Scenario: llm provider 非法
|
||
- **WHEN** YAML 中 llm target 的 `llm.provider` 不是 `openai`、`openai-responses` 或 `anthropic`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.provider 不合法
|
||
|
||
#### Scenario: llm url 非法
|
||
- **WHEN** YAML 中 llm target 的 `llm.url` 不是 `http://` 或 `https://` URL
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.url 格式不合法
|
||
|
||
#### Scenario: llm model 为空
|
||
- **WHEN** YAML 中 llm target 的 `llm.model` 不是非空字符串
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.model 必须为非空字符串
|
||
|
||
#### Scenario: llm prompt 为空
|
||
- **WHEN** YAML 中 llm target 的 `llm.prompt` 不是非空字符串
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.prompt 必须为非空字符串
|
||
|
||
#### Scenario: llm mode 非法
|
||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `mode` 不是 `http` 或 `stream`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.mode 不合法
|
||
|
||
#### Scenario: llm headers 类型非法
|
||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `headers` 不是对象,或任一 header 值不是字符串
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.headers 格式错误
|
||
|
||
#### Scenario: llm ignoreSSL 类型非法
|
||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `ignoreSSL` 不是布尔值
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.ignoreSSL 必须为布尔值
|
||
|
||
#### Scenario: llm authToken provider 非法
|
||
- **WHEN** YAML 中 `provider: openai` 或 `provider: openai-responses` 的 llm target 配置 `authToken`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 authToken 仅支持 anthropic provider
|
||
|
||
#### Scenario: Anthropic key 与 authToken 冲突
|
||
- **WHEN** YAML 中 `provider: anthropic` 的 llm target 同时配置非空 `key` 和非空 `authToken`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 key 与 authToken 不能同时配置
|
||
|
||
#### Scenario: llm options 非法
|
||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `options.maxOutputTokens` 不是正整数,`options.temperature`/`topP`/`topK`/`presencePenalty`/`frequencyPenalty`/`seed` 类型不合法,或 `options.stopSequences` 不是字符串数组
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.options 格式错误
|
||
|
||
#### Scenario: llm providerOptions 非法
|
||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `providerOptions` 不是 JSON object
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.providerOptions 格式错误
|
||
|
||
#### Scenario: llm 禁止字段失败
|
||
- **WHEN** YAML 中 llm target 配置 `api`、`providerName`、`baseURL`、`apiKey`、`messages`、`maxRetries`、`request`、`maxBodyBytes` 或 `maxStreamBytes`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm 分组包含未知字段
|
||
|
||
#### Scenario: llm output expectation 缺少支持字段
|
||
- **WHEN** YAML 中 llm target 的 `expect.output` 数组项未包含任何合法 ValueMatcher 字段或 extractor
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 output expectation 缺少支持的 expectation 类型
|
||
|
||
#### Scenario: llm output expectation 同时配置多个 extractor
|
||
- **WHEN** YAML 中 llm target 的同一条 output expectation 同时包含 json、css、xpath 中的多个 extractor
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示每条 output expectation 只能配置一种 extractor
|
||
|
||
#### Scenario: llm output regex 非法
|
||
- **WHEN** YAML 中 llm target 的 output regex expectation 不是字符串、不是可编译正则表达式或存在 ReDoS 风险
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示该 output regex 不合法
|
||
|
||
#### Scenario: llm output json path 非法
|
||
- **WHEN** YAML 中 llm target 的 output json expectation 缺少 path,或 path 不符合系统支持的 JSONPath 子集
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示该 output json path 不合法
|
||
|
||
#### Scenario: llm expect usage 非法
|
||
- **WHEN** YAML 中 llm target 的 `expect.usage.inputTokens`、`expect.usage.outputTokens` 或 `expect.usage.totalTokens` 不是合法 `RawValueExpectation`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.usage 格式错误
|
||
|
||
#### Scenario: llm expect stream 仅允许 stream mode
|
||
- **WHEN** YAML 中 llm target 配置 `llm.mode: http` 且配置 `expect.stream`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.stream 仅支持 stream mode
|
||
|
||
#### Scenario: llm expect stream firstTokenMs 非法
|
||
- **WHEN** YAML 中 llm target 的 `expect.stream.firstTokenMs` 不是合法 `RawValueExpectation`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.stream.firstTokenMs 格式错误
|
||
|
||
### Requirement: 日志配置格式
|
||
系统 SHALL 支持可选的顶层 `logging` 配置,用于定义运行时日志等级、命令行日志等级、文件日志等级、文件路径和滚动策略。`logging` 未配置时 SHALL 使用内置默认值。系统 SHALL NOT 支持 `logging.console.enabled`、`logging.console.format`、`logging.file.enabled`、`logging.file.format` 或 `logging.file.rotation.enabled` 字段。
|
||
|
||
#### Scenario: 未配置 logging 使用默认值
|
||
- **WHEN** 配置文件未声明 `logging`
|
||
- **THEN** 系统 SHALL 使用 `logging.level=info`、`logging.console.level=info`、`logging.file.level=info`、`logging.file.path=<resolved dataDir>/logs/dial.log`、`logging.file.rotation.size=50MB`、`logging.file.rotation.frequency=daily` 和 `logging.file.rotation.maxFiles=14`
|
||
|
||
#### Scenario: console 和 file level 继承全局 level
|
||
- **WHEN** 配置声明 `logging.level: warn` 且未声明 `logging.console.level` 和 `logging.file.level`
|
||
- **THEN** 系统 SHALL 将 console 和 file 的日志等级均解析为 `warn`
|
||
|
||
#### Scenario: 显式配置文件日志路径
|
||
- **WHEN** 配置声明 `logging.file.path`
|
||
- **THEN** 系统 SHALL 使用该路径作为文件日志路径,而不是默认 `<resolved dataDir>/logs/dial.log`
|
||
|
||
#### Scenario: 相对日志路径
|
||
- **WHEN** `logging.file.path` 是相对路径
|
||
- **THEN** 系统 SHALL 基于配置文件所在目录解析为绝对路径
|
||
|
||
#### Scenario: 绝对日志路径
|
||
- **WHEN** `logging.file.path` 是绝对路径
|
||
- **THEN** 系统 SHALL 原样使用该绝对路径,并允许该路径位于 `dataDir` 之外
|
||
|
||
#### Scenario: 不支持日志开关和格式字段
|
||
- **WHEN** 配置声明 `logging.console.enabled`、`logging.console.format`、`logging.file.enabled`、`logging.file.format` 或 `logging.file.rotation.enabled`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示存在未知字段
|
||
|
||
### Requirement: 日志配置校验
|
||
系统 SHALL 在启动期校验 `logging` 配置。日志等级 SHALL 只能是 `trace`、`debug`、`info`、`warn`、`error` 或 `fatal`。`rotation.size` SHALL 使用有效 size 格式且解析为正整数字节数。`rotation.frequency` SHALL 只能是 `hourly`、`daily` 或 `weekly`。`rotation.maxFiles` SHALL 是正整数。
|
||
|
||
#### Scenario: 非法日志等级
|
||
- **WHEN** 配置声明 `logging.level: verbose`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示日志等级非法
|
||
|
||
#### Scenario: 非法滚动大小
|
||
- **WHEN** 配置声明 `logging.file.rotation.size: "large"`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 size 格式非法
|
||
|
||
#### Scenario: 非法滚动频率
|
||
- **WHEN** 配置声明 `logging.file.rotation.frequency: monthly`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 frequency 非法
|
||
|
||
#### Scenario: 非法归档数量
|
||
- **WHEN** 配置声明 `logging.file.rotation.maxFiles: 0`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 maxFiles 必须为正整数
|
||
|
||
#### Scenario: 非法日志路径
|
||
- **WHEN** 配置声明 `logging.file.path` 为空字符串或空白字符串
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示日志路径非法
|