1
0
Files
DiAL/openspec/specs/checker-runner-abstraction/spec.md

122 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Purpose
定义 Checker 接口规范、注册机制、CheckerContext 上下文注入,以及共享 expect 断言函数的职责边界。此 capability 是 checker 系统的架构基础,不定义任何具体 checker 类型的业务行为。
## Requirements
### Requirement: Checker 接口定义
系统 SHALL 在 `src/server/checker/runner/types.ts` 中定义 `Checker` 接口,包含 `type``resolve``execute``serialize` 四个成员。`CheckerContext` SHALL 包含引擎注入的 `AbortSignal`
#### Scenario: Checker 接口包含必要方法
- **WHEN** 开发者实现一个新的 Checker
- **THEN** 该实现 MUST 提供 `type`(字符串标识)、`resolve(target, context)`(解析配置并校验)、`execute(target, ctx)`(执行探测返回 CheckResult`serialize(target)`(返回 target 展示文本和 config JSON
#### Scenario: CheckerContext 注入 signal
- **WHEN** 引擎调用 `checker.execute(target, ctx)`
- **THEN** `ctx.signal` SHALL 是一个由引擎创建的 `AbortSignal`,在超时或引擎关闭时 abort
### 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``resolveTarget()` 中通过 `checkerRegistry.get(target.type).resolve(target, context)` 委托解析,替代原有的 `if/else` 分支。`validateConfig()` SHALL 仅校验通用字段name 非空、name 不重复、group 类型),不再包含 type 专属字段校验。
#### Scenario: 配置解析委托 checker
- **WHEN** config-loader 解析一个 type 为 "command" 的 target
- **THEN** config-loader SHALL 调用 `checkerRegistry.get("command").resolve()` 进行解析、校验和默认值填充
#### Scenario: 通用字段校验保留在 config-loader
- **WHEN** YAML 配置中某个 target 缺少 name 或 type 字段
- **THEN** config-loader 的 `validateConfig()` SHALL 仍负责校验这些通用字段
#### Scenario: type 专属校验下沉到 checker
- **WHEN** YAML 配置中 HTTP target 缺少 `http.url`
- **THEN** HttpChecker 的 `resolve()` SHALL 抛出校验错误,提示缺少必填字段
#### Scenario: HTTP method 非法校验
- **WHEN** YAML 配置中 HTTP target 的 `http.method` 不在合法方法列表中
- **THEN** HttpChecker 的 `resolve()` SHALL 抛出校验错误,提示 method 不合法
#### Scenario: URL 格式校验
- **WHEN** YAML 配置中 HTTP target 的 `http.url` 不以 `http://``https://` 开头
- **THEN** HttpChecker 的 `resolve()` 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.phase` SHALL 可以是 `"spawn"` 等任意字符串值,类型系统 SHALL 不报错
#### Scenario: 前端展示 phase 不依赖硬编码类型
- **WHEN** 前端收到任意 phase 字符串值
- **THEN** 前端 SHALL 直接展示而不做类型判断