- 合并 20+ 细粒度 spec 为粗粒度主题规范:dashboard、data-store、probe-engine、probe-api、probe-config 等 - 删除完全冗余规范:data-retention(被 probe-engine+data-store 覆盖)、backend-code-quality(DEVELOPMENT.md 已记录) - 补充 http-checker 规范至完整标准(配置+执行+expect+校验+observation),匹配代码 440 行实现 - 清理 tcp/udp/llm checker 规范中已废弃 defaults 配置段的残留 Scenario - 清理 checker-cohesion-structure 中的实现路径引用(src/server/...) - 统一所有 spec 格式(## Purpose 开头,去除 # Capability/Title 形式) - 更新 prompt-spec-review.md 审查提示文档
729 lines
49 KiB
Markdown
729 lines
49 KiB
Markdown
## Purpose
|
||
|
||
定义拨测工具的 YAML 配置文件格式、解析校验规则和 CLI 启动流程。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: 旧配置入口拒绝
|
||
系统 SHALL 拒绝旧版顶层 `runtime` 和顶层 `logging` 配置入口。系统 SHALL 拒绝旧版 `server.host`、`server.port` 和 `server.dataDir` 入口,并要求使用 `server.listen` 与 `server.storage` 下的新路径。
|
||
|
||
#### Scenario: 顶层 runtime 被拒绝
|
||
- **WHEN** 配置文件声明顶层 `runtime`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示存在未知字段 `runtime`
|
||
|
||
#### Scenario: 顶层 logging 被拒绝
|
||
- **WHEN** 配置文件声明顶层 `logging`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示存在未知字段 `logging`
|
||
|
||
#### Scenario: server 旧监听字段被拒绝
|
||
- **WHEN** 配置文件声明 `server.host` 或 `server.port`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 `server` 中存在未知字段
|
||
|
||
#### Scenario: server 旧 dataDir 字段被拒绝
|
||
- **WHEN** 配置文件声明 `server.dataDir`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 `server` 中存在未知字段 `dataDir`
|
||
|
||
### Requirement: YAML 配置文件格式
|
||
系统 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 和 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 和 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 使用其自身的值,不受对应内置默认值影响
|
||
|
||
#### 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: per-target http.method 仍然有效
|
||
- **WHEN** HTTP target 配置 `http.method: POST`
|
||
- **THEN** 系统 SHALL 使用 POST 作为该 target 的请求方法
|
||
|
||
#### Scenario: 未配置 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
|
||
|
||
#### 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: 最简 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
|
||
|
||
### 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 定义 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`。
|
||
|
||
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 可编译。
|
||
|
||
契约校验、normalizer 和语义 validator SHALL 统一产出 `ConfigValidationIssue`,最终由配置加载流程统一渲染为中文错误信息。除 `headers`、`env`、Authoring `variables` 等明确声明为动态键值表的对象外,配置中的未知字段 SHALL 导致启动期配置错误。系统 MUST NOT 静默忽略未知字段。
|
||
|
||
#### Scenario: target 缺少必填字段
|
||
- **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 字段
|
||
|
||
#### 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** Normalized Config 中 probes.execution.maxConcurrentChecks 不是正整数
|
||
- **THEN** 系统 SHALL 以错误退出并提示 probes.execution.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** Normalized Config 中某个 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** Normalized Config 中某个 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` 是负数、非整数或非安全整数
|
||
- **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** Normalized Config 中某个 target 的 `expect.durationMs` 不是合法 `ValueMatcher` 对象
|
||
- **THEN** 系统 SHALL 以错误退出,提示该 target 的 expect.durationMs 格式错误
|
||
|
||
#### Scenario: durationMs 原始值简写在 Authoring schema 合法
|
||
- **WHEN** 使用 Authoring schema 校验配置文件中 `expect.durationMs: 5000`
|
||
- **THEN** JSON Schema 校验 SHALL 通过,因为 Authoring schema 接受 primitive 简写
|
||
|
||
#### Scenario: durationMs 原始值简写在 Normalized schema 非法
|
||
- **WHEN** 使用 Normalized schema 校验配置对象中 `expect.durationMs: 5000`
|
||
- **THEN** JSON Schema 校验 SHALL 失败,因为 Normalized schema 只接受 `ValueMatcher` 对象
|
||
|
||
#### Scenario: 变量引用在 Authoring schema 合法
|
||
- **WHEN** 使用 Authoring schema 校验配置文件中 `server.listen.port: "${PORT|3000}"`
|
||
- **THEN** JSON Schema 校验 SHALL 通过,因为 Authoring schema 面向用户可书写 YAML
|
||
|
||
#### Scenario: 变量引用在 Normalized schema 非法
|
||
- **WHEN** 使用 Normalized schema 校验配置对象中 `server.listen.port: "${PORT|3000}"`
|
||
- **THEN** JSON Schema 校验 SHALL 失败,因为 Normalized schema 只接受变量替换后的 integer
|
||
|
||
#### 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`
|
||
- **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` 或 `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: 变量替换后字段超长由 Normalized schema 的 maxLength 校验拦截
|
||
- **WHEN** Authoring Config 中 target 的 `description` 通过 `${...}` 变量替换后超过 500 个字符
|
||
- **THEN** Normalized schema SHALL 在 AJV 校验阶段以错误退出,提示 description 字段长度错误
|
||
|
||
#### Scenario: 配置生命周期分离
|
||
- **WHEN** 系统加载配置文件
|
||
- **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** 契约校验、normalizer、语义 validator 或变量替换阶段发现非法配置
|
||
- **THEN** 系统 SHALL 先生成包含 code、path、message 和可选 targetName 的结构化 `ConfigValidationIssue`,再统一渲染为中文错误信息
|
||
|
||
#### Scenario: 导出配置 JSON Schema
|
||
- **WHEN** 仓库生成或检查配置契约
|
||
- **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
|
||
- **WHEN** YAML 中配置 `maxBodyBytes: "100MB"`
|
||
- **THEN** 系统 SHALL 将其解析为 104857600 bytes
|
||
|
||
#### Scenario: 解析 KB
|
||
- **WHEN** YAML 中配置 `maxOutputBytes: "512KB"`
|
||
- **THEN** 系统 SHALL 将其解析为 524288 bytes
|
||
|
||
### Requirement: runtime 并发配置
|
||
系统 SHALL 支持 `probes.execution.maxConcurrentChecks` 配置全局最大并发检查数。`probes` 和 `probes.execution` 配置段均 SHALL 为可选,省略时使用默认值。
|
||
|
||
#### Scenario: 使用默认并发限制
|
||
- **WHEN** YAML 中未配置 `probes` 或未配置 `probes.execution.maxConcurrentChecks`
|
||
- **THEN** 系统 SHALL 使用默认值 20
|
||
|
||
#### Scenario: 配置并发限制
|
||
- **WHEN** YAML 中配置 `probes.execution.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`。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` 中的列值断言。
|
||
|
||
配置加载流程 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** 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** 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** Normalized Config SHALL 包含 normalized rowCount、rows keyed expectations 和 result content expectations
|
||
|
||
#### 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 在 Normalized expect 中保留执行顺序,供执行阶段按配置顺序快速失败
|
||
|
||
#### Scenario: 不配置 HTTP status
|
||
- **WHEN** HTTP target 未配置 `expect.status`
|
||
- **THEN** Normalized Config SHALL 不注入 status,checker.resolve() 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** Normalized Config SHALL 不注入 exitCode,checker.resolve() SHALL 在 Resolved cmd expect 中物化默认 `exitCode: [0]` 语义
|
||
|
||
#### Scenario: 不配置 expect
|
||
- **WHEN** target 未配置任何 expect 规则
|
||
- **THEN** 系统 SHALL 正常处理,Normalized Config 不包含 expect,Resolved expect 由各 checker 物化自身默认状态语义
|
||
|
||
#### Scenario: Raw expect 不再保留
|
||
- **WHEN** YAML 中配置 `expect.durationMs: 1000`
|
||
- **THEN** Normalized Config SHALL 包含 `expect.durationMs: {equals: 1000}`,ResolvedTarget MUST NOT 携带 `rawExpect`
|
||
|
||
#### 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`、`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 不合法
|
||
|
||
### Requirement: 数据保留配置字段
|
||
配置 schema 的 `server.storage` 段 SHALL 支持 `retention` 字段,类型为字符串,格式为 `<数字><单位>`(单位:`d` 天、`h` 小时、`m` 分钟),用于指定历史数据保留时长。
|
||
|
||
#### Scenario: retention 字段校验通过
|
||
- **WHEN** 配置文件中 `server.storage.retention` 为合法格式(如 `"7d"`、`"24h"`、`"30m"`)
|
||
- **THEN** 配置校验 SHALL 通过
|
||
|
||
#### Scenario: retention 字段格式非法
|
||
- **WHEN** 配置文件中 `server.storage.retention` 为非法格式(如 `"abc"`、`"7x"`、`""`)
|
||
- **THEN** 配置校验 SHALL 失败并报告格式错误
|
||
|
||
#### Scenario: retention 字段缺省
|
||
- **WHEN** 配置文件中未指定 `server.storage.retention`
|
||
- **THEN** 系统 SHALL 使用默认值 `"7d"`
|
||
|
||
### Requirement: 数据目录路径解析
|
||
配置加载流程 SHALL 将 `server.storage.dataDir` 相对路径基于配置文件所在目录(configDir)解析为绝对路径。绝对路径 SHALL 保持不变。
|
||
|
||
#### Scenario: dataDir 为相对路径
|
||
- **WHEN** 配置文件位于 `/opt/dial/probes.yaml`,且 `server.storage.dataDir` 配置为 `./data`
|
||
- **THEN** 系统 SHALL 将 dataDir 解析为 `/opt/dial/data`,而非依赖进程 cwd
|
||
|
||
#### Scenario: dataDir 为绝对路径
|
||
- **WHEN** `server.storage.dataDir` 配置为 `/var/lib/dial/data`
|
||
- **THEN** 系统 SHALL 直接使用该绝对路径,不做额外解析
|
||
|
||
#### Scenario: dataDir 使用默认值
|
||
- **WHEN** 未配置 `server.storage.dataDir`(使用默认值 `./data`)
|
||
- **THEN** 系统 SHALL 将默认值 `./data` 基于 configDir 解析为绝对路径
|
||
|
||
### Requirement: target 通用元信息字段约束
|
||
系统 SHALL 在 YAML target 通用字段中对 `id`、`name` 和 `description` 执行契约校验。`id` MUST 为 1 到 30 个字符,符合 `[a-zA-Z0-9][a-zA-Z0-9_-]*` 命名规则,MUST 在所有 targets 中全局唯一,MUST NOT 参与变量替换。`name` MUST 为 null 或 1 到 30 个字符的字符串,支持变量替换,MUST NOT 要求全局唯一,MUST NOT 参与 target 唯一性判定,语义校验 SHALL 拒绝仅包含空白字符的 name。`description` MUST 为 null 或不超过 500 个字符的字符串,支持变量替换,且 MAY 为空字符串,MUST NOT 参与 target 唯一性判定。`name` 为 null 时前端展示 SHALL 使用 `name ?? id` 作为目标名称文案,但该 fallback MUST NOT 改变 target 本身的 name 值。
|
||
|
||
#### Scenario: description 字段解析
|
||
- **WHEN** 系统读取包含 `description: "检查生产 API 健康状态"` 的 target
|
||
- **THEN** 系统 SHALL 将该字段解析为 target 的目标说明
|
||
|
||
#### Scenario: id 包含下划线和连字符
|
||
- **WHEN** target 配置 `id: "db_check-01"`
|
||
- **THEN** 系统 SHALL 使用该 id 作为 target 的唯一标识符
|
||
|
||
#### Scenario: id 不合法报错
|
||
- **WHEN** target 配置 `id: "_invalid"` 或 `id: "-start"` 或 `id: "has space"`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 id 不符合命名规则
|
||
|
||
#### Scenario: id 重复报错
|
||
- **WHEN** 两个 target 配置相同的 `id: "api-health"`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 id 重复
|
||
|
||
#### Scenario: id 不参与变量替换
|
||
- **WHEN** target 配置 `id: "${VAR}"` 形式的变量引用
|
||
- **THEN** 系统 SHALL NOT 对 id 执行变量替换
|
||
|
||
#### Scenario: name 使用变量
|
||
- **WHEN** target 配置 `name: "${env} API 健康检查"` 且 variables 中 `env: "生产"`
|
||
- **THEN** 系统 SHALL 将 name 解析为 "生产 API 健康检查"
|
||
|
||
#### Scenario: name 显式 null
|
||
- **WHEN** target 配置 `id: "api-health"` 和 `name: null`
|
||
- **THEN** 系统 SHALL 接受该配置,并在解析、存储和 API 响应中保留 name 为 null
|
||
|
||
#### Scenario: name 为 null 时前端展示 fallback
|
||
- **WHEN** 前端展示 name 为 null 的 target
|
||
- **THEN** 前端 SHALL 显示该 target 的 id 作为目标名称文案
|
||
|
||
#### 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: target 分组
|
||
系统 SHALL 支持在每个 target 上配置可选的 `group` 字段用于将目标归类到不同分组。未指定 `group` 的 target SHALL 归入 `"default"` 分组。系统 SHALL 保证 "default" 分组始终排在最前面,其余分组按配置文件中首次出现的顺序排列。系统 SHALL 在 API 响应中返回每个 target 的分组信息。系统 SHALL 在数据库 targets 表中持久化每个 target 的分组信息。
|
||
|
||
#### Scenario: 配置分组名称
|
||
- **WHEN** YAML 配置中某个 target 指定 `group: "搜索引擎"`
|
||
- **THEN** 系统 SHALL 将该 target 归类到 "搜索引擎" 分组
|
||
|
||
#### Scenario: 不配置分组
|
||
- **WHEN** YAML 配置中某个 target 未指定 `group` 字段
|
||
- **THEN** 系统 SHALL 将该 target 归类到 "default" 分组
|
||
|
||
#### Scenario: default 分组排最前
|
||
- **WHEN** 配置中存在多个分组(包括 "default" 和自定义分组)
|
||
- **THEN** API 返回的目标列表中 "default" 分组的目标 SHALL 排在其他分组之前
|
||
|
||
#### Scenario: 自定义分组按出现顺序
|
||
- **WHEN** 配置中 "搜索引擎" 分组在 "后端服务" 分组之前首次出现
|
||
- **THEN** API 返回中 "搜索引擎" 分组 SHALL 排在 "后端服务" 分组之前
|
||
|
||
#### Scenario: targets 列表包含分组
|
||
- **WHEN** 客户端请求 `GET /api/targets`
|
||
- **THEN** 响应中每个目标 SHALL 包含 `group` 字段,值为该目标所属的分组名称
|
||
|
||
#### Scenario: 持久化分组信息
|
||
- **WHEN** 系统同步 targets 到数据库
|
||
- **THEN** 每个 target 的 `grp` 列 SHALL 存储其分组名称,未配置分组的存储 `"default"`
|
||
|
||
### Requirement: 配置 schema 导出包含 target 元信息约束
|
||
系统 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,并允许完整变量引用字符串
|
||
|
||
#### 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 输入。
|
||
|
||
#### 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 的 `bannerReadTimeout` 不是非负有限数字
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 bannerReadTimeout 格式错误
|
||
|
||
#### Scenario: tcp maxBannerBytes 非法
|
||
- **WHEN** YAML 中 tcp target 的 `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 分组包含未知字段
|
||
|
||
### Requirement: LLM 配置校验
|
||
系统 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`
|
||
- **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 的 `mode` 不是 `http` 或 `stream`
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.mode 不合法
|
||
|
||
#### Scenario: llm headers 类型非法
|
||
- **WHEN** YAML 中 llm target 的 `headers` 不是对象,或任一 header 值不是字符串
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.headers 格式错误
|
||
|
||
#### Scenario: llm ignoreSSL 类型非法
|
||
- **WHEN** YAML 中 llm target 的 `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 的 `options.maxOutputTokens` 不是正整数,`options.temperature`/`topP`/`topK`/`presencePenalty`/`frequencyPenalty`/`seed` 类型不合法,或 `options.stopSequences` 不是字符串数组
|
||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.options 格式错误
|
||
|
||
#### Scenario: llm providerOptions 非法
|
||
- **WHEN** YAML 中 llm target 的 `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 支持可选的 `server.logging` 配置,用于定义运行时日志等级、命令行日志等级、文件日志等级、文件路径和滚动策略。`server.logging` 未配置时 SHALL 使用内置默认值。系统 SHALL NOT 支持 `server.logging.console.enabled`、`server.logging.console.format`、`server.logging.file.enabled`、`server.logging.file.format` 或 `server.logging.file.rotation.enabled` 字段。
|
||
|
||
#### Scenario: 未配置 logging 使用默认值
|
||
- **WHEN** 配置文件未声明 `server.logging`
|
||
- **THEN** 系统 SHALL 使用 `server.logging.level=info`、`server.logging.console.level=info`、`server.logging.file.level=info`、`server.logging.file.path=<resolved dataDir>/logs/dial.log`、`server.logging.file.rotation.size=50MB`、`server.logging.file.rotation.frequency=daily` 和 `server.logging.file.rotation.maxFiles=14`
|
||
|
||
#### Scenario: console 和 file level 继承全局 level
|
||
- **WHEN** 配置声明 `server.logging.level: warn` 且未声明 `server.logging.console.level` 和 `server.logging.file.level`
|
||
- **THEN** 系统 SHALL 将 console 和 file 的日志等级均解析为 `warn`
|
||
|
||
#### Scenario: 显式配置文件日志路径
|
||
- **WHEN** 配置声明 `server.logging.file.path`
|
||
- **THEN** 系统 SHALL 使用该路径作为文件日志路径,而不是默认 `<resolved dataDir>/logs/dial.log`
|
||
|
||
#### Scenario: 相对日志路径
|
||
- **WHEN** `server.logging.file.path` 是相对路径
|
||
- **THEN** 系统 SHALL 基于配置文件所在目录解析为绝对路径
|
||
|
||
#### Scenario: 绝对日志路径
|
||
- **WHEN** `server.logging.file.path` 是绝对路径
|
||
- **THEN** 系统 SHALL 原样使用该绝对路径,并允许该路径位于 `dataDir` 之外
|
||
|
||
#### Scenario: 不支持日志开关和格式字段
|
||
- **WHEN** 配置声明 `server.logging.console.enabled`、`server.logging.console.format`、`server.logging.file.enabled`、`server.logging.file.format` 或 `server.logging.file.rotation.enabled`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示存在未知字段
|
||
|
||
### Requirement: 日志配置校验
|
||
系统 SHALL 在启动期校验 `server.logging` 配置。日志等级 SHALL 只能是 `trace`、`debug`、`info`、`warn`、`error` 或 `fatal`。`rotation.size` SHALL 使用有效 size 格式且解析为正整数字节数。`rotation.frequency` SHALL 只能是 `hourly`、`daily` 或 `weekly`。`rotation.maxFiles` SHALL 是正整数。
|
||
|
||
#### Scenario: 非法日志等级
|
||
- **WHEN** 配置声明 `server.logging.level: verbose`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示日志等级非法
|
||
|
||
#### Scenario: 非法滚动大小
|
||
- **WHEN** 配置声明 `server.logging.file.rotation.size: "large"`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 size 格式非法
|
||
|
||
#### Scenario: 非法滚动频率
|
||
- **WHEN** 配置声明 `server.logging.file.rotation.frequency: monthly`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 frequency 非法
|
||
|
||
#### Scenario: 非法归档数量
|
||
- **WHEN** 配置声明 `server.logging.file.rotation.maxFiles: 0`
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示 maxFiles 必须为正整数
|
||
|
||
#### Scenario: 非法日志路径
|
||
- **WHEN** 配置声明 `server.logging.file.path` 为空字符串或空白字符串
|
||
- **THEN** 系统 SHALL 以配置错误退出并提示日志路径非法
|