## 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 包含 statusCode（number）、headers（Record<string, string>，截断）、bodyPreview（string | null，截断）、contentType（string | null）、contentLength（number | 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 包含 connected（boolean）、connectTimeMs（number | null）、banner（string | null，截断）、error（string | 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 包含 responded（boolean）、durationMs（number）、responseSize（number | null）、responsePreview（string | null，截断）、sourceAddress（string | null）、sourcePort（number | null）、error（string | 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 包含 alive（boolean）、transmitted（number）、received（number）、packetLoss（number）、avgLatencyMs（number | null）、maxLatencyMs（number | null）、minLatencyMs（number | null）、error（string | 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 包含 connected（boolean）、rowCount（number | null）、rowsPreview（unknown[] | null，截断前 N 行）、error（string | null）。

#### Scenario: 数据库查询成功
- **WHEN** 数据库连接和查询成功
- **THEN** observation SHALL 包含 connected=true、行数、截断后的行预览

#### Scenario: 仅探活无查询
- **WHEN** 数据库配置仅探活连接（无 query）
- **THEN** observation SHALL 包含 connected=true，rowCount 和 rowsPreview 为 null

### Requirement: CMD Checker Observation
CMD checker 的 observation SHALL 包含 exitCode（number | null）、stdoutPreview（string | null，截断）、stderrPreview（string | null，截断）、error（string | 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 包含 provider（string）、mode（string）、model（string）、http（{ status, statusText, headers } | null，headers 截断）、finishReason（string | null）、rawFinishReason（string | null）、outputPreview（string | null，截断）、outputLength（number | null）、usage（{ inputTokens, outputTokens, totalTokens } | null）、stream（{ completed, firstTokenMs } | null）、warnings（string[]）。

#### 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 用量摘要
