- 合并 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 审查提示文档
19 KiB
Purpose
定义 checker 模块的内聚化组织结构,确保每个 checker 以独立目录形式存在,包含其全部类型定义、schema 声明、语义校验、执行逻辑和断言逻辑。同时定义严格的依赖方向约束、Checker 接口定义、CheckerRegistry 注册中心、配置契约片段、配置校验 issue、引擎调度和服务注册委托。
Requirements
Requirement: Checker 目录内聚结构
每个 checker SHALL 以独立目录形式存在于 checker runner 目录下,目录内 SHALL 包含该 checker 的全部类型定义、schema 声明、语义校验、执行逻辑和断言逻辑。
Scenario: checker 目录完整性
- WHEN 开发者查看某个 checker 目录
- THEN 该目录 SHALL 包含该 checker 的全部类型定义、schema 声明、语义校验、执行逻辑和断言逻辑
Scenario: 新增 checker 最小改动
- WHEN 开发者新增一个 checker 类型(如 dns)
- THEN 开发者 SHALL 只需创建 checker 目录及其内部文件,并在注册列表中添加一行 import 和一行数组项
Requirement: 断言基础设施
系统 SHALL 提供所有 checker 共享的断言基础设施,使用 Raw/Resolved expectation 术语和 value/content/keyed/status/headers 模块边界。
Requirement: Schema 体系
系统 SHALL 通过 schema 体系组织配置校验、契约片段和 issue 报告,支持从 registry 动态构建整体配置 schema、共享 schema 片段引用、Ajv 校验入口和 ConfigValidationIssue 构造。
Requirement: 依赖方向约束
checker 系统内的模块依赖 SHALL 遵循严格的分层方向。
Scenario: checker 之间无横向依赖
- WHEN 开发者查看任何 checker 目录的 import 语句
- THEN 该 checker SHALL NOT 导入其他 checker 目录的任何模块
Scenario: expect/ 不依赖 runner/
- WHEN 开发者查看
expect/目录的 import 语句 - THEN
expect/中的文件 SHALL NOT 导入runner/目录的任何模块
Scenario: schema/ 不依赖 runner/ 的具体 checker
- WHEN 开发者查看
schema/目录的 import 语句 - THEN
schema/中的文件 SHALL 仅通过CheckerDefinition接口与 checker 交互,SHALL NOT 直接导入具体 checker 目录
Requirement: 显式注册列表
系统 SHALL 在 checker 注册入口文件中使用显式 import 列表注册所有 checker。
Scenario: 注册入口结构
- WHEN 开发者查看 checker 注册入口文件
- THEN 该文件 SHALL 包含所有 checker 的静态 import 和一个 checker 实例数组,通过循环调用
registry.register()完成注册
Scenario: 新增 checker 注册
- WHEN 开发者新增一个 checker
- THEN 开发者 SHALL 在注册入口文件中添加一行 import 和一行数组项,无需修改其他文件
Requirement: Checker 配置契约片段
系统 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 被注册
- THEN registry SHALL 能提供 HTTP target 和 HTTP expect 的 TypeBox 契约片段
Scenario: Cmd checker 提供契约片段
- WHEN Cmd checker 被注册
- THEN registry SHALL 能提供 Cmd target 和 Cmd expect 的 TypeBox 契约片段
Scenario: 新 checker 只维护自身契约
- WHEN 开发者新增一个 checker 类型
- THEN 该 checker SHALL 提供自身 TypeBox 配置契约和语义 validator,而不需要把 checker 专属字段写入中央手工校验逻辑
Scenario: 外部 schema 通过 registry 生成
- WHEN 系统生成
probe-config.schema.json - THEN 生成流程 SHALL 从 registry 获取已注册 checker 的 Authoring 契约片段,并将其组合进完整配置 schema
Scenario: 运行时 schema 通过 registry 生成
- WHEN config-loader 执行运行时 AJV 契约校验
- THEN 校验流程 SHALL 从 registry 获取已注册 checker 的 Normalized 契约片段,并将其组合进完整配置 schema
Scenario: 契约组装不依赖全局 singleton
- WHEN 测试或 schema 生成流程需要组装配置契约
- THEN 系统 SHALL 支持传入 fresh CheckerRegistry 实例完成契约组装,避免重复注册或全局状态污染
Requirement: Checker 启动期语义校验
系统 SHALL 支持 checker 提供启动期语义 validator,用于校验 TypeBox/Ajv 契约不适合表达或需要 checker 业务知识判断的配置规则。语义 validator MUST 在 resolver 填充最终 ResolvedTarget 之前执行,并 MUST 返回 ConfigValidationIssue[]。
Scenario: checker 语义校验先于 resolve
- WHEN config-loader 准备解析一个 target
- THEN 系统 SHALL 先完成该 target 的 checker 语义校验,再调用 checker.resolve()
Scenario: 语义校验失败阻止启动
- WHEN checker 语义 validator 发现非法配置
- THEN 系统 SHALL 以配置错误退出,不进入 checker 执行阶段
Requirement: 结构化配置校验 issue
系统 SHALL 使用统一 ConfigValidationIssue 表示配置校验问题,至少包含 code、path、message,并支持可选 targetName。契约校验和 checker 语义校验都 SHALL 产出该结构,由配置加载模块统一渲染为中文错误。
Scenario: Ajv 错误转换为 issue
- WHEN Ajv 校验发现 required、type 或 additionalProperties 错误
- THEN 系统 SHALL 将该错误转换为
ConfigValidationIssue,保留配置路径和可读 message
Scenario: checker validator 返回 issue
- WHEN checker 语义 validator 发现非法 XPath 或正则表达式
- THEN checker SHALL 返回
ConfigValidationIssue,而不是直接抛出最终用户错误字符串
Requirement: Checker 接口定义
系统 SHALL 定义面向扩展的泛型 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(配置分组名)、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) - THEN
ctx.signalSHALL 是一个由引擎创建的AbortSignal,在超时或引擎关闭时 abort
Scenario: resolve 不承担通用契约校验
- WHEN config-loader 调用 checker.resolve()
- 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" - THEN checker 的
configKeySHALL 默认使用"tcp",对应 target 的tcp分组
Scenario: 接口方法使用泛型约束
- WHEN 开发者查看
CheckerDefinition<TResolved>接口签名 - THEN
resolve的返回值 SHALL 为TResolved;execute的参数 SHALL 为TResolved;serialize的参数 SHALL 为TResolved
Scenario: checker 实现无需手动断言
- WHEN HttpChecker 实现
CheckerDefinition<ResolvedHttpTarget> - THEN
execute方法的 target 参数类型 SHALL 直接为ResolvedHttpTarget,无需在方法内部使用as类型断言
Scenario: registry 使用默认泛型参数
- WHEN CheckerRegistry 存储和返回 checker 实例
- THEN registry 内部 SHALL 使用
CheckerDefinition(等价于CheckerDefinition<ResolvedTargetBase>),实现类型擦除
Scenario: buildDetail 方法签名
- WHEN 开发者实现 buildDetail 方法
- THEN 方法签名 SHALL 为
buildDetail(observation: Record<string, unknown>): string | null,接收 observation 对象并返回人可读摘要字符串或 null
Scenario: buildDetail 由 API 层调用
- WHEN API 序列化 CheckResult
- THEN API 层 SHALL 通过 registry 获取对应 checker 并调用 buildDetail,而非由 execute 方法直接生成 detail
Requirement: CheckerRegistry 注册中心
系统 SHALL 提供 CheckerRegistry 类,支持 register(checker)、get(type) 和 supportedTypes。重复注册同一 type SHALL 抛出错误。registry 内部 SHALL 存储 CheckerDefinition(使用默认泛型参数),对外提供类型擦除后的接口。
Scenario: 注册并获取 Checker
- WHEN 调用
registry.register(new HttpChecker())后再调用registry.get("http") - THEN 返回的 SHALL 是之前注册的 HttpChecker 实例(类型为
CheckerDefinition)
Scenario: 获取未注册的 type
- WHEN 调用
registry.get("unknown")且未注册对应 type 的 checker - THEN 系统 SHALL 抛出错误,提示不支持的 probe type
Scenario: 重复注册
- WHEN 同一 type 值被重复
register() - THEN 系统 SHALL 抛出错误,提示该 type 已注册
Scenario: 查询支持的 type 列表
- WHEN 注册了 "http" 和 "cmd" 两个 checker 后查询
registry.supportedTypes - THEN 返回的数组 SHALL 包含
["http", "cmd"](按注册顺序)
Requirement: 引擎通过 registry 调度 checker
系统 SHALL 在引擎执行检查时通过 checkerRegistry.get(target.type).execute(target, ctx) 调度检查,替代原有的 switch/case 分支。
Scenario: 引擎使用 registry 调度
- WHEN engine 需要执行一个 type 为 "http" 的 target
- THEN engine SHALL 从
checkerRegistry中获取对应 checker 并调用其execute()方法,不再使用switch/case
Scenario: 引擎注入超时 signal
- WHEN engine 调度一次 checker 执行
- THEN engine SHALL 创建
AbortController,设置超时定时器,将controller.signal注入CheckerContext,执行完成后清理定时器
Requirement: 配置解析通过 registry 委托 checker
系统 SHALL 在配置加载流程中通过 checkerRegistry 发现已注册 checker,组合公共 TypeBox 契约与 checker 契约,并将 checker 专属语义校验和解析委托给对应 checker。公共配置校验 SHALL 仅保留公共语义校验(name 非空、name 不重复、group 类型、type 已注册等)和契约调度职责,不包含 checker 专属字段校验。
Scenario: 配置契约通过 registry 组合
- 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
Scenario: 通用字段校验保留在 config-loader
- WHEN YAML 配置中某个 target 缺少 name 或 type 字段
- THEN config-loader 的公共校验流程 SHALL 仍负责校验这些通用字段
Scenario: type 专属校验下沉到 checker
- WHEN YAML 配置中 HTTP target 缺少
http.url - THEN HTTP checker 的契约或语义校验 SHALL 抛出校验错误,提示缺少必填字段
Scenario: HTTP method 非法校验
- WHEN YAML 配置中 HTTP target 的
http.method不是大写合法方法枚举值 - THEN HTTP checker 契约或语义校验 SHALL 抛出校验错误,提示 method 不合法
Scenario: URL 格式校验
- WHEN YAML 配置中 HTTP target 的
http.url不以http://或https://开头 - THEN HttpChecker 的语义校验 SHALL 抛出校验错误,提示 URL 格式不合法
Requirement: 存储序列化通过 registry 获取展示格式
系统 SHALL 在存储同步 targets 时通过 checkerRegistry.get(t.type).serialize(t) 获取每个 target 的展示摘要和配置 JSON,替代函数中的类型分支。系统 SHALL 将 targets.expect 持久化为 null,不依赖 Raw expect 或 Resolved expect。
Scenario: 序列化委托 checker
- WHEN store 同步 targets 表
- THEN store SHALL 对每个 target 调用对应 checker 的
serialize()方法获取{ target, config }
Scenario: expect 持久化不依赖 rawExpect
- WHEN store 同步带 expect 的 target 到 targets 表
- THEN store SHALL 将
targets.expect写入 NULL,MUST NOT 依赖rawExpect或 Raw 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 不再展开 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 字段
- THEN config-loader SHALL 只调用该 checker 的
validate()和resolve(),不新增 checker 类型分支
Requirement: 共享 expect 断言函数
系统 SHALL 提供可被多个 checker 复用的 expect 基础设施。共享基础设施 SHALL 包含 value expectation、content expectations、keyed expectations、status code 断言、headers keyed 断言、failure 构造和 ReDoS 校验。checker 专用的状态类断言 SHALL 保留在对应 checker 目录,或在多个 checker 复用时移动到共享模块。仅被单个 checker 使用且不属于通用 value/content/keyed/status/header 模型的断言模块 SHALL 位于该 checker 目录内。
Scenario: 共享 ValueExpectation 断言
- WHEN 任何 checker 需要对数字、字符串、布尔或 JSON value 执行 matcher 匹配
- THEN SHALL 调用共享 value expectation 工具执行
equals、contains、regex、exists、empty、gt、gte、lt和lte语义
Scenario: 共享 ContentExpectations 断言
- WHEN HTTP body、LLM output、Cmd stdout/stderr、UDP response 或 TCP banner 需要执行返回内容校验
- THEN SHALL 调用共享 content expectations 工具,而不是在 checker 目录内复制 contains/regex/json/css/xpath 逻辑
Scenario: 共享 KeyedExpectations 断言
- WHEN HTTP 或 LLM checker 需要校验响应 headers,或 DB checker 需要校验 rows 中的列值
- THEN SHALL 调用共享 keyed expectations 工具,并按调用方规则决定 key 是否大小写敏感
Scenario: 共享 headers 断言
- WHEN HTTP 或 LLM checker 需要校验响应 headers
- THEN SHALL 调用共享 header expectation 包装函数,确保 header key 大小写不敏感
Scenario: 共享 regex ReDoS 校验
- WHEN 任一 matcher 或 content expectation 配置
regex - THEN SHALL 调用共享 ReDoS 校验工具在启动期拒绝危险正则
Scenario: 共享 failure 构造
- WHEN 任何 checker 需要构造 CheckFailure 对象
- THEN SHALL 调用共享的
errorFailure()或mismatchFailure()构造 CheckFailure,并保留 actual 截断策略
Scenario: 共享 status 断言
- WHEN HTTP 或 LLM checker 需要校验响应状态码
- THEN SHALL 复用共享 status code 断言函数,支持精确状态码和
1xx到5xx范围模式
Requirement: 超时控制由引擎注入 signal
Checker 实现的 execute() MUST 使用 ctx.signal 感知超时,不得自行创建 AbortController 或 setTimeout 用于超时控制。Cmd checker 和 ping checker 可在 signal abort 时 proc.kill() 以确保子进程被终止。
Scenario: HTTP checker 使用 signal
- WHEN HttpChecker 执行 HTTP 请求
- THEN SHALL 将
ctx.signal传入fetch()的signal选项,不自行创建AbortController
Scenario: Cmd checker 响应 signal
- WHEN CommandChecker 执行命令且 signal 被 abort
- THEN SHALL 调用
proc.kill()终止子进程,并在 CheckResult 中记录超时错误
Scenario: Ping checker 响应 signal
- WHEN IcmpChecker 执行 ping 命令且 signal 被 abort
- THEN SHALL 调用
proc.kill()终止 ping 子进程,并在 CheckResult 中记录超时错误
Requirement: CheckFailure.phase 使用 string 类型
CheckFailure.phase 的类型 SHALL 定义为 string,替代原有的硬编码联合类型 "status" | "duration" | "headers" | "body" | "exitCode" | "stdout" | "stderr"。
Scenario: phase 支持 checker 专用值
- WHEN cmd checker 在执行失败(spawn error)时生成 failure
- THEN
failure.phaseSHALL 可以是"spawn"等任意字符串值,类型系统 SHALL 不报错
Scenario: 前端展示 phase 不依赖硬编码类型
- WHEN 前端收到任意 phase 字符串值
- THEN 前端 SHALL 直接展示而不做类型判断