## 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 抛出校验错误,提示缺少必填字段 ### 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 直接展示而不做类型判断