feat: 重构配置生命周期为 Authoring/Normalized/Resolved 三层
将变量替换和 expect 简写展开统一放入 Normalized 阶段, 运行时 AJV 使用 Normalized schema,导出 schema 面向 Authoring Config。 主要变更: - 新增 normalizer.ts 实现 normalizeAuthoringConfig() - 拆分 Authoring/Normalized 双 schema,checker 接口支持 authoring/normalized 片段 - config-loader 流程:normalize → Normalized AJV → semantic → resolve - validator 兼容层自动分派 raw/normalized expect 形态 - 删除 rawExpect,store.expect 列写入 null - Authoring schema 对 integer/boolean/enum 字段接受变量引用 - 修复 DB/HTTP validate 入口守卫和 LLM options integer 变量引用 - 优化 compact() 避免 undefined 覆盖隐患 - 移除 content.ts 恒为 true 的前置条件 - 同步 5 个主规范并归档 change
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: Checker 配置契约片段
|
||||
系统 SHALL 支持 checker 提供自身 TypeBox 配置契约片段,用于描述该 checker 的 target 领域分组和 expect 分组。公共配置加载模块 SHALL 通过 registry 获取已注册 checker 的契约片段,并组合为启动期 Ajv 契约校验流程和外部 `probe-config.schema.json` 导出流程。
|
||||
系统 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 被注册
|
||||
@@ -21,7 +21,11 @@
|
||||
|
||||
#### Scenario: 外部 schema 通过 registry 生成
|
||||
- **WHEN** 系统生成 `probe-config.schema.json`
|
||||
- **THEN** 生成流程 SHALL 从 registry 获取已注册 checker 的契约片段,并将其组合进完整配置 schema
|
||||
- **THEN** 生成流程 SHALL 从 registry 获取已注册 checker 的 Authoring 契约片段,并将其组合进完整配置 schema
|
||||
|
||||
#### Scenario: 运行时 schema 通过 registry 生成
|
||||
- **WHEN** config-loader 执行运行时 AJV 契约校验
|
||||
- **THEN** 校验流程 SHALL 从 registry 获取已注册 checker 的 Normalized 契约片段,并将其组合进完整配置 schema
|
||||
|
||||
#### Scenario: 契约组装不依赖全局 singleton
|
||||
- **WHEN** 测试或 schema 生成流程需要组装配置契约
|
||||
@@ -50,11 +54,11 @@
|
||||
- **THEN** checker SHALL 返回 `ConfigValidationIssue`,而不是直接抛出最终用户错误字符串
|
||||
|
||||
### Requirement: Checker 接口定义
|
||||
系统 SHALL 在 `src/server/checker/runner/types.ts` 中定义面向扩展的泛型 `CheckerDefinition<TResolved extends ResolvedTargetBase = ResolvedTargetBase>`,包含 `type`、`configKey`、TypeBox 配置契约、启动期语义校验、`resolve`、`execute`、`serialize`、`buildDetail` 成员。泛型参数 SHALL 约束 `execute` 和 `serialize` 方法的 target 参数类型,使 checker 实现内部获得编译期类型安全。默认泛型参数 `= ResolvedTargetBase` 保证中间层(registry、engine、config-loader)无需指定泛型。
|
||||
系统 SHALL 在 `src/server/checker/runner/types.ts` 中定义面向扩展的泛型 `CheckerDefinition<TResolved extends ResolvedTargetBase = ResolvedTargetBase>`,包含 `type`、`configKey`、Authoring/Normalized TypeBox 配置契约、启动期语义校验、`resolve`、`execute`、`serialize`、`buildDetail` 成员。泛型参数 SHALL 约束 `execute` 和 `serialize` 方法的 target 参数类型,使 checker 实现内部获得编译期类型安全。默认泛型参数 `= ResolvedTargetBase` 保证中间层(registry、engine、config-loader)无需指定泛型。
|
||||
|
||||
#### Scenario: Checker 接口包含必要方法
|
||||
- **WHEN** 开发者实现一个新的 Checker
|
||||
- **THEN** 该实现 MUST 提供 `type`(字符串标识)、`configKey`(配置分组名)、TypeBox 配置契约、启动期语义校验、`resolve(target, context)`(解析配置并填充默认值)、`execute(target, ctx)`(执行探测返回 CheckResult)、`serialize(target)`(返回 target 展示文本和 config JSON)和 `buildDetail(observation)`(从 observation 构造人可读摘要)
|
||||
- **THEN** 该实现 MUST 提供 `type`(字符串标识)、`configKey`(配置分组名)、Authoring/Normalized TypeBox 配置契约、启动期语义校验、`resolve(target, context)`(解析配置并填充默认值)、`execute(target, ctx)`(执行探测返回 CheckResult)、`serialize(target)`(返回 target 展示文本和 config JSON)和 `buildDetail(observation)`(从 observation 构造人可读摘要)
|
||||
|
||||
#### Scenario: CheckerContext 注入 signal
|
||||
- **WHEN** 引擎调用 `checker.execute(target, ctx)`
|
||||
@@ -62,7 +66,11 @@
|
||||
|
||||
#### Scenario: resolve 不承担通用契约校验
|
||||
- **WHEN** config-loader 调用 checker.resolve()
|
||||
- **THEN** checker.resolve() SHALL 假定配置已经通过 TypeBox/Ajv 契约校验和启动期语义校验,只负责默认值填充、路径解析和领域配置转换
|
||||
- **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"`
|
||||
@@ -125,6 +133,14 @@
|
||||
- **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
|
||||
@@ -146,22 +162,22 @@
|
||||
- **THEN** HttpChecker 的语义校验 SHALL 抛出校验错误,提示 URL 格式不合法
|
||||
|
||||
### Requirement: 存储序列化通过 registry 获取展示格式
|
||||
系统 SHALL 在 `ProbeStore.syncTargets()` 中通过 `checkerRegistry.get(t.type).serialize(t)` 获取每个 target 的展示摘要(`target` 列)和配置 JSON(`config` 列),替代 `buildTargetDisplay()` / `buildTargetConfig()` 中的类型分支。系统 SHALL 将 `targets.expect` 持久化为 resolved target 上的 Raw expect 快照,而不是运行期 Resolved expect 执行计划。
|
||||
系统 SHALL 在 `ProbeStore.syncTargets()` 中通过 `checkerRegistry.get(t.type).serialize(t)` 获取每个 target 的展示摘要(`target` 列)和配置 JSON(`config` 列),替代 `buildTargetDisplay()` / `buildTargetConfig()` 中的类型分支。系统 SHALL 将 `targets.expect` 持久化为 null,不依赖 Raw expect 或 Resolved expect。
|
||||
|
||||
#### Scenario: 序列化委托 checker
|
||||
- **WHEN** store 同步 targets 表
|
||||
- **THEN** store SHALL 对每个 target 调用对应 checker 的 `serialize()` 方法获取 `{ target, config }`
|
||||
|
||||
#### Scenario: expect 持久化使用 rawExpect
|
||||
#### Scenario: expect 持久化不依赖 rawExpect
|
||||
- **WHEN** store 同步带 expect 的 target 到 targets 表
|
||||
- **THEN** store SHALL 将 `rawExpect` 序列化写入 `targets.expect`,MUST NOT 将包含 `kind` 的 Resolved content expectation 写入该列
|
||||
- **THEN** store SHALL 将 `targets.expect` 写入 NULL,MUST NOT 依赖 `rawExpect` 或 Raw expect 快照
|
||||
|
||||
### Requirement: Checker resolve 生成 Raw 与 Resolved expect
|
||||
每个 checker 的 `resolve()` SHALL 在解析 checker 专属 target 配置时,同时保留变量替换后的 Raw expect 快照并生成运行期 Resolved expect 执行计划。Raw expect SHALL 用于配置快照持久化;Resolved expect SHALL 用于 checker `execute()`。`config-loader` SHALL 继续通过 registry 委托 checker resolve,MUST NOT 在中间层理解 checker 专属 expect 字段。
|
||||
### Requirement: Checker resolve 只接收已去糖配置
|
||||
每个 checker 的 `resolve()` SHALL 接收已通过 Normalized schema 和语义校验的配置,不再包含变量引用、Authoring expect primitive 简写或 Raw content/keyed DSL。`config-loader` SHALL 继续通过 registry 委托 checker resolve,MUST NOT 在中间层理解 checker 专属 expect 字段。
|
||||
|
||||
#### Scenario: resolve 输出双 expect 模型
|
||||
- **WHEN** config-loader 解析一个带 `expect.durationMs: 1000` 的 target
|
||||
- **THEN** 对应 checker 的 resolved target SHALL 包含 Raw expect 中的 `durationMs: 1000`,并在 Resolved expect 中包含 `{equals: 1000}` 形式的运行期 matcher
|
||||
#### 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 字段
|
||||
|
||||
Reference in New Issue
Block a user