## Purpose 定义 CheckResult 的 observation 数据模型、各 checker 类型 observation 结构、截断策略、序列化规则和 detail 动态构造机制。 ## Requirements ### Requirement: Observation 数据模型 CheckResult SHALL 包含 `observation: Record | 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,截断)、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 用量摘要