1
0

refactor: 移除 success 字段,简化为 matched 单层判定模型

This commit is contained in:
2026-05-11 13:12:55 +08:00
parent 548b44d28e
commit 35ba56888b
93 changed files with 3893 additions and 103 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-09

View File

@@ -0,0 +1,255 @@
## Context
当前实现的配置、执行、存储、API 和 Dashboard 都以 HTTP 请求为中心:`target.url` 是必填字段,执行器直接 `fetch(url)`,结果存储包含 `status_code``latency_ms`,前端展示 URL、method 和 HTTP 状态码。这种模型无法承载本地命令等非 HTTP checker也让 `expect` 只能表达 HTTP response 的 status/header/body。
项目尚未上线,不需要兼容旧 YAML、旧数据库 schema 或旧 API 契约,因此本次设计选择直接建立 typed target 与领域专用 expect而不是添加兼容分支。目标是让 HTTP 变成 runner 的一种实现,同时新增 command runner并为未来其他 checker 类型保留清晰扩展点。
```text
YAML target
ResolvedTarget(type)
ProbeEngine + concurrency limit
├─ http runner
│ └─ HTTP expect pipeline
└─ command runner
└─ command expect pipeline
CheckResult(success, matched, durationMs, statusDetail, failure)
SQLite + API + Dashboard
```
## Goals / Non-Goals
**Goals:**
- 使用 `target.type` 建模不同 checker 类型v1 支持 `http``command`
- 将 HTTP 配置放入 `target.http`,将命令配置放入 `target.command`,移除顶层 HTTP 字段。
- 为各 checker 类型定义领域专用 expect 名称HTTP 使用 `status``headers``body`command 使用 `exitCode``stdout``stderr`
- 为不同 checker 类型提供默认成功语义HTTP 默认 `status: [200]`command 默认 `exitCode: [0]`
- 将可排序内容检查表达为数组,保证 `body``stdout``stderr` 按配置顺序执行。
- 在 runner 和 expect pipeline 层共同实现快速失败,避免 status/header 已失败时仍读取或解析 body。
- 使用 `durationMs` 表达 checker 执行耗时,替代 HTTP-only 的 `latencyMs`
- 引入结构化失败信息并入库,区分执行错误和 expect 不匹配,耗时阈值字段统一为 `maxDurationMs`
- 引入全局并发限制和 100MB 默认读取上限,避免 HTTP body 或 command 输出造成资源失控。
**Non-Goals:**
- 不兼容旧的顶层 `url``method``headers``body` 配置。
- 不做旧 SQLite schema 迁移;实现阶段可以按新 schema 初始化和测试。
- 不支持 shell 字符串命令command v1 仅支持 `exec + args`
- 不持久化完整 HTTP body、stdout 或 stderr只持久化结构化失败摘要。
- 不引入新的解析或执行依赖。
- 不在本次实现告警通知、认证鉴权或动态增删目标。
## Decisions
### 1. 使用判别联合建模 Target
配置和解析后的目标都使用 `type` 判别:
```yaml
targets:
- name: "HTTP 健康检查"
type: http
http:
url: "https://example.com/health"
method: GET
- name: "Nginx 进程检查"
type: command
command:
exec: "pgrep"
args: ["nginx"]
```
理由HTTP 与 command 的领域字段差异明显,强行把 URL、exec、status、exitCode 抽成统一字段会降低语义清晰度。判别联合可以让 TypeScript 在执行器选择、配置校验和 expect 校验中获得更明确的类型约束。
替代方案:保留顶层 `url` 并通过字段存在性推断 HTTP。该方案兼容性更好但会继续让 HTTP 成为隐式默认类型,不符合当前无兼容包袱下的最佳模型。
### 2. defaults 分为通用和领域分组
建议配置形态:
```yaml
runtime:
maxConcurrentChecks: 20
defaults:
interval: "30s"
timeout: "10s"
http:
method: GET
maxBodyBytes: "100MB"
command:
cwd: "."
maxOutputBytes: "100MB"
```
通用默认值只覆盖所有 checker 都共享的调度与超时字段,领域默认值只覆盖对应 target type。target 自身配置优先级高于 defaults。
替代方案:继续使用 `defaults.method``defaults.headers` 等 HTTP 字段。该方案会在 command target 中产生无意义字段,因此不采用。
### 3. 默认 expect 是逻辑默认值
当用户未显式配置对应状态类 expect 时runner 在校验阶段应用领域默认值,而不是把默认值写回用户配置。
HTTP 默认:`status: [200]`
Command 默认:`exitCode: [0]`
示例:
```yaml
expect:
body:
- contains: "ok"
```
该 HTTP target 仍然先检查 `status == 200`,再检查 body。这样用户只写内容检查时不会把 HTTP 500 错误响应误判为 UP。
替代方案:只有完全不写 `expect` 时才应用默认值。该方案会让“只写 body”绕过 status 检查,不符合默认成功语义,因此不采用。
### 4. Expect pipeline 使用固定阶段顺序和有序规则数组
HTTP 顺序:
```text
status -> duration -> headers -> body[0] -> body[1] -> ...
```
Command 顺序:
```text
exitCode -> duration -> stdout[0] -> stdout[1] -> ... -> stderr[0] -> stderr[1] -> ...
```
`body``stdout``stderr` 使用数组表达配置顺序:
```yaml
expect:
body:
- contains: "healthy"
- json:
path: "$.status"
equals: "ok"
- regex: '"version":"\\d+\\.\\d+"'
```
理由:对象字段天然更像无序集合,不适合表达用户指定的检查顺序。数组规则可以直接生成 `path`,例如 `expect.body[1].json($.status)`,方便失败定位。
替代方案:保留对象结构并约定 contains/regex/json/css/xpath 固定顺序。该方案无法满足“按配置文件中的配置顺序依次检查”的要求,因此不采用。
### 5. 复用通用值操作符,但保持领域 expect 名称
保留并扩展现有操作符:`equals``contains``match``empty``exists``gte``lte``gt``lt`。这些操作符可用于 HTTP header、HTTP body 提取值、command stdout/stderr 文本等。
领域名称保持专用HTTP 使用 `status`command 使用 `exitCode`HTTP body 可使用 `json/css/xpath`command 输出只使用文本规则和通用操作符。
替代方案:把所有值统一抽象成 `status``metadata``payload`。该方案过度泛化,会让 YAML 对使用者不直观,因此不采用。
### 6. Runner 负责按需产生 Observation
HTTP runner 不应总是读取完整 response body。它先发起请求并取得 status、headers 和 duration再运行 status/duration/headers 阶段;只有配置中存在 body 规则且前置阶段通过时,才读取 body并受 `maxBodyBytes` 限制。
Command runner 需要执行命令并收集 exitCode、duration、stdout、stderr。stdout 和 stderr 合计受 `maxOutputBytes` 限制,默认 `100MB`。命令超时或输出超限时runner 产生 `success=false``failure.kind=error`
替代方案runner 总是完整产生所有字段,再交给 expect。该方案实现简单但无法真正快速失败也无法避免不必要的资源读取因此不采用。
### 7. Command 执行不经过 shell
command target 使用 `exec + args`,实现阶段优先使用 Bun 可用的子进程 API并禁止默认 shell 展开。
```yaml
command:
exec: "pgrep"
args: ["nginx"]
cwd: "."
env:
LANG: "C"
```
`cwd` 相对配置文件所在目录解析。`env` 默认继承当前进程环境并允许覆盖指定键。v1 不支持 stdin避免命令阻塞。
替代方案:允许 `shell: "pgrep nginx | wc -l"`。该方案更灵活,但引入转义、注入和跨平台 shell 差异,不适合作为第一版默认能力。
### 8. 全局并发限制由 ProbeEngine 统一执行
`runtime.maxConcurrentChecks` 默认 20。调度仍按 interval 分组触发,但每个目标进入全局并发池后再执行,避免同一 tick 或多个 tick 同时启动过多 HTTP 请求和本地进程。
理由command target 可能启动本地进程,继续无限 `Promise.allSettled` 会有资源风险。全局限制比按组限制更容易理解,也能覆盖不同 interval 组同时触发的情况。
替代方案:为 HTTP 和 command 分别设置并发上限。该方案更精细,但增加配置复杂度,当前需求只要求全局默认值。
### 9. CheckResult 使用结构化 failure
结果模型区分 runner 执行失败和 expect 不匹配:
```ts
interface CheckFailure {
kind: "error" | "mismatch";
phase: "status" | "duration" | "headers" | "body" | "exitCode" | "stdout" | "stderr";
path: string;
expected?: unknown;
actual?: unknown;
message: string;
}
```
`success=false` 表示 runner 未能正常产生可校验结果,例如网络错误、超时、命令启动失败、输出超限。`matched=false` 表示 runner 执行成功但 expect 不匹配。`failure` 字段存储首个失败原因,实际值需要截断,避免超长内容或敏感内容进入数据库和 API。
替代方案:继续只存 `error` 字符串。该方案无法区分执行失败与规则不匹配,也不能准确定位失败 path因此不采用。
### 10. 存储、API、Dashboard 改为 checker 通用语义
SQLite schema 建议从 HTTP-only 字段调整为:
```text
targets:
id, name, type, target, config, interval_ms, timeout_ms, expect
check_results:
id, target_id, timestamp, success, matched, duration_ms, status_detail, failure
```
`target` 是用于展示和搜索的目标摘要,例如 HTTP URL 或 command 命令行摘要;`config` 持久化解析后的领域配置 JSON`status_detail` 存储领域状态摘要,例如 `HTTP 200``exitCode=1`
API 共享类型使用 `durationMs``statusDetail``failure`Dashboard 表格展示“类型、目标、状态、耗时、最近失败原因、趋势”。HTTP 详情可显示 status codecommand 详情可显示 exit code但列表层不使用 HTTP-only 列名。
替代方案:继续保留 `url``method``status_code``latency_ms` 并为 command 填空。该方案会把领域语义混在一起,后续扩展成本高,因此不采用。
### 11. Size 字符串解析
新增 size 解析支持 `B``KB``MB``GB`,默认 `100MB` 等于 `104857600` bytes。HTTP `maxBodyBytes` 限制单次 body 读取command `maxOutputBytes` 限制 stdout 和 stderr 合计读取。
理由YAML 直接写字节数可读性差,二进制单位更适合内存和 buffer 限制。
替代方案:复用 duration 解析或只接受 number。前者语义不匹配后者配置可读性差。
## Risks / Trade-offs
- [Risk] `maxConcurrentChecks=20` 且单次读取上限为 `100MB` 时理论内存峰值较高 → [Mitigation] 提供全局并发限制和 per-target/per-default 读取上限,文档明确资源上限由用户配置共同决定。
- [Risk] 结构化失败信息可能包含敏感响应片段或命令输出 → [Mitigation] 只存首个失败原因,`actual` 做长度截断,默认不持久化完整 body/stdout/stderr。
- [Risk] command checker 允许执行本地命令,有误配置或高开销命令风险 → [Mitigation] 不支持 shell强制 timeout限制输出大小使用全局并发限制。
- [Risk] 不兼容旧配置会导致现有样例和测试全部失效 → [Mitigation] 项目未上线,实施时同步更新 README、示例配置、单元测试和 smoke test。
- [Risk] SQLite schema 重建会丢失旧数据 → [Mitigation] 当前无上线数据,不做迁移;若后续需要升级已部署实例,应另起兼容迁移 change。
## Migration Plan
- 更新类型定义、配置解析和 README 示例,先让新 YAML 契约成为唯一入口。
- 重构存储 schema 和共享 API 类型,再更新 Dashboard 使用新字段。
- 引入 expect 规则数组和结构化 failure迁移 HTTP runner 到新 pipeline。
- 添加 command runner并接入 ProbeEngine 的 runner 选择与全局并发限制。
- 更新测试覆盖配置、HTTP expect、command expect、存储、API、Dashboard 和 smoke test。
- 运行 `bun run check``bun run verify`,确保完整质量门禁通过。
## Open Questions
无。当前讨论已确认默认 HTTP status 使用 `[200]`、默认并发限制使用全局配置、HTTP body 与 command 输出默认上限均为 `100MB`

View File

@@ -0,0 +1,39 @@
## Why
当前系统以 HTTP 请求作为唯一 checker 形态,`target``expect`、存储、API 和 Dashboard 都围绕 URL、HTTP method、status code 与 response body 建模,无法自然表达本地命令检查等非 HTTP 场景。项目尚未上线,没有兼容性约束,适合一次性重构为面向多种 checker 类型的清晰模型。
## What Changes
- **BREAKING**: 移除顶层 `target.url``target.method``target.headers``target.body` 配置形态,改为 `target.type` 判别不同 checker 类型,并将领域字段放入 `http``command` 分组。
- **BREAKING**: `expect.body` 从对象分组改为有序规则数组,按配置顺序执行并快速失败。
- 引入 `http` target 类型,支持 HTTP URL、method、headers、body、最大 body 读取字节数和 HTTP 专用 expect。
- 引入 `command` target 类型,支持本地命令 `exec + args``cwd``env`、最大输出读取字节数和 command 专用 expect。
- 为不同 checker 类型提供领域默认成功语义HTTP 默认 `expect.status: [200]`command 默认 `expect.exitCode: [0]`
- 引入全局并发限制 `runtime.maxConcurrentChecks`,默认值为 20。
- 引入 size 配置解析,支持 `B``KB``MB``GB`HTTP `maxBodyBytes` 和 command `maxOutputBytes` 默认均为 `100MB`
- 调整 expect 执行管线:先执行状态类检查,再执行耗时检查,再执行元数据或内容检查;同一内容字段内部按数组顺序检查,任一失败立即返回结构化失败信息。
- 将 check result 的失败信息结构化入库,区分 runner 执行错误与 expect 不匹配,便于后续追查。
- 将 DB、API、Dashboard 从 HTTP-only 字段命名调整为通用 checker 展示模型,同时保留 HTTP 和 command 的领域专用细节。
- 同步更新 README、示例配置和测试覆盖 typed target、默认 expect、快速失败、输出限制、失败信息和 Dashboard 展示。
## Capabilities
### New Capabilities
- `command-checker`: 定义本地命令 checker 的配置、执行、安全边界、默认成功语义、输出限制和 expect 校验。
### Modified Capabilities
- `probe-config`: YAML 配置从 HTTP-only target 改为 typed target新增 runtime 并发限制、HTTP/command 默认配置和 size 字符串解析。
- `probe-engine`: 调度引擎从固定 HTTP fetch 改为按 target type 选择 runner并在全局并发限制下执行检查。
- `expect-body-checkers`: HTTP body expect 改为有序规则数组,通用值操作符可复用于 stdout/stderr/header/body 等不同字段。
- `probe-data-store`: targets 和 check_results schema 从 HTTP-only 字段改为 checker 通用字段,并持久化结构化失败信息。
- `probe-api`: API 响应从 URL/method/statusCode/latencyMs 为中心改为 type/target/durationMs/statusDetail/failure 等通用 checker 契约。
- `probe-dashboard`: Dashboard 从 HTTP 拨测视图调整为 checker 通用视图,展示类型、目标、耗时、最近失败原因和领域状态详情。
## Impact
- 影响后端类型定义、配置加载校验、调度执行、HTTP runner、command runner、expect 校验模块、SQLite schema、聚合查询和 API 映射。
- 影响前后端共享 API 类型和 Dashboard 表格、详情、历史记录、趋势图展示字段。
- 影响 README、`probes.example.yaml`、单元测试和 smoke test 配置样例。
- 不引入新依赖,优先复用 Bun、TypeScript、现有 cheerio/xpath 和 SQLite 能力。

View File

@@ -0,0 +1,78 @@
## ADDED Requirements
### Requirement: command target 配置
系统 SHALL 支持 `type: command` 的 target 配置,通过 `command.exec``command.args` 描述本地命令,并使用 command 专用字段配置工作目录、环境变量和输出限制。
#### Scenario: 解析 command target
- **WHEN** YAML 中 target 配置 `type: command``command.exec: "pgrep"``command.args: ["nginx"]`
- **THEN** 系统 SHALL 将其解析为 command checker并保留 exec、args、cwd、env、maxOutputBytes、interval、timeout 和 expect 配置
#### Scenario: command target 缺少 exec
- **WHEN** YAML 中 target 配置 `type: command` 但缺少 `command.exec`
- **THEN** 系统 SHALL 以配置错误退出,并提示该 target 缺少 command.exec 字段
#### Scenario: cwd 相对配置文件目录解析
- **WHEN** command target 配置 `command.cwd: "scripts"` 且配置文件位于 `/opt/checker/probes.yaml`
- **THEN** 系统 SHALL 将 cwd 解析为 `/opt/checker/scripts`
#### Scenario: command 不使用 shell
- **WHEN** command target 配置 `exec``args`
- **THEN** 系统 MUST 直接执行该程序和参数,不通过 shell 解释整段命令字符串
#### Scenario: env 默认继承并允许覆盖
- **WHEN** command target 配置 `command.env: {LANG: "C"}` 且当前进程环境包含 `PATH`
- **THEN** 系统 SHALL 继承当前进程的全部环境变量,并将 `LANG` 覆盖为 `"C"`
#### Scenario: 不支持 stdin
- **WHEN** command target 配置并执行命令
- **THEN** 系统 MUST NOT 向子进程 stdin 写入数据,避免命令因等待输入而阻塞
### Requirement: command checker 执行
系统 SHALL 按 command target 配置执行本地命令记录执行耗时、退出码、stdout 和 stderr并在执行失败时产生结构化错误信息。
#### Scenario: 命令正常退出
- **WHEN** command target 执行的进程正常退出且 exit code 为 0
- **THEN** 系统 SHALL 记录 `success=true``durationMs``statusDetail="exitCode=0"`,并进入 expect 校验
#### Scenario: 命令非零退出
- **WHEN** command target 执行的进程正常退出但 exit code 为 1
- **THEN** 系统 SHALL 记录 `success=true``statusDetail="exitCode=1"`,并由 expect.exitCode 决定 matched 结果
#### Scenario: 命令启动失败
- **WHEN** command target 的 exec 不存在或无法启动
- **THEN** 系统 SHALL 记录 `success=false``matched=false`,并在 failure 中写入 kind=`error`、phase=`exitCode` 和可读错误信息
#### Scenario: 命令超时
- **WHEN** command target 在 timeout 时间内未结束
- **THEN** 系统 MUST 终止该子进程,记录 `success=false``matched=false`,并在 failure 中写入命令超时信息
#### Scenario: 命令输出超限
- **WHEN** command target 的 stdout 和 stderr 合计输出超过 `maxOutputBytes`
- **THEN** 系统 MUST 停止收集输出并终止该检查,记录 `success=false``matched=false`,并在 failure 中写入输出超限信息
### Requirement: command expect 校验
系统 SHALL 支持 command 专用 expect包括 `exitCode``stdout``stderr`,并按 exitCode、duration、stdout、stderr 的阶段顺序快速失败。
#### Scenario: 默认 exitCode 成功语义
- **WHEN** command target 未显式配置 `expect.exitCode`
- **THEN** 系统 SHALL 使用默认 `expect.exitCode: [0]` 进行校验
#### Scenario: 显式 exitCode 校验
- **WHEN** command target 配置 `expect.exitCode: [0, 2]` 且实际 exit code 为 2
- **THEN** 系统 SHALL 判定 exitCode 阶段通过,并继续后续 expect 阶段
#### Scenario: exitCode 不匹配快速失败
- **WHEN** command target 配置 `expect.exitCode: [0]` 且实际 exit code 为 1
- **THEN** 系统 SHALL 立即返回 `matched=false`,并在 failure 中写入 phase=`exitCode`、path=`expect.exitCode`、expected 和 actual
#### Scenario: stdout 按配置顺序校验
- **WHEN** command target 配置 `expect.stdout` 为两个规则,第一条通过且第二条失败
- **THEN** 系统 SHALL 先执行第一条 stdout 规则,再执行第二条,并将 failure.path 指向失败的 `expect.stdout[1]`
#### Scenario: stderr 校验为空
- **WHEN** command target 配置 `expect.stderr: [{empty: true}]` 且实际 stderr 为空字符串
- **THEN** 系统 SHALL 判定 stderr 阶段通过
#### Scenario: stdout 失败后不检查 stderr
- **WHEN** command target 同时配置 stdout 和 stderr 规则,且 stdout 规则失败
- **THEN** 系统 SHALL 快速失败并 MUST NOT 继续执行 stderr 规则

View File

@@ -0,0 +1,126 @@
## MODIFIED Requirements
### Requirement: 响应体多种校验方法
系统 SHALL 支持对 HTTP 响应体进行五种可组合的校验方法contains子串、regex正则、jsonJSONPath、cssCSS 选择器、xpathXPath。这些方法 MUST 配置在 `expect.body` 有序数组中。
#### Scenario: contains 子串匹配
- **WHEN** HTTP target 配置 `expect.body: [{contains: "healthy"}]`,且响应体包含 `"healthy"`
- **THEN** 系统 SHALL 判定该 body 规则通过
#### Scenario: contains 不匹配
- **WHEN** HTTP target 配置 `expect.body: [{contains: "healthy"}]`,且响应体不包含该文本
- **THEN** 系统 SHALL 判定 matched 为 false并记录该规则的 failure.path
#### Scenario: regex 正则匹配
- **WHEN** HTTP target 配置 `expect.body: [{regex: '"status"\\s*:\\s*"ok"'}]`,且响应体匹配该正则
- **THEN** 系统 SHALL 判定该 body 规则通过
#### Scenario: regex 不匹配
- **WHEN** HTTP target 配置 regex body 规则,且响应体不匹配该正则
- **THEN** 系统 SHALL 判定 matched 为 false并记录该规则的 failure.path
#### Scenario: json JSONPath 等值匹配
- **WHEN** HTTP target 配置 `expect.body: [{json: {path: "$.status", equals: "ok"}}]`,且响应 JSON 中 `$.status` 值为 `"ok"`
- **THEN** 系统 SHALL 判定该 body 规则通过
#### Scenario: json JSONPath 值不匹配
- **WHEN** HTTP target 配置 json body 规则,且提取值不符合期望
- **THEN** 系统 SHALL 判定 matched 为 false并记录包含 JSONPath 的 failure.path
#### Scenario: json 解析失败
- **WHEN** HTTP target 配置了 json body 规则但响应体不是合法 JSON
- **THEN** 系统 SHALL 判定 matched 为 false
#### Scenario: css 选择器匹配
- **WHEN** HTTP target 配置 `expect.body: [{css: {selector: "div#health", equals: "OK"}}]`,且 HTML 中存在 `div#health` 元素文本为 `"OK"`
- **THEN** 系统 SHALL 判定该 body 规则通过
#### Scenario: css 选择器匹配属性值
- **WHEN** HTTP target 配置 css 规则带 `attr: "content"` 用于提取属性,且属性值匹配期望
- **THEN** 系统 SHALL 判定该 body 规则通过
#### Scenario: css 选择器无匹配元素
- **WHEN** HTTP target 配置了 css 选择器但 HTML 中无匹配元素
- **THEN** 系统 SHALL 判定 matched 为 false
#### Scenario: xpath 表达式匹配
- **WHEN** HTTP target 配置 `expect.body: [{xpath: {path: "/root/status/text()", equals: "ok"}}]`,且 XML 中 `/root/status` 节点文本为 `"ok"`
- **THEN** 系统 SHALL 判定该 body 规则通过
#### Scenario: xpath 表达式无匹配节点
- **WHEN** HTTP target 配置了 xpath 表达式但 XML 中无匹配节点
- **THEN** 系统 SHALL 判定 matched 为 false
### Requirement: 多种 body 校验方法 AND 组合
系统 SHALL 支持在 `expect.body` 数组中同时配置多种 body 校验方法,所有方法均通过时 matched 方为 true。
#### Scenario: 多种方法全部通过
- **WHEN** HTTP target 的 `expect.body` 数组依次配置 contains、json、regex且全部通过
- **THEN** 系统 SHALL 判定 matched 为 true
#### Scenario: 多种方法任一失败
- **WHEN** HTTP target 的 `expect.body` 数组第一条 contains 不通过,后续还有 json 规则
- **THEN** 系统 SHALL 判定 matched 为 false且不再检查后续 json 规则
### Requirement: 操作符系统
系统 SHALL 支持对提取值和文本值使用以下操作符进行比较equals默认等值、contains子串包含、match正则匹配、empty空值判断、exists存在性判断、gte/lte/gt/lt数值比较
#### Scenario: 标量值隐式 equals
- **WHEN** 配置的期望值为标量(字符串/数字/布尔/null`equals: "ok"`
- **THEN** 系统 SHALL 使用 equals 操作符,对实际值做严格相等比较
#### Scenario: 显式 contains 操作符
- **WHEN** 配置 `{contains: "success"}`,且实际值包含 `"success"`
- **THEN** 系统 SHALL 判定该规则通过
#### Scenario: 显式 match 操作符
- **WHEN** 配置 `{match: '\\d+\\.\\d+\\.\\d+'}`,且实际值匹配该正则
- **THEN** 系统 SHALL 判定该规则通过
#### Scenario: empty 操作符判断为空
- **WHEN** 配置 `{empty: true}`,且实际值为空数组 `[]`
- **THEN** 系统 SHALL 判定该规则通过
#### Scenario: empty 操作符判断非空
- **WHEN** 配置 `{empty: false}`,且实际值为 `[1, 2]`
- **THEN** 系统 SHALL 判定该规则通过
#### Scenario: exists 操作符判断存在
- **WHEN** 配置 `{exists: false}`,且实际值不存在
- **THEN** 系统 SHALL 判定该规则通过
#### Scenario: gte 数值比较
- **WHEN** 配置 `{gte: 10}`,且实际值为 `15`(数字)
- **THEN** 系统 SHALL 判定该规则通过
#### Scenario: gt/lt 数值比较
- **WHEN** 配置 `{gt: 0, lt: 1000}`,且实际值为 `500`
- **THEN** 系统 SHALL 对同一字段进行多操作符复合比较,全部通过则该规则通过
### Requirement: 响应头校验
系统 SHALL 支持通过 `expect.headers` 配置对 HTTP 响应头进行键值规则校验header 名称匹配 MUST 不区分大小写。
#### Scenario: 响应头匹配
- **WHEN** HTTP target 配置 `expect.headers: {"Content-Type": {contains: "application/json"}}`,且响应包含该 header 且值匹配
- **THEN** 系统 SHALL 判定 headers 阶段通过
#### Scenario: 响应头不匹配
- **WHEN** HTTP target 配置 `expect.headers: {"Content-Type": {equals: "application/json"}}`,且响应 header 值为 `"text/html"`
- **THEN** 系统 SHALL 判定 matched 为 false
#### Scenario: 响应头缺失
- **WHEN** HTTP target 配置了某个 header 但响应中不存在该 header
- **THEN** 系统 SHALL 判定 matched 为 false
## ADDED Requirements
### Requirement: 结构化 expect 失败信息
系统 SHALL 在任一 expect 规则失败时生成结构化 failure用于标识失败阶段、规则路径、期望值、实际值和可读错误信息。
#### Scenario: body 规则失败信息
- **WHEN** HTTP target 的 `expect.body[1].json` 规则失败
- **THEN** failure SHALL 包含 kind=`mismatch`、phase=`body`、path 指向 `expect.body[1]`,并包含 message
#### Scenario: actual 值截断
- **WHEN** 失败规则的实际值超过系统允许记录的摘要长度
- **THEN** 系统 MUST 截断 actual 摘要,而不是持久化完整响应体或命令输出

View File

@@ -0,0 +1,54 @@
## MODIFIED Requirements
### Requirement: 总览统计 API
系统 SHALL 提供 `GET /api/summary` 端点,返回所有目标的总体统计信息。
#### Scenario: 获取总览统计
- **WHEN** 客户端请求 `GET /api/summary`
- **THEN** 系统 SHALL 返回 JSON 包含 total总目标数、up正常数、down异常数、avgDurationMs所有目标平均耗时、lastCheckTime最近一次检查时间
### Requirement: 目标列表 API
系统 SHALL 提供 `GET /api/targets` 端点,返回所有 typed target 及其最新状态和统计摘要。
#### Scenario: 获取目标列表
- **WHEN** 客户端请求 `GET /api/targets`
- **THEN** 系统 SHALL 返回 JSON 数组每个元素包含目标基本信息id、name、type、target、interval、最近一次检查结果timestamp、success、matched、durationMs、statusDetail、failure和统计摘要totalChecks、availability、avgDurationMs、p99DurationMs
#### Scenario: 目标无历史记录
- **WHEN** 某目标尚未执行过任何检查
- **THEN** 其 latestCheck 为 nullstats 中 totalChecks 为 0
### Requirement: 历史记录 API
系统 SHALL 提供 `GET /api/targets/:id/history` 端点,返回指定目标的最近 N 条检查记录。
#### Scenario: 获取最近历史记录
- **WHEN** 客户端请求 `GET /api/targets/1/history?limit=20`
- **THEN** 系统 SHALL 返回最多 20 条检查记录,按时间倒序排列,且每条包含 success、matched、durationMs、statusDetail 和 failure
#### Scenario: 使用默认 limit
- **WHEN** 客户端请求 `GET /api/targets/1/history`(未指定 limit
- **THEN** 系统 SHALL 默认返回最近 20 条记录
### Requirement: 趋势聚合 API
系统 SHALL 提供 `GET /api/targets/:id/trend` 端点,返回指定目标按小时聚合的趋势数据。
#### Scenario: 获取 24 小时趋势
- **WHEN** 客户端请求 `GET /api/targets/1/trend?hours=24`
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,每个数据点包含 hour、avgDurationMs、availability、totalChecks
#### Scenario: 使用默认时间范围
- **WHEN** 客户端请求 `GET /api/targets/1/trend`(未指定 hours
- **THEN** 系统 SHALL 默认返回最近 24 小时的趋势数据
## ADDED Requirements
### Requirement: 失败信息 API 契约
系统 SHALL 通过 API 返回结构化 failure 信息,供 Dashboard 展示和后续排查。
#### Scenario: 返回 expect 不匹配信息
- **WHEN** 最近一次检查结果包含 failure.kind=`mismatch`
- **THEN** `/api/targets``/api/targets/:id/history` SHALL 返回该 failure 的 kind、phase、path、expected、actual、message 字段
#### Scenario: 无失败信息
- **WHEN** 检查结果 success=true 且 matched=true
- **THEN** API SHALL 返回 failure 为 null

View File

@@ -0,0 +1,102 @@
## MODIFIED Requirements
### Requirement: YAML 配置文件格式
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、runtime 配置、checker 默认值和 typed target 列表。target MUST 使用 `type` 字段声明 checker 类型HTTP 领域字段 MUST 放在 `http` 分组command 领域字段 MUST 放在 `command` 分组。
#### Scenario: 完整配置文件解析
- **WHEN** 系统启动并读取包含 server、runtime、defaults、targets 的 YAML 配置文件
- **THEN** 系统 SHALL 正确解析所有字段并用于初始化服务、调度引擎和对应 checker runner
#### Scenario: 最简 HTTP 配置文件解析
- **WHEN** 系统读取只包含一个 `type: http` target 和 `http.url` 的 YAML 配置文件(省略 server、runtime、defaults 和 expect
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段host=127.0.0.1, port=3000, dir=./data, interval=30s, timeout=10s, runtime.maxConcurrentChecks=20, http.method=GET, http.maxBodyBytes=100MB
#### Scenario: 最简 command 配置文件解析
- **WHEN** 系统读取只包含一个 `type: command` target 和 `command.exec` 的 YAML 配置文件
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段interval=30s, timeout=10s, command.cwd 为配置文件所在目录, command.maxOutputBytes=100MB
#### Scenario: per-target 配置覆盖全局默认值
- **WHEN** 某个 target 指定 interval、timeout 或对应领域分组中的默认字段
- **THEN** 该 target SHALL 使用其自身的值,不受 defaults 中对应字段影响
### Requirement: 配置校验
系统 SHALL 在启动时对 YAML 配置进行完整校验,校验失败时以非零状态退出并输出清晰的错误信息。
#### Scenario: target 缺少必填字段
- **WHEN** YAML 中某个 target 缺少 name 或 type 字段
- **THEN** 系统 SHALL 以错误退出,提示哪个 target 缺少哪个字段
#### Scenario: HTTP target 缺少 url
- **WHEN** YAML 中某个 target 配置 `type: http` 但缺少 `http.url`
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 http.url 字段
#### Scenario: command target 缺少 exec
- **WHEN** YAML 中某个 target 配置 `type: command` 但缺少 `command.exec`
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 command.exec 字段
#### Scenario: target type 非法
- **WHEN** YAML 中某个 target 的 type 不是 `http``command`
- **THEN** 系统 SHALL 以错误退出,提示不支持的 target type
#### Scenario: target name 重复
- **WHEN** YAML 中存在两个 name 相同的 target
- **THEN** 系统 SHALL 以错误退出,提示重复的 name
#### Scenario: interval 格式非法
- **WHEN** interval 或 timeout 值不是有效的时长格式(如 `30s``5m``500ms`
- **THEN** 系统 SHALL 以错误退出并提示格式错误
#### Scenario: maxConcurrentChecks 非法
- **WHEN** runtime.maxConcurrentChecks 不是正整数
- **THEN** 系统 SHALL 以错误退出并提示 runtime.maxConcurrentChecks 格式错误
#### Scenario: size 格式非法
- **WHEN** maxBodyBytes 或 maxOutputBytes 值不是有效的 size 格式
- **THEN** 系统 SHALL 以错误退出并提示支持 B、KB、MB、GB 格式
### Requirement: expect 配置增强
系统 SHALL 支持 typed target 的领域专用 expect 配置,包括 HTTP 的 `status``headers``body` 和 command 的 `exitCode``stdout``stderr`。内容类 expect MUST 使用数组表达配置顺序。
#### Scenario: 解析 HTTP expect 配置
- **WHEN** YAML 配置文件中 HTTP target 的 expect 包含 status、headers、body 规则数组及内部方法
- **THEN** 系统 SHALL 正确解析并存储为 HTTP target 的 expect 字段
#### Scenario: 解析 command expect 配置
- **WHEN** YAML 配置文件中 command target 的 expect 包含 exitCode、stdout 和 stderr 规则数组
- **THEN** 系统 SHALL 正确解析并存储为 command target 的 expect 字段
#### Scenario: 解析 body 有序规则数组
- **WHEN** YAML 中 HTTP target 配置 `expect.body` 为 contains、json、regex 三个数组项
- **THEN** 系统 SHALL 保留数组顺序,供执行阶段按配置顺序快速失败
#### Scenario: 不配置 HTTP status
- **WHEN** HTTP target 未配置 `expect.status`
- **THEN** 系统 SHALL 在执行 expect 时使用默认 `status: [200]` 语义
#### Scenario: 不配置 command exitCode
- **WHEN** command target 未配置 `expect.exitCode`
- **THEN** 系统 SHALL 在执行 expect 时使用默认 `exitCode: [0]` 语义
## ADDED Requirements
### Requirement: size 配置解析
系统 SHALL 支持使用单位字符串配置读取上限,单位包括 `B``KB``MB``GB`
#### Scenario: 解析 MB
- **WHEN** YAML 中配置 `maxBodyBytes: "100MB"`
- **THEN** 系统 SHALL 将其解析为 104857600 bytes
#### Scenario: 解析 KB
- **WHEN** YAML 中配置 `maxOutputBytes: "512KB"`
- **THEN** 系统 SHALL 将其解析为 524288 bytes
### Requirement: runtime 并发配置
系统 SHALL 支持 `runtime.maxConcurrentChecks` 配置全局最大并发检查数。
#### Scenario: 使用默认并发限制
- **WHEN** YAML 中未配置 runtime.maxConcurrentChecks
- **THEN** 系统 SHALL 使用默认值 20
#### Scenario: 配置并发限制
- **WHEN** YAML 中配置 `runtime.maxConcurrentChecks: 5`
- **THEN** 系统 SHALL 将全局最大并发检查数设置为 5

View File

@@ -0,0 +1,71 @@
## MODIFIED Requirements
### Requirement: 总览统计卡片
Dashboard SHALL 在页面顶部展示总览统计卡片,包含总目标数、正常数、异常数和平均耗时。
#### Scenario: 展示统计卡片
- **WHEN** 用户打开 Dashboard 页面
- **THEN** 页面顶部 SHALL 显示 4 个统计卡片:全部目标数、正常目标数、异常目标数、所有目标平均耗时
#### Scenario: 统计数据自动刷新
- **WHEN** 页面处于打开状态
- **THEN** 统计卡片 SHALL 每 5-10 秒自动刷新数据
### Requirement: 目标列表表格
Dashboard SHALL 展示所有 checker target 的列表表格,包含名称、类型、目标摘要、当前状态、最新耗时、最近失败原因和迷你趋势线。
#### Scenario: 展示目标列表
- **WHEN** 用户打开 Dashboard 页面
- **THEN** 页面 SHALL 显示表格每行包含目标名称、类型、目标摘要、状态指示圆点UP / DOWN、最新耗时值、最近失败原因摘要、迷你 Sparkline 趋势线
#### Scenario: 状态指示圆点
- **WHEN** 目标最近一次检查 success=true 且 matched=true
- **THEN** 状态圆点 SHALL 显示为绿色UP
- **WHEN** 目标最近一次检查 success=false 或 matched=false
- **THEN** 状态圆点 SHALL 显示为红色DOWN
### Requirement: 可展开的目标详情面板
Dashboard SHALL 支持在目标列表中展开某行,显示该目标的详细状态、统计摘要、趋势图和最近历史记录。
#### Scenario: 展开目标详情
- **WHEN** 用户点击目标列表中的某一行
- **THEN** 该行下方 SHALL 展开详情面板包含可用率百分比、平均耗时、P99 耗时、24 小时耗时趋势折线图、最近 5-10 条检查记录列表、领域状态详情和失败信息
#### Scenario: 收起目标详情
- **WHEN** 用户再次点击已展开的目标行
- **THEN** 详情面板 SHALL 收起
#### Scenario: 趋势图按需加载
- **WHEN** 用户展开某个目标的详情面板
- **THEN** 系统 SHALL 此时请求该目标的趋势数据,而非页面加载时预加载所有目标的趋势数据
### Requirement: 历史记录展示
Dashboard SHALL 在目标详情面板中展示最近的检查记录,包含时间、领域状态详情、耗时、成功/失败标记和失败信息。
#### Scenario: 展示历史记录
- **WHEN** 用户展开目标详情面板
- **THEN** 面板 SHALL 显示最近检查记录列表每条包含时间戳、statusDetail如 HTTP 200 或 exitCode=1、耗时毫秒数、UP/DOWN 标记和 failure.message如存在
### Requirement: 趋势图可视化
Dashboard SHALL 使用 recharts 库渲染趋势图,包括目标列表中的迷你 Sparkline 和详情面板中的完整折线图。
#### Scenario: 表格行内迷你趋势线
- **WHEN** 目标列表表格渲染
- **THEN** 每行 SHALL 包含一个基于 recharts 的迷你折线图,展示最近的耗时趋势
#### Scenario: 详情面板完整趋势图
- **WHEN** 用户展开目标详情面板
- **THEN** 面板 SHALL 展示基于 recharts 的完整折线图X 轴为时间小时Y 轴为平均耗时,并标注可用率
## ADDED Requirements
### Requirement: checker 类型展示
Dashboard SHALL 在列表和详情中明确展示 target 的 checker 类型。
#### Scenario: 展示 HTTP 类型
- **WHEN** 目标 type 为 `http`
- **THEN** Dashboard SHALL 在类型列显示 HTTP并将目标摘要显示为 URL
#### Scenario: 展示 command 类型
- **WHEN** 目标 type 为 `command`
- **THEN** Dashboard SHALL 在类型列显示 Command并将目标摘要显示为命令摘要

View File

@@ -0,0 +1,74 @@
## MODIFIED Requirements
### Requirement: SQLite 数据库初始化
系统 SHALL 使用 Bun 内置 `bun:sqlite` 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。数据库 schema MUST 支持 typed checker target 和结构化检查结果。
#### Scenario: 首次启动创建数据库
- **WHEN** 指定的数据目录下不存在数据库文件
- **THEN** 系统 SHALL 创建数据库文件并初始化包含 type、target、config、duration_ms、status_detail、failure 等字段的 targets 和 check_results 表
#### Scenario: 数据目录不存在
- **WHEN** 配置的数据目录路径不存在
- **THEN** 系统 SHALL 自动创建该目录
#### Scenario: 数据库已存在时启动
- **WHEN** 数据库文件已存在
- **THEN** 系统 SHALL 直接打开数据库,不重新建表
### Requirement: targets 表同步
系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示摘要、领域配置、调度配置和 expect 配置。
#### Scenario: 首次同步目标
- **WHEN** 数据库为空且 YAML 中定义了 N 个 typed target
- **THEN** 系统 SHALL 将所有目标插入 targets 表,包含 name、type、target、config、interval_ms、timeout_ms 和 expect
#### Scenario: 配置变更后重新同步
- **WHEN** YAML 配置发生变更(新增、删除或修改目标)后重启
- **THEN** 系统 SHALL 根据 name 字段匹配:新增的插入、删除的移除、修改的更新
### Requirement: check_results 表追加写入
系统 SHALL 将每次检查结果追加写入 check_results 表,不更新或删除已有记录。
#### Scenario: 写入检查结果
- **WHEN** 一次 checker 执行完成
- **THEN** 系统 SHALL 插入一条包含 target_id、timestamp、success、matched、duration_ms、status_detail、failure 的记录
#### Scenario: 写入结构化失败信息
- **WHEN** checker 执行失败或 expect 不匹配
- **THEN** 系统 SHALL 将首个失败原因序列化写入 failure 字段
### Requirement: 聚合查询支持
数据存储 SHALL 支持按时间段聚合查询用于计算可用率、平均耗时、P99 耗时等统计指标。
#### Scenario: 计算目标可用率
- **WHEN** 查询某目标在指定时间范围内的可用率
- **THEN** 系统 SHALL 返回 UP (success=true AND matched=true) 的记录数占总记录数的百分比
#### Scenario: 计算目标平均耗时
- **WHEN** 查询某目标在指定时间范围内的平均耗时
- **THEN** 系统 SHALL 返回 duration_ms 的平均值(仅计算 success=true 的记录)
#### Scenario: 按小时聚合趋势数据
- **WHEN** 查询某目标在指定时间范围内的趋势数据
- **THEN** 系统 SHALL 返回按小时分组的聚合数据,包括每小时的平均耗时和可用率
## ADDED Requirements
### Requirement: 目标展示摘要持久化
数据存储 SHALL 为每个 target 持久化一个领域无关的展示摘要字段 `target`
#### Scenario: HTTP target 展示摘要
- **WHEN** 同步 HTTP target
- **THEN** targets.target SHALL 存储该 target 的 URL
#### Scenario: command target 展示摘要
- **WHEN** 同步 command target
+- **THEN** targets.target SHALL 存储由 exec 和 args 组成的命令摘要
#### Scenario: HTTP target config 序列化
- **WHEN** 同步 HTTP target
- **THEN** targets.config SHALL 存储 JSON包含 url、method、headers、body、maxBodyBytes
#### Scenario: command target config 序列化
- **WHEN** 同步 command target
- **THEN** targets.config SHALL 存储 JSON包含 exec、args、cwd、env、maxOutputBytes

View File

@@ -0,0 +1,128 @@
## MODIFIED Requirements
### Requirement: 组内并发拨测
系统 SHALL 在每次调度 tick 时并发执行同组内目标的检查,但实际同时运行的检查数 MUST 受全局 `runtime.maxConcurrentChecks` 限制。
#### Scenario: 同组目标并发执行
- **WHEN** 调度器触发一次 tick该组有 3 个目标,且全局并发余量至少为 3
- **THEN** 系统 SHALL 同时执行 3 个 checker而非顺序执行
#### Scenario: 单个目标失败不影响同组其他目标
- **WHEN** 同组中某个目标的检查请求超时或失败
- **THEN** 其他目标的检查 SHALL 正常完成并记录结果
#### Scenario: 全局并发限制生效
- **WHEN** 调度器同时触发 10 个目标且 runtime.maxConcurrentChecks 为 3
- **THEN** 系统 MUST 同时最多运行 3 个检查,其余检查等待并发槽位释放
### Requirement: HTTP 拨测执行
系统 SHALL 对 `type: http` 的目标执行 HTTP 请求,支持 GET、POST、PUT、DELETE、PATCH、HEAD 方法,并携带 `http.headers``http.body`
#### Scenario: 执行 GET 请求
- **WHEN** HTTP target 配置 http.method 为 GET
- **THEN** 系统 SHALL 发送 GET 请求到 http.url
#### Scenario: 执行 POST 请求带 body
- **WHEN** HTTP target 配置 http.method 为 POST 且指定了 http.body 和 Content-Type header
- **THEN** 系统 SHALL 发送带指定 body 的 POST 请求
#### Scenario: 携带自定义 headers
- **WHEN** HTTP target 配置了 http.headers如 Authorization
- **THEN** 系统 SHALL 在请求中包含所有配置的 headers
#### Scenario: HTTP body 读取上限
- **WHEN** HTTP response body 超过该 target 的 maxBodyBytes
- **THEN** 系统 MUST 停止读取并记录 `success=false``matched=false` 和结构化输出超限错误
### Requirement: 请求超时控制
系统 SHALL 对每次 checker 执行实施超时控制,超时时间使用目标配置的 timeout 值。
#### Scenario: HTTP 请求超时
- **WHEN** HTTP 请求在 timeout 时间内未收到响应
- **THEN** 系统 SHALL 中止该请求,记录为失败并标注超时错误
#### Scenario: command 执行超时
- **WHEN** command 进程在 timeout 时间内未退出
- **THEN** 系统 MUST 终止该子进程,记录为失败并标注超时错误
#### Scenario: 请求在超时前完成
- **WHEN** checker 在超时前完成执行
- **THEN** 系统 SHALL 正常记录执行结果并进入 expect 校验
### Requirement: expect 校验
系统 SHALL 在 checker 执行完成后根据目标类型的 expect 配置校验观测结果,校验结果和首个失败原因记入 check result。
#### Scenario: HTTP 默认状态码
- **WHEN** HTTP target 未配置 `expect.status`
- **THEN** 系统 SHALL 按默认 `status: [200]` 校验响应状态码
#### Scenario: 校验 HTTP 状态码
- **WHEN** HTTP target 配置了 `expect.status: [200, 201]`
- **THEN** 系统 SHALL 检查响应状态码是否在列表中,将匹配结果记录到 matched 字段
#### Scenario: 校验 HTTP 响应头
- **WHEN** HTTP target 配置了 `expect.headers: {"Content-Type": {contains: "application/json"}}`
- **THEN** 系统 SHALL 检查响应头是否符合指定规则,全部匹配时继续后续阶段
#### Scenario: 校验 HTTP 响应体
- **WHEN** HTTP target 配置了有序 `expect.body` 规则数组
- **THEN** 系统 SHALL 按数组顺序执行 body 规则,任一失败立即记录 failure 并停止后续规则
#### Scenario: command 默认 exitCode
- **WHEN** command target 未配置 `expect.exitCode`
- **THEN** 系统 SHALL 按默认 `exitCode: [0]` 校验命令退出码
#### Scenario: 校验 command stdout
- **WHEN** command target 配置了有序 `expect.stdout` 规则数组
- **THEN** 系统 SHALL 按数组顺序执行 stdout 规则,任一失败立即记录 failure 并停止后续规则
#### Scenario: 校验耗时阈值
- **WHEN** 目标配置了 `expect.maxDurationMs`
- **THEN** 系统 SHALL 检查实际 durationMs 是否超过阈值,将匹配结果记录到 matched 字段
#### Scenario: 多条 expect 规则
- **WHEN** 目标同时配置状态、duration、元数据和内容规则
- **THEN** 系统 SHALL 所有规则全部通过时 matched 为 true任一不通过则为 false 并记录首个失败原因
### Requirement: Body 校验按需解析
系统 SHALL 仅在 HTTP target 配置了 body 校验且 status、duration、headers 阶段均通过时才读取并解析响应体,避免不必要的读取和解析开销。
#### Scenario: status 失败时不读取 body
- **WHEN** HTTP target 的 status 阶段不匹配
- **THEN** 系统 SHALL 立即返回 matched=false且 MUST NOT 读取 response body
#### Scenario: 仅配置 contains 时不解析 JSON
- **WHEN** HTTP target 仅配置 body contains 规则而未配置 json/css/xpath 规则
- **THEN** 系统 SHALL 不执行 JSON.parse 或 HTML/XML 解析
#### Scenario: 配置 json 时解析 JSON 失败
- **WHEN** HTTP target 配置了 body json 规则但响应体不是合法 JSON
- **THEN** 系统 SHALL 判定 matched 为 false并记录 json 规则对应的 failure.path
### Requirement: 拨测结果记录
系统 SHALL 在每次 checker 完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、success、matched、duration_ms、status_detail、failure 字段。
#### Scenario: 成功检查结果记录
- **WHEN** checker 成功执行且 expect 全部匹配
- **THEN** 系统 SHALL 记录 success=true、matched=true、duration_ms、status_detailfailure 为 null
#### Scenario: 执行失败结果记录
- **WHEN** checker 执行失败(网络错误、超时、命令启动失败、输出超限等)
- **THEN** 系统 SHALL 记录 success=false、matched=false、failure.kind="error" 和具体错误信息
#### Scenario: expect 不匹配结果记录
- **WHEN** checker 执行成功但 expect 不匹配
- **THEN** 系统 SHALL 记录 success=true、matched=false、failure.kind="mismatch" 和具体不匹配信息
## ADDED Requirements
### Requirement: runner 选择
系统 SHALL 根据 target.type 选择对应 runner 执行检查。
#### Scenario: 选择 HTTP runner
- **WHEN** target.type 为 `http`
- **THEN** 系统 SHALL 使用 HTTP runner 执行该目标
#### Scenario: 选择 command runner
- **WHEN** target.type 为 `command`
- **THEN** 系统 SHALL 使用 command runner 执行该目标

View File

@@ -0,0 +1,50 @@
## 1. 类型与配置契约
- [x] 1.1 重构 checker 类型定义为 `http``command` 判别联合,并新增 `CheckFailure``durationMs``statusDetail` 等结果字段,将 `maxLatencyMs` 重命名为 `maxDurationMs`
- [x] 1.2 更新 YAML 配置类型,新增 `runtime.maxConcurrentChecks``defaults.http``defaults.command` 和 typed target 配置
- [x] 1.3 实现 size 解析工具,支持 `B``KB``MB``GB` 并覆盖 `100MB=104857600` 的测试
- [x] 1.4 重构 config-loader 校验逻辑,移除顶层 HTTP 字段支持并校验 type、http.url、command.exec、并发和 size 格式ResolvedConfig 需携带配置文件目录,用于 command cwd 相对路径解析
- [x] 1.5 更新配置解析测试,覆盖最简 HTTP、最简 command、per-target 覆盖、默认值、非法 type、缺失字段和非法 size
## 2. Expect 与失败信息
- [x] 2.1 抽取通用值操作符,使 equals、contains、match、empty、exists、gte、lte、gt、lt 可复用于 header、body、stdout 和 stderr
- [x] 2.2 将 HTTP `expect.body` 重构为有序规则数组,并支持 contains、regex、json、css、xpath 规则
- [x] 2.3 实现 HTTP expect pipeline按 status、duration、headers、body[] 顺序执行并应用默认 `status: [200]`
- [x] 2.4 实现 command expect pipeline按 exitCode、duration、stdout[]、stderr[] 顺序执行并应用默认 `exitCode: [0]`
- [x] 2.5 实现结构化 failure 生成与 actual 摘要截断,区分 `error``mismatch`
- [x] 2.6 将 expect 相关文件body、http、command、failure移入 `checker/expect/` 子目录,统一导入路径并更新测试文件引用
- [x] 2.7 更新 expect 单元测试,覆盖规则顺序、快速失败、默认 status、默认 exitCode、失败 path 和 actual 截断
## 3. Runner 与调度引擎
- [x] 3.1 将现有 fetcher 拆分或重命名为 HTTP runner并改为读取 status、duration、headers 后再按需读取 body
- [x] 3.2 在 HTTP runner 中实现 maxBodyBytes 限制、超时处理、statusDetail 和结构化执行错误
- [x] 3.3 新增 command runner使用 `exec + args` 执行本地命令且不经过 shell
- [x] 3.4 在 command runner 中实现 cwd 相对配置文件目录解析、env 覆盖、timeout kill 和 maxOutputBytes 合计限制
- [x] 3.5 重构 ProbeEngine 按 target.type 选择 runner并引入全局 maxConcurrentChecks 并发池
- [x] 3.6 更新 runner 和 engine 测试,覆盖 HTTP 快速失败不读 body、command 非零退出、启动失败、超时、输出超限和并发限制
## 4. 存储与 API
- [x] 4.1 重建 SQLite schema使用 targets 的 type、target、config 字段和 check_results 的 duration_ms、status_detail、failure 字段
- [x] 4.2 更新目标同步逻辑,持久化 HTTP URL 摘要和 command 命令摘要
- [x] 4.3 更新检查结果写入和聚合查询,使用 duration_ms 计算平均耗时、P99 耗时和趋势数据
- [x] 4.4 更新 shared API 类型,将 avgLatencyMs、p99LatencyMs、latencyMs、statusCode 替换为 avgDurationMs、p99DurationMs、durationMs、statusDetail 和 failure
- [x] 4.5 更新 API handler 映射逻辑,返回 type、target、durationMs、statusDetail、failure 和新的统计字段
- [x] 4.6 更新 store 和 API 测试,覆盖结构化 failure 入库、目标摘要、summary、targets、history 和 trend 响应
## 5. Dashboard 与文档
- [x] 5.1 更新 Dashboard 总览卡片、目标表格和详情面板,将 URL/方法/延迟改为类型、目标、耗时和失败原因展示
- [x] 5.2 更新趋势图和 Sparkline 数据字段,从 latency 切换为 duration
- [x] 5.3 更新前端类型引用和组件测试或相关断言,覆盖 HTTP 与 command target 展示
- [x] 5.4 更新 README 的项目说明、配置说明、目标状态判定、API 字段和已知限制
- [x] 5.5 更新 `probes.example.yaml`,提供 HTTP 与 command typed target 示例以及 100MB 默认说明
- [x] 5.6 更新 smoke test 配置和断言,确保生产 executable 可使用新配置启动并服务 API 与 Dashboard
## 6. 质量验证
- [x] 6.1 运行 `bun run check`修复类型检查、lint、格式检查和单元测试问题
- [x] 6.2 运行 `bun run verify`,修复生产构建和 smoke test 问题
- [x] 6.3 复查 OpenSpec change 与实现一致性,确认所有任务完成且 README、测试和示例同步更新