1
0
Files
DiAL/openspec/specs/checker-cohesion-structure/spec.md
lanyuanxiaoyao cfca03b4d6 refactor: 规范审查与重组,合并细粒度规范,清理过时内容
- 合并 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 审查提示文档
2026-05-22 18:55:18 +08:00

19 KiB
Raw Blame History

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 表示配置校验问题,至少包含 codepathmessage,并支持可选 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>,包含 typeconfigKey、Authoring/Normalized TypeBox 配置契约、启动期语义校验、resolveexecuteserializebuildDetail 成员。泛型参数 SHALL 约束 executeserialize 方法的 target 参数类型,使 checker 实现内部获得编译期类型安全。默认泛型参数 = ResolvedTargetBase 保证中间层registry、engine、config-loader无需指定泛型。

Scenario: Checker 接口包含必要方法

  • WHEN 开发者实现一个新的 Checker
  • THEN 该实现 MUST 提供 type(字符串标识)、configKey配置分组名、Authoring/Normalized TypeBox 配置契约、启动期语义校验、resolve(target, context)(解析配置并填充默认值)、execute(target, ctx)(执行探测返回 CheckResultserialize(target)(返回 target 展示文本和 config JSONbuildDetail(observation)(从 observation 构造人可读摘要)

Scenario: CheckerContext 注入 signal

  • WHEN 引擎调用 checker.execute(target, ctx)
  • THEN ctx.signal SHALL 是一个由引擎创建的 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 的 configKey SHALL 默认使用 "tcp",对应 target 的 tcp 分组

Scenario: 接口方法使用泛型约束

  • WHEN 开发者查看 CheckerDefinition<TResolved> 接口签名
  • THEN resolve 的返回值 SHALL 为 TResolvedexecute 的参数 SHALL 为 TResolvedserialize 的参数 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 写入 NULLMUST NOT 依赖 rawExpect 或 Raw expect 快照

Requirement: Checker resolve 只接收已去糖配置

每个 checker 的 resolve() SHALL 接收已通过 Normalized schema 和语义校验的配置不再包含变量引用、Authoring expect primitive 简写或 Raw content/keyed DSL。config-loader SHALL 继续通过 registry 委托 checker resolveMUST 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 工具执行 equalscontainsregexexistsemptygtgteltlte 语义

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 断言函数,支持精确状态码和 1xx5xx 范围模式

Requirement: 超时控制由引擎注入 signal

Checker 实现的 execute() MUST 使用 ctx.signal 感知超时,不得自行创建 AbortControllersetTimeout 用于超时控制。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.phase SHALL 可以是 "spawn" 等任意字符串值,类型系统 SHALL 不报错

Scenario: 前端展示 phase 不依赖硬编码类型

  • WHEN 前端收到任意 phase 字符串值
  • THEN 前端 SHALL 直接展示而不做类型判断