@@ -24,25 +24,27 @@
- **THEN** 系统 SHALL 以配置错误退出并提示 `server` 中存在未知字段 `dataDir`
### Requirement: YAML 配置文件格式
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、probes 执行配置、可选的 variables 段、checker 默认值 和 typed target 列表(含可选 group 字段) 。server 配置 SHALL 将 HTTP 监听参数放在 `server.listen` 分组,将本地数据目录和历史数据保留时长放在 `server.storage` 分组,将运行时日志配置放在 `server.logging` 分组。拨测全局执行策略 SHALL 放在 `probes.execution` 分组。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` (可选)字段。
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、probes 执行配置、可选的 variables 段和 typed target 列表(含可选 group 字段) 。server 配置 SHALL 将 HTTP 监听参数放在 `server.listen` 分组,将本地数据目录和历史数据保留时长放在 `server.storage` 分组,将运行时日志配置放在 `server.logging` 分组。拨测全局执行策略 SHALL 放在 `probes.execution` 分组。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` 不再是合法配置段。
#### Scenario: 完整配置文件解析
- **WHEN** 系统启动并读取包含 server.listen、server.storage、server.logging、probes.execution、variables、defaults、 targets( 含 id、group 字段)的 YAML 配置文件
- **WHEN** 系统启动并读取包含 server.listen、server.storage、server.logging、probes.execution、variables 和 targets( 含 id、group 字段)的 YAML 配置文件
- **THEN** 系统 SHALL 正确解析所有字段并用于初始化服务、调度引擎和对应 checker runner
#### Scenario: defaults 配置段被拒绝
- **WHEN** 配置文件声明顶层 `defaults`
- **THEN** 系统 SHALL 以配置错误退出并提示存在未知字段 `defaults`
#### Scenario: 最简 HTTP 配置文件解析
- **WHEN** 系统读取只包含一个 `type: http` target( 含 `id` 和 `http.url` )的 YAML 配置文件(省略 server、probes、variables、defaults 和 expect)
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段( host=127.0.0.1, port=3000, dataDir=./data, interval=30s, timeout=10s, probes.execution.maxConcurrentChecks=20, server.storage.retention=7d, http.method=GET, http.maxBodyBytes=100MB, http.ignoreSSL=false, http.maxRedirects=0, group=" default" ),并保留 name=null、description=null
- **WHEN** 系统读取只包含一个 `type: http` target( 含 `id` 和 `http.url` )的 YAML 配置文件(省略 server、probes、variables 和 expect)
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段( host=127.0.0.1, port=3000, dataDir=./data, interval=30s, timeout=10s, probes.execution.maxConcurrentChecks=20, server.storage.retention=7d, 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: per-target 配置覆盖内置 默认值
- **WHEN** 某个 target 指定 interval、timeout 或对应领域分组中的可选 字段
- **THEN** 该 target SHALL 使用其自身的值,不受对应内置默认值 影响
#### Scenario: HTTP target 配置 ignoreSSL
- **WHEN** YAML 配置中 HTTP target 设置 `http.ignoreSSL: true`
@@ -58,43 +60,27 @@
#### 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
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段( interval=30s, timeout=10s, group=default, tcp.readBanner=false, tcp.bannerReadTimeout=2000, tcp.maxBannerBytes=4096) , 并保留 name=null、description=null
#### 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 字段
- **WHEN** HTTP target 未配置 `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
- **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 中的值
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段( interval=30s, timeout=10s, group=default, udp.payload=空字符串 , udp.encoding=text, udp.responseEncoding=text, udp.maxResponseBytes=4096) , 并保留 name=null、description=null
#### 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 中的值
- **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
### Requirement: CLI 参数
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
@@ -126,6 +112,10 @@
- **WHEN** YAML 中某个 target 缺少 id 或 type 字段
- **THEN** 系统 SHALL 以错误退出,提示哪个 target 缺少哪个字段
#### Scenario: 顶层 defaults 字段非法
- **WHEN** YAML 配置文件声明顶层 `defaults`
- **THEN** 系统 SHALL 以配置错误退出,提示 `defaults` 是未知字段
#### Scenario: HTTP target 缺少 url
- **WHEN** YAML 中某个 target 配置 `type: http` 但缺少 `http.url`
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 http.url 字段
@@ -203,7 +193,7 @@
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 http.body 必须为字符串
#### Scenario: maxBodyBytes 数字非法
- **WHEN** YAML 中某个 HTTP target 的 `http.maxBodyBytes` 或 defaults.http.maxBodyBytes 是负数、非整数或非安全整数
- **WHEN** YAML 中某个 HTTP target 的 `http.maxBodyBytes` 是负数、非整数或非安全整数
- **THEN** 系统 SHALL 以错误退出,提示 maxBodyBytes 必须为非负安全整数字节数或合法 size 字符串
#### Scenario: status 模式非法
@@ -291,7 +281,7 @@
- **THEN** 系统 SHALL 以错误退出,提示未知字段所在路径
#### Scenario: 动态 headers 字段允许
- **WHEN** YAML 中 `http.headers` 、`defaults.http.headers` 或 `expect.headers` 包含任意 header 名称,且对应值符合契约
- **WHEN** YAML 中 `http.headers` 或 `expect.headers` 包含任意 header 名称,且对应值符合契约
- **THEN** 系统 SHALL 接受这些动态 header 名称
#### Scenario: 动态 env 字段允许
@@ -312,7 +302,7 @@
#### 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`
- **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`
@@ -431,7 +421,7 @@
- **THEN** 系统 SHALL 接受该配置并在运行期使用完整执行耗时进行 matcher 校验
#### Scenario: 动态 headers 字段允许
- **WHEN** YAML 中 `http.headers` 、`defaults.http.headers` 、`llm.headers` 、`defaults. llm.headers` 或 `expect.headers` 包含任意 header 名称,且对应值符合契约
- **WHEN** YAML 中 `http.headers` 、`llm.headers` 或 `expect.headers` 包含任意 header 名称,且对应值符合契约
- **THEN** 系统 SHALL 接受这些动态 header 名称
#### Scenario: ContentExpectations 字段必须为数组
@@ -546,11 +536,11 @@
- **THEN** 系统 SHALL 以配置错误退出,提示 tcp.readBanner 必须为布尔值
#### Scenario: tcp bannerReadTimeout 非法
- **WHEN** YAML 中 tcp target 或 defaults.tcp 的 `bannerReadTimeout` 不是非负有限数字
- **WHEN** YAML 中 tcp target 的 `bannerReadTimeout` 不是非负有限数字
- **THEN** 系统 SHALL 以配置错误退出,提示 bannerReadTimeout 格式错误
#### Scenario: tcp maxBannerBytes 非法
- **WHEN** YAML 中 tcp target 或 defaults.tcp 的 `maxBannerBytes` 不是合法 size 值
- **WHEN** YAML 中 tcp target 的 `maxBannerBytes` 不是合法 size 值
- **THEN** 系统 SHALL 以配置错误退出,提示 maxBannerBytes 格式错误
#### Scenario: tcp expect connected 类型非法
@@ -569,12 +559,8 @@
- **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 输入。
系统 SHALL 在启动期对 llm checker 的配置契约和语义执行严格校验。LLM target 的 `llm` 分组 SHALL 只允许 `provider` 、`url` 、`model` 、`prompt` 、`mode` 、`key` 、`authToken` 、`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`
@@ -593,15 +579,15 @@
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.prompt 必须为非空字符串
#### Scenario: llm mode 非法
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `mode` 不是 `http` 或 `stream`
- **WHEN** YAML 中 llm target 的 `mode` 不是 `http` 或 `stream`
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.mode 不合法
#### Scenario: llm headers 类型非法
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `headers` 不是对象,或任一 header 值不是字符串
- **WHEN** YAML 中 llm target 的 `headers` 不是对象,或任一 header 值不是字符串
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.headers 格式错误
#### Scenario: llm ignoreSSL 类型非法
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `ignoreSSL` 不是布尔值
- **WHEN** YAML 中 llm target 的 `ignoreSSL` 不是布尔值
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.ignoreSSL 必须为布尔值
#### Scenario: llm authToken provider 非法
@@ -613,11 +599,11 @@
- **THEN** 系统 SHALL 以配置错误退出,提示 key 与 authToken 不能同时配置
#### Scenario: llm options 非法
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `options.maxOutputTokens` 不是正整数,`options.temperature` /`topP` /`topK` /`presencePenalty` /`frequencyPenalty` /`seed` 类型不合法,或 `options.stopSequences` 不是字符串数组
- **WHEN** YAML 中 llm target 的 `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
- **WHEN** YAML 中 llm target 的 `providerOptions` 不是 JSON object
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.providerOptions 格式错误
#### Scenario: llm 禁止字段失败