1
0

docs: 新增 checker-observation 变更提案,归档历史 openspec 变更记录

This commit is contained in:
2026-05-19 19:22:16 +08:00
parent 12cd05b04e
commit 22c06820fa
15 changed files with 754 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
## ADDED 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、responseSizenumber | null、responsePreviewstring | null截断、sourceAddressstring | null、sourcePortnumber | null、errorstring | null
#### 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 用量摘要

View File

@@ -0,0 +1,40 @@
## MODIFIED Requirements
### 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无需指定泛型。
#### Scenario: Checker 接口包含必要方法
- **WHEN** 开发者实现一个新的 Checker
- **THEN** 该实现 MUST 提供 `type`(字符串标识)、`configKey`配置分组名、TypeBox 配置契约、启动期语义校验、`resolve(target, context)`(解析配置并填充默认值)、`execute(target, ctx)`(执行探测返回 CheckResult`serialize(target)`(返回 target 展示文本和 config JSON`buildDetail(observation)`(从 observation 构造人可读摘要)
#### Scenario: CheckerContext 注入 signal
- **WHEN** 引擎调用 `checker.execute(target, ctx)`
- **THEN** `ctx.signal` SHALL 是一个由引擎创建的 `AbortSignal`,在超时或引擎关闭时 abort
#### Scenario: resolve 不承担通用契约校验
- **WHEN** config-loader 调用 checker.resolve()
- **THEN** checker.resolve() SHALL 假定配置已经通过 TypeBox/Ajv 契约校验和启动期语义校验,只负责默认值填充、路径解析和领域配置转换
#### Scenario: type 与 configKey 默认一致
- **WHEN** checker 定义 `type: "tcp"`
- **THEN** checker 的 `configKey` SHALL 默认使用 `"tcp"`,对应 target 的 `tcp` 分组和 defaults.tcp 分组
#### Scenario: 接口方法使用泛型约束
- **WHEN** 开发者查看 `CheckerDefinition<TResolved>` 接口签名
- **THEN** `resolve` 的返回值 SHALL 为 `TResolved``execute` 的参数 SHALL 为 `TResolved``serialize` 的参数 SHALL 为 `TResolved`
#### Scenario: checker 实现无需手动断言
- **WHEN** HttpChecker 实现 `CheckerDefinition<ResolvedHttpTarget>`
- **THEN** `execute` 方法的 target 参数类型 SHALL 直接为 `ResolvedHttpTarget`,无需在方法内部使用 `as` 类型断言
#### Scenario: registry 使用默认泛型参数
- **WHEN** CheckerRegistry 存储和返回 checker 实例
- **THEN** registry 内部 SHALL 使用 `CheckerDefinition`(等价于 `CheckerDefinition<ResolvedTargetBase>`),实现类型擦除
#### Scenario: buildDetail 方法签名
- **WHEN** 开发者实现 buildDetail 方法
- **THEN** 方法签名 SHALL 为 `buildDetail(observation: Record<string, unknown>): string | null`,接收 observation 对象并返回人可读摘要字符串或 null
#### Scenario: buildDetail 由 API 层调用
- **WHEN** API 序列化 CheckResult
- **THEN** API 层 SHALL 通过 registry 获取对应 checker 并调用 buildDetail而非由 execute 方法直接生成 detail

View File

@@ -0,0 +1,24 @@
## MODIFIED Requirements
### Requirement: cmd checker 执行
系统 SHALL 按 cmd target 配置执行本地命令记录执行耗时、退出码、stdout 和 stderr observation并在执行失败时产生结构化错误信息。
#### Scenario: 命令正常退出
- **WHEN** cmd target 执行的进程正常退出且 exit code 为 0
- **THEN** 系统 SHALL 记录 `durationMs` 和包含 exitCode、stdoutPreview、stderrPreview 的 observation并进入 expect 校验API detail SHALL 为 `exitCode=0`
#### Scenario: 命令非零退出
- **WHEN** cmd target 执行的进程正常退出但 exit code 为 1
- **THEN** 系统 SHALL 记录包含 exitCode、stdoutPreview、stderrPreview 的 observation并由 expect.exitCode 决定 matched 结果API detail SHALL 为 `exitCode=1`
#### Scenario: 命令启动失败
- **WHEN** cmd target 的 exec 不存在或无法启动
- **THEN** 系统 SHALL 记录 `matched=false`observation SHALL 为 null并在 failure 中写入 kind=`error` 和可读错误信息
#### Scenario: 命令超时
- **WHEN** cmd target 在 timeout 时间内未结束
- **THEN** 系统 MUST 终止该子进程,记录 `matched=false`,并在 failure 中写入命令超时信息如已收集输出片段observation SHALL 包含 stdoutPreview、stderrPreview 和 error
#### Scenario: 命令输出超限
- **WHEN** cmd target 的 stdout 和 stderr 合计输出超过 `maxOutputBytes`
- **THEN** 系统 MUST 停止收集输出并终止该检查,记录 `matched=false`,并在 failure 中写入输出超限信息observation SHALL 包含已截断输出预览和 error

View File

@@ -0,0 +1,16 @@
## MODIFIED Requirements
### Requirement: ping detail 摘要
系统 SHALL 在 ping API 序列化时从 observation 动态生成结构化 detail 摘要展示关键指标。API registry type SHALL 仍为 `ping`
#### Scenario: 目标可达无丢包
- **WHEN** ping observation 为 alive=true, avgLatencyMs=12, packetLoss=0%, transmitted=3, received=3
- **THEN** detail SHALL 为 `alive, avg 12ms, loss 0% (3/3)`
#### Scenario: 目标可达有丢包
- **WHEN** ping observation 为 alive=true, avgLatencyMs=156, maxLatencyMs=340, packetLoss=33%, transmitted=3, received=2
- **THEN** detail SHALL 包含 avg、max 和 loss 信息
#### Scenario: 目标不可达
- **WHEN** ping observation 为 alive=false, transmitted=3, received=0
- **THEN** detail SHALL 为 `unreachable (0/3 received)`

View File

@@ -0,0 +1,39 @@
## MODIFIED Requirements
### Requirement: LLM Failure Phase 与状态摘要
LLM checker SHALL 使用 `request``status``headers``stream``output``finishReason``rawFinishReason``usage``duration` 作为第一版 failure phase。成功结果的 API detail SHALL 从持久化 observation 动态构造,简短描述 provider、mode、HTTP status、finish reason、raw finish reason、first token、输出长度和 token usage 中可用的信息。observation 和 detail MUST NOT 写入完整 prompt、完整输出或 key。
#### Scenario: request failure
- **WHEN** 模型请求因网络错误、认证调用异常、AbortSignal 或无 HTTP metadata 的 SDK 错误失败
- **THEN** LLM checker SHALL 返回 `phase: "request"` 的 error failure
#### Scenario: output mismatch failure
- **WHEN** 模型输出不满足 `expect.output`
- **THEN** LLM checker SHALL 返回 `phase: "output"` 的 mismatch failure
#### Scenario: 非流式成功摘要
- **WHEN** `provider: openai` 的非流式检查成功
- **THEN** detail SHALL 使用类似 `LLM openai http 200 finish=stop, output=2 chars, usage=12/2 tokens` 的简短格式
#### Scenario: 流式成功摘要
- **WHEN** `provider: anthropic` 的流式检查成功且存在 raw finish reason
- **THEN** detail SHALL 使用类似 `LLM anthropic stream 200 finish=stop raw=end_turn, firstToken=624ms, output=2 chars` 的简短格式
#### Scenario: serialize 展示文本
- **WHEN** store 同步 LLM target
- **THEN** LLM checker `serialize()` SHALL 返回类似 `openai:gpt-4o-mini @ https://api.openai.com/v1` 的 target 展示文本和 resolved config JSON
### Requirement: LLM Checker 测试策略
LLM checker 的自动化测试 MUST 不访问真实外部模型服务。测试 SHALL 使用本地 mock HTTP/SSE 服务模拟 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 的成功、错误和流式响应。测试 SHALL 覆盖 schema、语义校验、defaults 合并、变量替换、provider factory、observation、expect、execute、registry 注册、配置加载和 JSON Schema 导出。
#### Scenario: 本地 mock provider 测试成功路径
- **WHEN** 测试运行 LLM checker 的 OpenAI、OpenAI Responses 和 Anthropic 成功路径
- **THEN** 测试 SHALL 使用本地 mock 服务返回 provider 响应,不依赖外部网络或真实 API key
#### Scenario: 本地 mock provider 测试错误路径
- **WHEN** 测试运行 401、429、500、超时、stream error、stream abort、缺 usage 或无文本输出路径
- **THEN** 测试 SHALL 断言 LLM checker 返回符合 spec 的 matched、failure phase、actual、detail 和 observation
#### Scenario: 质量检查覆盖 LLM checker
- **WHEN** 实现完成后执行质量检查
- **THEN** `bun run schema:check``bun run check` SHALL 通过

View File

@@ -0,0 +1,48 @@
## MODIFIED Requirements
### Requirement: 新增共享类型
系统 SHALL 在 `src/shared/api.ts` 中定义 Dashboard 和 Metrics 相关共享类型。CheckResult SHALL 包含 durationMsnull | number、failureCheckFailure | null、matchedboolean、detailnull | string、observationRecord<string, unknown> | null、timestampstring。其中 detail 替代原 statusDetail 字段名。
#### Scenario: DashboardResponse 类型
- **WHEN** 前后端共享 `DashboardResponse` 类型
- **THEN** 该类型 SHALL 包含 summary 和 targets 字段
#### Scenario: TargetStatus 类型
- **WHEN** 前后端共享 `TargetStatus` 类型
- **THEN** 该类型 SHALL 包含目标基本信息字段id、name、description、group、type、target、interval、statstotalChecks、upChecks、downChecks、availability、currentStreak 和 recentSamples 字段,其中 name 和 description 类型均为 null 或字符串
#### Scenario: TargetMetricsResponse 类型
- **WHEN** 前后端共享 `TargetMetricsResponse` 类型
- **THEN** 该类型 SHALL 包含 targetId、window、stats 和 trend 字段
#### Scenario: TrendPoint 类型
- **WHEN** 前后端共享 `TrendPoint` 类型
- **THEN** 该类型 SHALL 包含 bucketStart、avgDurationMs、minDurationMs、maxDurationMs、availability、totalChecks、upChecks、downChecks 字段
#### Scenario: CheckResult 类型变更
- **WHEN** 前端或后端引用 CheckResult 类型
- **THEN** 该类型 SHALL 包含 `timestamp: string``matched: boolean``durationMs: number | null``detail: string | null``observation: Record<string, unknown> | null``failure` 字段,不包含 statusDetail 字段,不包含 success 字段
#### Scenario: RecentSample 类型
- **WHEN** 前后端共享 `RecentSample` 类型
- **THEN** 该类型 SHALL 包含 `timestamp: string``durationMs: number | null``up: boolean` 字段,其中 up 为 boolean 且等于 matched
#### Scenario: HistoryResponse 类型
- **WHEN** 前后端共享 `HistoryResponse` 类型
- **THEN** 该类型 SHALL 包含 `items: CheckResult[]``total: number``page: number``pageSize: number` 字段
#### Scenario: API 序列化构造 detail
- **WHEN** API 路由序列化 StoredCheckResult 为 API 响应
- **THEN** 系统 SHALL 从 StoredCheckResult 中反序列化 observation根据 target type 通过 checkerRegistry 获取对应 checker 并调用 buildDetail(observation) 动态生成 detail 字段
#### Scenario: mapCheckResult 接收 type 参数
- **WHEN** 序列化辅助函数 mapCheckResult 被调用
- **THEN** 函数 SHALL 接收 target type 参数,用于从 registry 获取对应 checker 调用 buildDetail
#### Scenario: Dashboard API 传递 type
- **WHEN** Dashboard 路由序列化 latestCheck
- **THEN** 路由 SHALL 将 target.type 传递给 mapCheckResult
#### Scenario: History API 传递 type
- **WHEN** History 路由序列化历史记录列表
- **THEN** 路由 SHALL 将已查询的 target.type 传递给 mapCheckResult

View File

@@ -0,0 +1,41 @@
## MODIFIED Requirements
### Requirement: SQLite 数据库初始化
系统 SHALL 使用 Bun 内置 `bun:sqlite` 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。数据库 schema MUST 支持 typed checker target 和结构化检查结果targets 表 MUST 包含 `grp` 列存储分组信息,且 targets 表的 `name``description` 列 MUST 允许 NULL。
#### Scenario: 首次启动创建数据库
- **WHEN** 指定的数据目录下不存在数据库文件
- **THEN** 系统 SHALL 创建数据库文件并初始化 targets 表和 check_results 表check_results 表包含 idINTEGER PRIMARY KEY AUTOINCREMENT、target_idTEXT NOT NULL、timestampTEXT NOT NULL、matchedINTEGER NOT NULL、duration_msREAL、observationTEXT、failureTEXT不包含 status_detail 列,不包含 success 列
#### Scenario: targets name 列允许 NULL
- **WHEN** 系统首次创建 targets 表
- **THEN** targets.name 列 SHALL 允许存储 NULL
#### Scenario: 数据目录不存在
- **WHEN** 配置的数据目录路径不存在
- **THEN** 系统 SHALL 自动创建该目录
#### Scenario: 数据库已存在时启动
- **WHEN** 数据库文件已存在
- **THEN** 系统 SHALL 直接打开数据库,不重新建表
#### Scenario: 外键约束
- **THEN** 系统 SHALL 启用 `PRAGMA foreign_keys = ON`
#### Scenario: 级联删除
- **THEN** check_results 表的外键约束 SHALL 使用 `ON DELETE CASCADE`,确保删除目标时自动清理关联结果记录
### Requirement: check_results 表追加写入
系统 SHALL 将每次检查结果追加写入 check_results 表,不更新或删除已有记录。
#### Scenario: 写入检查结果
- **WHEN** 一次 checker 执行完成
- **THEN** 系统 SHALL 插入一条包含 target_id、timestamp、matched、duration_ms、observation、failure 的记录,其中 observation 使用 JSON.stringify 序列化为 TEXT
#### Scenario: 查询检查结果
- **WHEN** 系统查询 latest check 或历史 check_results
- **THEN** 存储层 SHALL 返回 observation 字段而非 status_detail 字段,供 API 序列化层反序列化并构造 detail
#### Scenario: 写入结构化失败信息
- **WHEN** checker 执行失败或 expect 不匹配
- **THEN** 系统 SHALL 将首个失败原因序列化写入 failure 字段

View File

@@ -0,0 +1,16 @@
## MODIFIED Requirements
### Requirement: 拨测结果记录
系统 SHALL 在每次 checker 完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、matched、duration_ms、observation、failure 字段。detail SHALL 为 API 层派生字段,不写入存储层;系统 SHALL NOT 写入 status_detail 字段。
#### Scenario: 成功检查结果记录
- **WHEN** checker 成功执行且 expect 全部匹配
- **THEN** 系统 SHALL 记录 matched=true、duration_ms、observationfailure 为 null
#### Scenario: 执行失败结果记录
- **WHEN** checker 执行失败(网络错误、超时、命令启动失败、输出超限等)
- **THEN** 系统 SHALL 记录 matched=false、failure.kind="error" 和具体错误信息,并在可收集领域观测数据时记录 observation
#### Scenario: expect 不匹配结果记录
- **WHEN** checker 执行成功但 expect 不匹配
- **THEN** 系统 SHALL 记录 matched=false、observation、failure.kind="mismatch" 和具体不匹配信息

View File

@@ -0,0 +1,16 @@
## MODIFIED Requirements
### Requirement: 记录面板
记录 Tab SHALL 展示分页检查结果列表,使用 TDesign PrimaryTable。
#### Scenario: 检查结果表格
- **WHEN** 记录面板渲染且数据可用
- **THEN** 面板 SHALL 使用 TDesign PrimaryTable 展示检查结果列包含状态StatusDot 圆点、时间YYYY-MM-DD HH:mm:ss 格式)、耗时(标题含 ms 单位单元格仅显示数值居中对齐、详情detail 和 failure.message 用冒号拼接)
#### Scenario: 服务端分页
- **WHEN** 检查结果总数超过一页
- **THEN** 表格 SHALL 使用内建 paginationdisableDataPage=true分页器显示在表格底部
#### Scenario: 翻页触发请求
- **WHEN** 用户切换分页页码
- **THEN** 系统 SHALL 请求对应页码的服务端数据,表格更新

View File

@@ -0,0 +1,51 @@
## MODIFIED Requirements
### Requirement: tcp checker 执行
系统 SHALL 按 tcp target 配置建立 TCP 连接,记录完整执行耗时和 TCP observation并在连接失败、超时或资源超限时产生结构化失败信息。
#### Scenario: TCP 连接成功
- **WHEN** tcp target 指向可连接的 TCP 服务,且未配置 expect 或 `expect.connected``true`
- **THEN** 系统 SHALL 记录 `matched=true``durationMs` 和包含 connected、connectTimeMs、banner 的 observation并关闭 socket
#### Scenario: TCP 连接失败
- **WHEN** tcp target 指向不可连接的 host/port且未配置 expect 或 `expect.connected``true`
- **THEN** 系统 SHALL 记录 `matched=false`observation SHALL 包含 connected=false 和错误信息failure 的 kind 为 `error`phase 为 `connect`message 包含可读连接失败原因
#### Scenario: 期望端口不可达且连接失败
- **WHEN** tcp target 配置 `expect.connected: false`,且 TCP 连接失败
- **THEN** 系统 SHALL 记录 `matched=true`observation SHALL 包含 connected=false 和实际连接失败原因API detail SHALL 展示实际连接失败原因摘要
#### Scenario: 期望端口不可达但连接成功
- **WHEN** tcp target 配置 `expect.connected: false`,但 TCP 连接成功
- **THEN** 系统 SHALL 记录 `matched=false`observation SHALL 包含 connected=truefailure 的 kind 为 `mismatch`phase 为 `connected`
#### Scenario: TCP 执行超时
- **WHEN** 引擎注入的 `ctx.signal` 在 TCP 连接或 banner 读取过程中 abort
- **THEN** 系统 SHALL best-effort 关闭 socket记录 `matched=false`failure 的 kind 为 `error`phase 为 `connect``banner`message 包含超时信息,并在可收集时记录 observation
#### Scenario: duration 包含 banner 读取
- **WHEN** tcp target 开启 `readBanner` 且服务端延迟发送 banner
- **THEN** 结果中的 `durationMs` SHALL 覆盖连接建立、banner 等待、banner 读取和 expect 校验的完整耗时
### Requirement: tcp banner 读取
系统 SHALL 仅在 `tcp.readBanner: true` 时读取服务端主动发送的 banner 数据,并同时受 `bannerReadTimeout``maxBannerBytes` 限制。
#### Scenario: 默认不读取 banner
- **WHEN** tcp target 未配置 `readBanner` 或配置为 `false`
- **THEN** 系统 SHALL 在连接建立后立即进入 connected 和 duration 校验,不等待服务端数据
#### Scenario: 读取服务端 banner
- **WHEN** tcp target 配置 `readBanner: true`,且服务端连接后发送 `220 smtp.example.com ESMTP`
- **THEN** 系统 SHALL 收集 banner 文本,并允许后续 `expect.banner` 对该文本执行 operator 断言
#### Scenario: banner 等待超时无数据
- **WHEN** tcp target 配置 `readBanner: true`,但服务端在 `bannerReadTimeout` 内未发送任何数据
- **THEN** 系统 SHALL 将 banner 视为空字符串并继续执行 expect 校验,不将无 banner 本身作为连接错误
#### Scenario: banner 读取超过最大字节数
- **WHEN** 服务端发送的 banner 数据超过 `maxBannerBytes`
- **THEN** 系统 SHALL 停止读取并记录 `matched=false`、failure.kind=`error`、failure.phase=`banner` 的结构化错误
#### Scenario: banner detail 截断展示
- **WHEN** tcp target 成功读取到较长 banner
- **THEN** observation.banner SHALL 保存截断后的 banner 摘要API detail SHALL 展示截断后的 banner 摘要,避免 UI 展示过长文本

View File

@@ -0,0 +1,55 @@
## MODIFIED Requirements
### Requirement: udp checker 执行
系统 SHALL 使用 Bun connected UDP socket 向目标发送单个 datagram等待第一个 UDP 响应 datagram记录完整执行耗时和 UDP observation并在发送失败、超时、资源超限或底层 socket 错误时产生结构化失败信息。
#### Scenario: UDP 请求响应成功
- **WHEN** udp target 指向会返回 `PONG` 的 UDP 服务,且未配置 expect 或 `expect.responded``true`
- **THEN** 系统 SHALL 记录 `matched=true``durationMs` 和包含响应大小的 observation并关闭 socket
#### Scenario: 使用 hostname 执行 UDP 探测
- **WHEN** udp target 的 `udp.host` 为可解析域名或 `localhost`
- **THEN** 系统 SHALL 使用 Bun connected UDP socket 完成发送和接收,不要求配置 IP 地址
#### Scenario: 只处理第一个响应 datagram
- **WHEN** UDP 服务对一次请求返回多个 datagram
- **THEN** 系统 SHALL 仅使用第一个收到的 UDP datagram 执行 expect 校验,并关闭 socket
#### Scenario: UDP 无响应且默认期望响应
- **WHEN** udp target 指向在 timeout 内不返回 UDP datagram 的服务,且未配置 expect 或 `expect.responded``true`
- **THEN** 系统 SHALL 在 `ctx.signal` abort 后记录 `matched=false`observation SHALL 包含 responded=false 和 errorfailure 的 kind 为 `error`phase 为 `response`message 包含超时或未响应信息
#### Scenario: 期望无响应且实际无响应
- **WHEN** udp target 配置 `expect.responded: false`,且 timeout 内未收到 UDP datagram
- **THEN** 系统 SHALL 记录 `matched=true`observation SHALL 包含 responded=falseAPI detail SHALL 表示未收到响应
#### Scenario: 期望无响应但实际收到响应
- **WHEN** udp target 配置 `expect.responded: false`,但收到了 UDP datagram
- **THEN** 系统 SHALL 记录 `matched=false`observation SHALL 包含 responded=true 和响应摘要failure 的 kind 为 `mismatch`phase 为 `responded`
#### Scenario: UDP socket 底层错误
- **WHEN** Bun UDP socket 在发送或接收过程中触发 error 事件
- **THEN** 系统 SHALL best-effort 关闭 socket并记录 `matched=false`、failure.kind=`error` 和可读错误信息,并在可收集时记录 observation
#### Scenario: ICMP unreachable 不作为 UDP 响应
- **WHEN** 底层系统因目标端口不可达产生 ICMP unreachable
- **THEN** 系统 SHALL NOT 将其视为 `responded=true` 的 UDP datagram 响应
#### Scenario: UDP 执行超时关闭 socket
- **WHEN** 引擎注入的 `ctx.signal` 在 UDP 发送或等待响应过程中 abort
- **THEN** 系统 SHALL best-effort 关闭 UDP socket并记录结构化超时或未响应结果
### Requirement: udp detail 摘要
系统 SHALL 在 udp API 序列化时从 observation 动态生成简短 detail 摘要,展示关键结果并避免返回过长响应内容。
#### Scenario: 收到响应的摘要
- **WHEN** udp target 收到 4 字节响应且完整执行耗时为 12ms
- **THEN** detail SHALL 包含 `responded in 12ms``4 bytes`
#### Scenario: 未收到响应的摘要
- **WHEN** udp target 配置 `expect.responded: false` 且 timeout 内未收到 UDP datagram
- **THEN** detail SHALL 包含 `no response` 和执行耗时
#### Scenario: 响应内容摘要截断
- **WHEN** udp target 收到较长响应内容
- **THEN** detail SHALL 只展示按 `responseEncoding` 转换并截断后的响应摘要