- 新增 config-contract 模块(TypeBox fragments、Ajv 契约校验、ConfigValidationIssue) - CheckerDefinition 扩展为含 configKey、schemas、validate 的完整插件接口 - HTTP/Command 各自维护 contract.ts + validate.ts,校验从 resolve 中分离 - resolve 不再承担校验,只做默认值合并和路径/单位解析 - config-loader 流程: unknown → RawProbeConfig → ValidatedProbeConfig → ResolvedConfig - 导出 probe-config.schema.json,新增 schema/schema:check 脚本 - 更新 DEVELOPMENT.md 新增 1.7 开发新 Checker 完整指引 - 同步更新 4 个 main specs(probe-config、command-checker、expect-body-checkers、checker-runner-abstraction)
11 KiB
Purpose
定义 Checker 接口规范、注册机制、CheckerContext 上下文注入,以及共享 expect 断言函数的职责边界。此 capability 是 checker 系统的架构基础,不定义任何具体 checker 类型的业务行为。
Requirements
Requirement: Checker 配置契约片段
系统 SHALL 支持 checker 提供自身 TypeBox 配置契约片段,用于描述该 checker 的 defaults 分组、target 领域分组和 expect 分组。公共配置加载模块 SHALL 通过 registry 获取已注册 checker 的契约片段,并组合为启动期 Ajv 契约校验流程和外部 probe-config.schema.json 导出流程。
Scenario: HTTP checker 提供契约片段
- WHEN HTTP checker 被注册
- THEN registry SHALL 能提供 HTTP defaults、HTTP target 和 HTTP expect 的 TypeBox 契约片段
Scenario: Command checker 提供契约片段
- WHEN Command checker 被注册
- THEN registry SHALL 能提供 Command defaults、Command target 和 Command expect 的 TypeBox 契约片段
Scenario: 新 checker 只维护自身契约
- WHEN 开发者新增一个 checker 类型
- THEN 该 checker SHALL 提供自身 TypeBox 配置契约和语义 validator,而不需要把 checker 专属字段写入中央手工校验逻辑
Scenario: 外部 schema 通过 registry 生成
- WHEN 系统生成
probe-config.schema.json - THEN 生成流程 SHALL 从 registry 获取已注册 checker 的契约片段,并将其组合进完整配置 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 在 src/server/checker/runner/types.ts 中定义面向扩展的 CheckerDefinition,包含 type、configKey、TypeBox 配置契约、启动期语义校验、resolve、execute、serialize 成员。CheckerContext SHALL 包含引擎注入的 AbortSignal。
Scenario: Checker 接口包含必要方法
- WHEN 开发者实现一个新的 Checker
- THEN 该实现 MUST 提供
type(字符串标识)、configKey(配置分组名)、TypeBox 配置契约、启动期语义校验、resolve(target, context)(解析配置并填充默认值)、execute(target, ctx)(执行探测返回 CheckResult)和serialize(target)(返回 target 展示文本和 config JSON)
Scenario: CheckerContext 注入 signal
- WHEN 引擎调用
checker.execute(target, ctx) - THEN
ctx.signalSHALL 是一个由引擎创建的AbortSignal,在超时或引擎关闭时 abort
Scenario: resolve 不承担通用契约校验
- WHEN config-loader 调用 checker.resolve()
- THEN checker.resolve() SHALL 假定配置已经通过 TypeBox/Ajv 契约校验和启动期语义校验,只负责默认值填充、路径解析和领域配置转换
Scenario: type 与 configKey 默认一致
- WHEN checker 定义
type: "tcp" - THEN checker 的
configKeySHALL 默认使用"tcp",对应 target 的tcp分组和 defaults.tcp 分组
Requirement: CheckerRegistry 注册中心
系统 SHALL 在 src/server/checker/runner/registry.ts 中提供 CheckerRegistry 类,支持 register(checker)、get(type) 和 supportedTypes。重复注册同一 type SHALL 抛出错误。
Scenario: 注册并获取 Checker
- WHEN 调用
registry.register(new HttpChecker())后再调用registry.get("http") - THEN 返回的 SHALL 是之前注册的 HttpChecker 实例
Scenario: 获取未注册的 type
- WHEN 调用
registry.get("unknown")且未注册对应 type 的 checker - THEN 系统 SHALL 抛出错误,提示不支持的 probe type
Scenario: 重复注册
- WHEN 同一 type 值被重复
register() - THEN 系统 SHALL 抛出错误,提示该 type 已注册
Scenario: 查询支持的 type 列表
- WHEN 注册了 "http" 和 "command" 两个 checker 后查询
registry.supportedTypes - THEN 返回的数组 SHALL 包含
["http", "command"](按注册顺序)
Requirement: 引擎通过 registry 调度 checker
系统 SHALL 在 ProbeEngine.runCheck() 中通过 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 在 config-loader.ts 的配置加载流程中通过 checkerRegistry 发现已注册 checker,组合公共 TypeBox 契约与 checker 契约,并将 checker 专属语义校验和解析委托给对应 checker。validateConfig() SHALL 仅保留公共语义校验(name 非空、name 不重复、group 类型、type 已注册等)和契约调度职责,不包含 checker 专属字段校验。
Scenario: 配置契约通过 registry 组合
- WHEN config-loader 校验配置文件
- THEN config-loader SHALL 从
checkerRegistry获取已注册 checker 的契约片段,并用于校验 defaults 与 targets 中对应 checker 的配置形状
Scenario: 配置解析委托 checker
- WHEN config-loader 解析一个 type 为 "command" 的 target
- THEN config-loader SHALL 调用
checkerRegistry.get("command")获取对应 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 在 ProbeStore.syncTargets() 中通过 checkerRegistry.get(t.type).serialize(t) 获取每个 target 的展示摘要(target 列)和配置 JSON(config 列),替代 buildTargetDisplay() / buildTargetConfig() 中的类型分支。
Scenario: 序列化委托 checker
- WHEN store 同步 targets 表
- THEN store SHALL 对每个 target 调用对应 checker 的
serialize()方法获取{ target, config }
Requirement: 共享 expect 断言函数
系统 SHALL 在 src/server/checker/runner/shared/ 中提供可被多个 checker 复用的 expect 函数。checker 专用的 expect 函数 SHALL 保留在各自子包内。
Scenario: 共享 duration 断言
- WHEN 任何 checker 需要校验执行耗时
- THEN SHALL 调用
runner/shared/duration.ts中的checkDuration(durationMs, maxDurationMs?),返回统一的ExpectResult
Scenario: 共享 text 规则断言
- WHEN 任何 checker 需要对文本输出执行有序规则校验
- THEN SHALL 调用
runner/shared/text.ts中的checkTextRules(text, rules, phase),返回统一的ExpectResult
Scenario: 共享 body 规则断言
- WHEN 任何 checker 需要对文本体执行 contains/regex/json/css/xpath 规则校验
- THEN SHALL 调用
runner/shared/body.ts中的checkBodyExpect(body, rules),返回统一的ExpectResult
Scenario: HTTP 专用 expect
- WHEN HTTP checker 需要校验响应状态码和响应头
- THEN SHALL 调用
runner/http/expect.ts中的checkStatus()和checkHeaders()
Scenario: Command 专用 expect
- WHEN Command checker 需要校验退出码
- THEN SHALL 调用
runner/command/expect.ts中的checkExitCode()
Requirement: 超时控制由引擎注入 signal
Checker 实现的 execute() MUST 使用 ctx.signal 感知超时,不得自行创建 AbortController 或 setTimeout 用于超时控制。仅 command checker 可在 signal abort 时 proc.kill() 以确保子进程被终止。
Scenario: HTTP checker 使用 signal
- WHEN HttpChecker 执行 HTTP 请求
- THEN SHALL 将
ctx.signal传入fetch()的signal选项,不自行创建AbortController
Scenario: Command checker 响应 signal
- WHEN CommandChecker 执行命令且 signal 被 abort
- THEN SHALL 调用
proc.kill()终止子进程,并在 CheckResult 中记录超时错误
Requirement: CheckFailure.phase 使用 string 类型
shared/api.ts 中 CheckFailure.phase 的类型 SHALL 定义为 string,替代原有的硬编码联合类型 "status" | "duration" | "headers" | "body" | "exitCode" | "stdout" | "stderr"。
Scenario: phase 支持 checker 专用值
- WHEN command checker 在执行失败(spawn error)时生成 failure
- THEN
failure.phaseSHALL 可以是"spawn"等任意字符串值,类型系统 SHALL 不报错
Scenario: 前端展示 phase 不依赖硬编码类型
- WHEN 前端收到任意 phase 字符串值
- THEN 前端 SHALL 直接展示而不做类型判断