1
0
Files
DiAL/openspec/specs/checker-observation/spec.md
lanyuanxiaoyao 375dd3492b feat: 结构化 observation 替代 statusDetail,API 层动态构造 detail
- CheckResult: statusDetail -> observation (持久化) + detail (API 动态派生)
- 存储: status_detail 列 -> observation TEXT (JSON)
- CheckerDefinition: 新增 buildDetail(observation) 方法
- 各 checker 返回结构化 observation,API 层通过 registry 调用 buildDetail
- HTTP: bodyPreview 在 status/header 失败时也提前采集
- UDP: observation 包含 durationMs,未响应归为 error failure
- CMD: 超时/输出超限时保留已收集 observation
- TCP: connectTimeMs 仅含连接建立耗时,不含 banner 等待
- 新增 buildDetail 单测和 mapCheckResult 覆盖测试
- 同步 openspec 主规范,归档 checker-observation 变更
2026-05-19 22:49:00 +08:00

159 lines
8.7 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
定义 CheckResult 的 observation 数据模型、各 checker 类型 observation 结构、截断策略、序列化规则和 detail 动态构造机制。
## Requirements
### Requirement: Observation 数据模型
CheckResult SHALL 包含 `observation: Record<string, unknown> | null` 字段,用于承载 checker 执行过程中收集的结构化观测数据。observation 为 null 表示执行过程中无法形成有意义的领域观测数据(如进程 spawn 失败、内部异常、请求在拿到响应前失败且无可记录元数据等场景)。各 checker SHALL 自行定义 observation 的内部结构,不做跨 checker 类型的统一约束。
#### Scenario: 正常执行返回 observation
- **WHEN** checker 执行成功、expect 断言失败或产生可收集上下文的负向结果
- **THEN** CheckResult.observation SHALL 包含该 checker 类型定义的完整观测数据
#### Scenario: 异常执行返回 null observation
- **WHEN** checker 执行过程中发生无法收集领域观测数据的异常
- **THEN** CheckResult.observation SHALL 为 null
### Requirement: HTTP Checker Observation
HTTP checker 的 observation SHALL 包含 statusCodenumber、headersRecord<string, string>截断、bodyPreviewstring | null截断、contentTypestring | null、contentLengthnumber | null
#### Scenario: 正常 HTTP 响应
- **WHEN** HTTP 请求成功返回
- **THEN** observation SHALL 包含响应状态码、截断后的响应 headers、响应体预览、Content-Type 和 Content-Length即使未配置 body expect 也 SHALL 采集 bodyPreview
#### Scenario: HTTP 请求失败
- **WHEN** HTTP 请求因网络错误或超时失败
- **THEN** observation SHALL 为 null
### Requirement: TCP Checker Observation
TCP checker 的 observation SHALL 包含 connectedboolean、connectTimeMsnumber | null、bannerstring | null截断、errorstring | null
#### Scenario: TCP 连接成功且读取 banner
- **WHEN** TCP 连接成功并读取到 banner
- **THEN** observation SHALL 包含 connected=true、连接耗时、截断后的 banner 内容
#### Scenario: TCP 连接失败
- **WHEN** TCP 连接失败
- **THEN** observation SHALL 包含 connected=false 和错误信息
### Requirement: UDP Checker Observation
UDP checker 的 observation SHALL 包含 respondedboolean、durationMsnumber、responseSizenumber | null、responsePreviewstring | null截断、sourceAddressstring | null、sourcePortnumber | null、errorstring | null。durationMs 用于 API 序列化层生成包含耗时的 UDP detail 摘要。
#### Scenario: UDP 收到响应
- **WHEN** UDP 发送数据后收到响应
- **THEN** observation SHALL 包含 responded=true、响应大小、截断后的响应预览、来源地址和端口
#### Scenario: UDP 未收到响应
- **WHEN** UDP 发送数据后超时未收到响应
- **THEN** observation SHALL 包含 responded=false
### Requirement: Ping Checker Observation
Ping/ICMP checker 的 observation SHALL 包含 aliveboolean、transmittednumber、receivednumber、packetLossnumber、avgLatencyMsnumber | null、maxLatencyMsnumber | null、minLatencyMsnumber | null、errorstring | null。API registry type SHALL 仍为 `ping`
#### Scenario: Ping 正常返回统计
- **WHEN** ping 命令成功执行并解析出统计数据
- **THEN** observation SHALL 包含完整的 PingStats 字段
#### Scenario: Ping 命令失败
- **WHEN** ping 命令未找到或超时
- **THEN** observation SHALL 为 null 或包含 error 字段
### Requirement: DB Checker Observation
DB checker 的 observation SHALL 包含 connectedboolean、rowCountnumber | null、rowsPreviewunknown[] | null截断前 N 行、errorstring | null
#### Scenario: 数据库查询成功
- **WHEN** 数据库连接和查询成功
- **THEN** observation SHALL 包含 connected=true、行数、截断后的行预览
#### Scenario: 仅探活无查询
- **WHEN** 数据库配置仅探活连接(无 query
- **THEN** observation SHALL 包含 connected=truerowCount 和 rowsPreview 为 null
### Requirement: CMD Checker Observation
CMD checker 的 observation SHALL 包含 exitCodenumber | null、stdoutPreviewstring | null截断、stderrPreviewstring | null截断、errorstring | null
#### Scenario: 命令正常执行
- **WHEN** 命令执行完成
- **THEN** observation SHALL 包含退出码、截断后的 stdout 和 stderr 预览
#### Scenario: 命令 spawn 失败
- **WHEN** 命令进程无法启动
- **THEN** observation SHALL 为 null
### Requirement: LLM Checker Observation
LLM checker SHALL 保留执行期 `LlmCheckObservation.outputText` 完整文本用于 expect 校验,并从执行期 observation 派生持久化 observation。持久化 observation SHALL 包含 providerstring、modestring、modelstring、http{ status, statusText, headers } | nullheaders 截断、finishReasonstring | null、rawFinishReasonstring | null、outputPreviewstring | null截断、outputLengthnumber | null、usage{ inputTokens, outputTokens, totalTokens } | null、stream{ completed, firstTokenMs } | null、warningsstring[])。
#### Scenario: LLM HTTP 模式成功
- **WHEN** LLM 以 HTTP 模式成功执行
- **THEN** observation SHALL 包含 provider、mode、model、HTTP 元数据、finish 原因、截断后的输出预览、完整输出长度和 token 用量
#### Scenario: LLM Stream 模式成功
- **WHEN** LLM 以 stream 模式成功执行
- **THEN** observation SHALL 额外包含 stream 观测数据completed、firstTokenMs
### Requirement: Observation 截断策略
各 checker SHALL 对 observation 中的大文本和集合字段执行截断,防止存储膨胀。
#### Scenario: HTTP body 截断
- **WHEN** HTTP 响应体超过 1024 字符
- **THEN** bodyPreview SHALL 截断为前 1024 字符
#### Scenario: HTTP headers 截断
- **WHEN** HTTP 响应 headers 超过 20 个
- **THEN** headers SHALL 仅保留前 20 个
#### Scenario: TCP banner 截断
- **WHEN** TCP banner 内容超过 256 字符
- **THEN** banner SHALL 截断为前 256 字符
#### Scenario: UDP response 截断
- **WHEN** UDP 响应预览超过 512 字符
- **THEN** responsePreview SHALL 截断为前 512 字符
#### Scenario: DB rows 截断
- **WHEN** 查询返回超过 5 行
- **THEN** rowsPreview SHALL 仅保留前 5 行
#### Scenario: CMD stdout/stderr 截断
- **WHEN** stdout 或 stderr 超过 1024 字符
- **THEN** 对应 Preview 字段 SHALL 截断为前 1024 字符
#### Scenario: LLM output 截断
- **WHEN** LLM 输出文本超过 512 字符
- **THEN** outputPreview SHALL 截断为前 512 字符
#### Scenario: LLM headers 截断
- **WHEN** LLM 响应 headers 超过 20 个
- **THEN** headers SHALL 仅保留前 20 个
### Requirement: Observation 序列化规则
observation SHALL 使用 JSON.stringify 序列化为 TEXT 格式存入 SQLite使用 JSON.parse 反序列化读出。不引入额外的序列化依赖。
#### Scenario: 写入 observation
- **WHEN** 存储 CheckResult 到数据库
- **THEN** observation 字段 SHALL 使用 JSON.stringify 序列化后存入 TEXT 列observation 为 null 时 SHALL 存入 SQL NULL
#### Scenario: 读取 observation
- **WHEN** 从数据库读取 CheckResult
- **THEN** observation 字段 SHALL 使用 JSON.parse 反序列化SQL NULL 值 SHALL 映射为 null
#### Scenario: 使用 SQLite CLI 直接查看
- **WHEN** 开发者使用 sqlite3 CLI 工具查看 check_results 表
- **THEN** observation 列 SHALL 为人可读的 JSON 文本
### Requirement: Detail 动态构造
CheckResult SHALL 包含 `detail: string | null` 字段(替代原 statusDetail该字段 SHALL 不持久化到数据库,而是在 API 序列化层根据 target type 从 observation 动态构造。每个 checker SHALL 提供 `buildDetail(observation)` 方法,定义该 checker 类型的人可读摘要格式。
#### Scenario: API 返回 detail 字段
- **WHEN** API 序列化 CheckResult 返回给前端
- **THEN** 系统 SHALL 根据 target type 调用对应 checker 的 buildDetail 方法,从 observation 动态生成 detail 字段
#### Scenario: observation 为 null 时
- **WHEN** observation 为 null
- **THEN** detail SHALL 为 null
#### Scenario: 各 checker 的 detail 格式
- **WHEN** 各 checker 的 buildDetail 被调用
- **THEN** HTTP SHALL 返回 `"HTTP {statusCode}"` 格式TCP SHALL 返回连接状态和 banner 摘要UDP SHALL 返回响应状态和大小摘要Ping SHALL 返回存活状态、平均延迟和丢包率摘要DB SHALL 返回连接状态或行数摘要CMD SHALL 返回 `"exitCode={N}"` 格式LLM SHALL 返回 provider、mode、状态码、finish 原因、输出长度和 token 用量摘要