# Checker 开发

Checker 是 DiAL 的核心扩展单元。每个 checker 是 `src/server/checker/runner/<type>/` 下的自包含目录，包含类型、schema、语义校验、执行逻辑、序列化和断言。

适用场景：新增 checker、修改 checker 配置或 expect、调整 checker 注册机制、改动 checker 测试或用户文档同步规则。

新增或修改 checker 前必须阅读 [开发入口](README.md)、[配置文件](../user/configuration.md)、[校验规则](../user/expectations.md) 和 [Checker 用户文档](../user/checkers/README.md)。还应阅读现有同类 checker 的实现和测试，例如 `src/server/checker/runner/http/` 与 `tests/server/checker/runner/http/`。

## 设计原则

- 每个 checker 必须自包含在 `src/server/checker/runner/<type>/`。
- checker 专属类型、schema、validate、execute、expect、normalize 和协议辅助逻辑放在同一目录。
- 注册只修改 `src/server/checker/runner/index.ts`，中间层不新增 type switch。
- schema 层只描述契约，语义规则放入 `validate.ts`。
- `resolve()` 只做默认值填充、路径解析和单位转换，不执行校验。
- `execute()` 必须支持 `CheckerContext.signal` 超时取消。
- expect 字段必须选择合适断言模型，不为了统一而滥用 ValueMatcher。
- failure phase 命名遵循去单位后缀规则，例如 `durationMs` 对应 `duration`。

## 架构目标

```text
checkerRegistry
  ├── runner/index.ts
  ├── schema/builder.ts
  ├── schema/validate.ts
  ├── config-loader.ts
  ├── engine.ts
  └── store.ts
```

注册后，中间层通过 registry 自动委托 schema 生成、契约校验、配置 normalize、配置 resolve、执行和序列化。新增 checker 不应在中间层新增 `switch/case` 或类型分支。

## 标准文件结构

| 文件           | 职责                                                    |
| -------------- | ------------------------------------------------------- |
| `index.ts`     | 模块入口，re-export Checker 类                          |
| `types.ts`     | Checker 专属类型                                        |
| `schema.ts`    | TypeBox 契约 schema，包含 config 和 expect              |
| `validate.ts`  | 启动期语义校验                                          |
| `normalize.ts` | Checker 专属 authoring expect 归一化                    |
| `execute.ts`   | Checker 类，实现 normalize、resolve、execute、serialize |
| `expect.ts`    | Checker 专用断言函数                                    |
| 其他文件       | 协议解析、编码、provider 适配、平台命令封装等专属逻辑   |

## 类型定义

在 `types.ts` 中定义：

- `RawXxxTargetConfig`
- `RawXxxExpectConfig`
- `ResolvedXxxExpectConfig`
- `ResolvedXxxTarget extends ResolvedTargetBase`

不需要修改顶层 `checker/types.ts`。base interface 使用 index signature 支持扩展。

## Schema

checker 必须提供 `CheckerSchemas`，包含 Authoring 和 Normalized 两套 config/expect 片段。Authoring 描述用户 YAML 可写 DSL，Normalized 描述 normalizer 输出。

常用 fragments：

| Fragment                            | 用途                      |
| ----------------------------------- | ------------------------- |
| `durationSchema`                    | 时长字符串                |
| `sizeSchema`                        | 大小单位                  |
| `statusCodePatternSchema`           | HTTP 状态码或范围         |
| `stringMapSchema`                   | headers、env 等字符串映射 |
| `createValueMatcherSchema()`        | ValueMatcher              |
| `createContentExpectationsSchema()` | ContentExpectations       |
| `createKeyedExpectationsSchema()`   | KeyedExpectations         |

默认对象策略为 `additionalProperties: false`。只有明确的动态键值表可以开放任意键名。

## 语义校验

在 `validate.ts` 中实现 JSON Schema 无法表达的规则，统一返回 `ConfigValidationIssue[]`，不要直接拼接最终错误字符串。

共享校验工具包括：

| 函数                             | 用途                         |
| -------------------------------- | ---------------------------- |
| `validateRawValueExpectation`    | 校验 Raw ValueExpectation    |
| `validateRawContentExpectations` | 校验 ContentExpectations     |
| `validateRawKeyedExpectations`   | 校验 KeyedExpectations       |
| `validateJsonPath`               | 校验项目支持的 JSONPath 子集 |
| `isJsonValue`                    | 判断合法 JSON value          |

## normalize 规范

`normalize()` 在 `CheckerDefinition` 中定义为必需方法，负责将 authoring expect DSL 转换为 normalized 形态。输入为变量已解析后的 target，输出为适配 normalized schema 的 target。该方法在 `resolve()` 和 normalized contract 校验之前执行。

在 `normalize.ts` 中实现 `normalizeTargetExpect` 函数，`execute.ts` 中的 `normalize` 方法委托到该函数。

共享 normalize helper 位于 `src/server/checker/expect/normalize.ts`：

| 函数               | 用途                                                     |
| ------------------ | -------------------------------------------------------- |
| `compactExpect`    | 合并两个 expect record，过滤 undefined 字段              |
| `normalizeValue`   | ValueMatcher 原始值简写展开为 `{equals: value}`          |
| `normalizeContent` | ContentExpectations 简写展开为 normalized 形态           |
| `normalizeKeyed`   | KeyedExpectations 对象形态展开为 `[{key, matcher}]` 数组 |

```typescript
import { compactExpect, normalizeContent, normalizeKeyed, normalizeValue } from "../../expect/normalize";

export function normalizeTargetExpect(target: RawTargetConfig): RawTargetConfig {
  if (target.expect === undefined || !isPlainObject(target.expect)) return target;
  const raw = target.expect as Record<string, unknown>;
  return {
    ...target,
    expect: compactExpect(raw, {
      /* checker 专属字段映射 */
    }),
  };
}
```

expect 字段的归一化规则：ValueMatcher 字段调用 `normalizeValue()`，ContentExpectations 字段调用 `normalizeContent()`，KeyedExpectations 字段调用 `normalizeKeyed()`，boolean/enum/array 等非断言模型字段直接透传。

## resolve 规范

`resolve()` 只做内置默认值填充、路径解析、单位转换，不执行校验。输入已经通过 Normalized schema 和语义校验，expect 已是 normalized 形态。

```typescript
const expect = target.expect as ResolvedXxxExpectConfig | undefined;
const resolvedExpect: ResolvedXxxExpectConfig = expect
  ? { ...expect, status: expect.status ?? [200] }
  : { status: [200] };
```

返回值使用 `satisfies ResolvedXxxTarget` 确保类型正确。

## execute 规范

- 始终记录 `timestamp` 和 `start = performance.now()`。
- 通过 `ctx.signal` 支持超时取消。
- 首个 expect 失败即停止，返回带 `failure` 的结果。
- 成功时 `failure: null, matched: true`。
- 异常时使用 `errorFailure()`。
- 不匹配时使用 `mismatchFailure()`。
- `expected` 参数应传用户可读值，必要时使用 `displayValueExpectation()`。

## expect 字段选择

| 场景                                 | 模型                |
| ------------------------------------ | ------------------- |
| 状态类结果且集合小而稳定             | enum 或 boolean     |
| 单值数字指标或字符串元数据           | ValueMatcher        |
| 文本、JSON、HTML、XML 或半结构化内容 | ContentExpectations |
| 动态键值表                           | KeyedExpectations   |

不要为了统一而把状态类字段改成 ValueMatcher。一个 expect 字段只能对应一种断言模型。

## 注册

1. 创建 `src/server/checker/runner/<type>/index.ts`。
2. 在 `src/server/checker/runner/index.ts` 添加导入。
3. 在 registry 初始化数组中添加 checker 实例。

注册后，schema builder、validate、config-loader、engine、store 会自动按 registry 分发。

## 测试要求

测试文件放在 `tests/server/checker/runner/<type>/`，结构镜像源文件。

| 测试类别       | 覆盖内容                                             |
| -------------- | ---------------------------------------------------- |
| 契约测试       | TypeBox schema 与 JSON Schema 导出一致性             |
| 语义校验测试   | 合法和非法配置                                       |
| normalize 测试 | authoring expect 简写展开和 normalized contract 通过 |
| resolve 测试   | 默认值合并、路径解析、单位转换                       |
| execute 测试   | 成功、失败、超时、expect 组合                        |
| 注册测试       | registry 注册行为                                    |
| 配置加载测试   | 含新 checker 的 YAML 完整加载流程                    |

## 文档和 schema 更新

新增或修改 checker 时通常需要更新：

- `probes.example.yaml`
- `probe-config.schema.json`，通过 `bun run schema` 生成
- `docs/user/checkers/<type>.md`
- `docs/user/checkers/README.md`
- `docs/user/expectations.md`，仅当断言模型、状态模型或通用规则变化
- `docs/user/configuration.md`，仅当 target 通用字段或配置加载形态变化
- `docs/development/checker.md`，仅当 checker 开发机制、测试要求或 checklist 变化
- `docs/README.md` 和 `openspec/config.yaml`，仅当文档同步规则变化

## 验证命令

新增或修改 checker 后通常需要运行：

```bash
bun run schema
bun run schema:check
bun run check
```

影响构建、Docker 或发布包时追加运行 `bun run verify`。

## 完成检查清单

```text
□ checker 类型、schema、validate、normalize、resolve、execute、serialize 已实现
□ checker 已在 runner/index.ts 注册
□ 配置契约、语义校验和 JSON Schema 导出已同步
□ probes.example.yaml 已添加或更新示例
□ tests/server/checker/runner/<type>/ 已覆盖契约、校验、normalize、resolve、execute、注册和配置加载
□ docs/user/checkers/<type>.md 已添加或更新
□ docs/user/checkers/README.md 已添加或更新
□ 文档影响分析已完成，必要文档已同步
□ bun run schema 和 bun run schema:check 已通过
□ bun run check 已通过
□ bun run verify 已通过或记录未执行原因
```

## 更新触发条件

修改 checker 开发机制、目录结构、schema/validate/normalize/resolve/execute/expect 约定、测试要求、验证命令或文档同步 checklist 时，必须更新本文档。
