refactor: 移除 success 字段,简化为 matched 单层判定模型
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-11
|
||||
@@ -0,0 +1,63 @@
|
||||
## Context
|
||||
|
||||
当前系统采用 `success` + `matched` 两层判定模型:
|
||||
- `success`:拨测是否成功完成(HTTP 收到响应 / Command 正常退出)
|
||||
- `matched`:expect 规则是否匹配
|
||||
- UP = `success AND matched`
|
||||
|
||||
但实际代码中 `fetcher.ts` 和 `command-runner.ts` 均将 `success` 设为 `expectResult.matched`,导致 `success ≡ matched`。两层模型从未真正生效,`success` 字段是冗余的。
|
||||
|
||||
同时发现两个附带问题:
|
||||
1. 分组排序使用 `ORDER BY grp`(字母序),spec 要求按 YAML 首现顺序
|
||||
2. `command-runner` 未设置 `stdin: "ignore"`,spec 要求禁止写入 stdin
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 移除 `success` 字段,将判定模型简化为 `matched` 单层判定
|
||||
- 修复分组排序为 YAML 首现顺序
|
||||
- 确保 command-runner 禁用 stdin
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不涉及 `success` 以外的其他字段变更
|
||||
- 不涉及前端 UI 样式或布局调整
|
||||
- 不涉及新增功能特性
|
||||
- 不处理代码质量问题(P2/P3 级别留给后续 change)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 判定模型简化为 matched 单层
|
||||
|
||||
**选择**: 移除 `success`,仅保留 `matched`
|
||||
|
||||
**理由**: `success` 与 `matched` 在实现中始终同值,没有独立语义。执行失败(网络错误、超时、进程崩溃)和 expect 不匹配都统一为 `matched=false`,通过 `failure.kind` 区分(`"error"` vs `"mismatch"`)。
|
||||
|
||||
**替代方案**: 修复 `success` 使其真正独立(如 HTTP 返回 404 时 success=true, matched=false)。被否决——当前项目不需要区分"请求成功但内容不符"和"请求失败",单层判定更简洁。
|
||||
|
||||
### 2. 可用率计算基于 matched
|
||||
|
||||
**选择**: `availability = matched=true 的记录数 / 总记录数 * 100`
|
||||
|
||||
avgDurationMs 仅计算 `matched=true` 记录的平均耗时:`AVG(CASE WHEN matched = 1 THEN duration_ms END)`
|
||||
|
||||
**理由**: 用户关心的是"健康请求"的性能趋势,排除失败请求的干扰。
|
||||
|
||||
### 3. 分组排序改为按 id 排序
|
||||
|
||||
**选择**: `ORDER BY CASE WHEN grp='default' THEN 0 ELSE 1 END, id`
|
||||
|
||||
**理由**: 目标按 YAML 顺序插入,`id` 自增,`ORDER BY id` 天然等于 YAML 首现顺序。无需额外存储排序权重。
|
||||
|
||||
### 4. 数据库迁移策略
|
||||
|
||||
**选择**: 直接删除旧数据库文件重新创建
|
||||
|
||||
**理由**: 项目未上线,无向前兼容要求。SQLite 不支持 `DROP COLUMN`(需重建表),直接删除是最简方案。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [风险] 数据库 schema 变更导致已有数据丢失 → 项目未上线,可接受
|
||||
- [风险] 前端 API 响应格式变更 → 前端代码同步修改,全量测试覆盖
|
||||
- [风险] 大量文件同时修改可能引入遗漏 → 通过 `bun run verify` 全量验证
|
||||
@@ -0,0 +1,38 @@
|
||||
## Why
|
||||
|
||||
当前系统使用 `success` + `matched` 两层判定模型,但实际实现中 `success` 始终等于 `matched`(两者永远同值),导致两层模型退化为单层。`success` 字段没有提供任何独立信息,反而增加了理解成本和维护负担。此外,分组排序使用字母序而非 YAML 配置中的首现顺序,与 spec 要求不一致。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**: 移除 `success` 字段,将判定模型简化为 `matched` 单层判定
|
||||
- 数据库 `check_results` 表移除 `success` 列
|
||||
- 所有 CheckResult 类型(服务端、共享、前端)移除 `success` 字段
|
||||
- UP/DOWN 判定统一为 `matched=true` / `matched=false`
|
||||
- availability 计算简化为 `matched=true 占比`
|
||||
- avgDurationMs 仅计算 `matched=true` 记录的平均耗时
|
||||
- 修复分组排序:非 default 分组按 YAML 首现顺序(即 `id` 顺序)排列,而非字母序
|
||||
- `command-runner` 添加 `stdin: "ignore"`,符合 spec 要求
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
无
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `probe-engine`: 移除结果记录中的 `success` 字段,简化为 `matched` + `failure`
|
||||
- `probe-api`: `CheckResult` 和 `RecentSample` 移除 `success` 字段;UP 判定改为 `matched`
|
||||
- `probe-data-store`: 数据库 schema 移除 `success` 列;可用率定义简化;排序规则修正
|
||||
- `probe-dashboard`: UP/DOWN 判定改为 `matched`
|
||||
- `card-dashboard`: UP/DOWN 判定改为 `matched`
|
||||
- `command-checker`: 移除所有 `success` 引用;确保 stdin 禁用
|
||||
- `target-grouping`: 排序规则明确为按 id 排序(YAML 首现顺序)
|
||||
|
||||
## Impact
|
||||
|
||||
- **数据库**: `check_results` 表结构变更(移除列),已有数据库需删除重建(项目未上线,无向前兼容要求)
|
||||
- **API**: `CheckResult` 响应移除 `success` 字段,`RecentSample.up` 计算逻辑变更
|
||||
- **前端**: 所有依赖 `success` 字段的组件需更新(TargetCard、TargetDetailModal、TargetGroup)
|
||||
- **测试**: 5 个测试文件需同步移除 `success` 相关断言
|
||||
- **README**: 目标状态判定模型说明需更新
|
||||
@@ -0,0 +1,12 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 卡片状态展示
|
||||
系统 SHALL 在卡片上展示目标的 UP/DOWN 状态。
|
||||
|
||||
#### Scenario: 卡片 UP 状态
|
||||
- **WHEN** 目标最近一次拨测 matched=true
|
||||
- **THEN** 系统 SHALL 显示绿色状态点
|
||||
|
||||
#### Scenario: 卡片 DOWN 状态
|
||||
- **WHEN** 目标最近一次拨测 matched=false
|
||||
- **THEN** 系统 SHALL 显示红色状态点
|
||||
@@ -0,0 +1,26 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 命令执行
|
||||
系统 SHALL 使用 Bun.spawn 执行命令类型目标,继承父进程环境变量并支持覆盖。
|
||||
|
||||
#### Scenario: 禁止 stdin 交互
|
||||
- **THEN** 系统 MUST 设置 stdin 为 "ignore",防止子进程等待标准输入而阻塞
|
||||
|
||||
### Requirement: 结果记录
|
||||
系统 SHALL 记录命令执行的完整结果。
|
||||
|
||||
#### Scenario: 命令成功执行
|
||||
- **WHEN** 命令正常退出
|
||||
- **THEN** 系统 SHALL 记录 durationMs、statusDetail="exitCode=N",并进入 expect 校验
|
||||
|
||||
#### Scenario: 命令启动失败
|
||||
- **WHEN** 命令无法启动
|
||||
- **THEN** 系统 SHALL 记录 matched=false,并在 failure 中写入 kind=error 和具体错误信息
|
||||
|
||||
#### Scenario: 命令超时
|
||||
- **WHEN** 命令执行超过 timeout 限制
|
||||
- **THEN** 系统 MUST 终止该子进程,记录 matched=false,并在 failure 中写入命令超时信息
|
||||
|
||||
#### Scenario: 输出超限
|
||||
- **WHEN** 命令输出超过 maxOutputBytes 限制
|
||||
- **THEN** 系统 MUST 停止收集输出并终止该检查,记录 matched=false,并在 failure 中写入输出超限信息
|
||||
@@ -0,0 +1,17 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 目标列表 API
|
||||
系统 SHALL 提供 `GET /api/targets` 端点,返回所有目标及其最新状态。
|
||||
|
||||
#### Scenario: recentSamples.up 判定
|
||||
- **WHEN** 系统返回 recentSamples 数组
|
||||
- **THEN** 每个元素的 `up` 字段 SHALL 为 `matched === true`
|
||||
|
||||
### Requirement: 共享类型
|
||||
系统 SHALL 在 `src/shared/api.ts` 中定义前后端共享的 TypeScript 类型。
|
||||
|
||||
#### Scenario: CheckResult 类型
|
||||
- **THEN** `CheckResult` 类型 SHALL 包含 timestamp、matched、durationMs、statusDetail、failure 字段,不包含 success 字段
|
||||
|
||||
#### Scenario: RecentSample 类型
|
||||
- **THEN** `RecentSample` 类型 SHALL 包含 timestamp、durationMs、up 字段,其中 up 为 boolean 且等于 matched
|
||||
@@ -0,0 +1,12 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 状态判定与展示
|
||||
系统 SHALL 根据最近一次拨测结果展示目标状态。
|
||||
|
||||
#### Scenario: 目标 UP 状态
|
||||
- **WHEN** 目标最近一次拨测 matched=true
|
||||
- **THEN** 系统 SHALL 显示绿色 UP 状态
|
||||
|
||||
#### Scenario: 目标 DOWN 状态
|
||||
- **WHEN** 目标最近一次拨测 matched=false
|
||||
- **THEN** 系统 SHALL 显示红色 DOWN 状态
|
||||
@@ -0,0 +1,41 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 数据库表结构
|
||||
系统 SHALL 使用 SQLite 存储 targets 和 check_results 两张表。
|
||||
|
||||
#### Scenario: check_results 表结构
|
||||
- **THEN** check_results 表 SHALL 包含 id(INTEGER PRIMARY KEY AUTOINCREMENT)、target_id(INTEGER NOT NULL)、timestamp(TEXT NOT NULL)、matched(INTEGER NOT NULL)、duration_ms(REAL)、status_detail(TEXT)、failure(TEXT)列,不包含 success 列
|
||||
|
||||
### Requirement: 结果写入
|
||||
系统 SHALL 将每次拨测结果插入 check_results 表。
|
||||
|
||||
#### Scenario: 插入结果记录
|
||||
- **THEN** 系统 SHALL 插入一条包含 target_id、timestamp、matched、duration_ms、status_detail、failure 的记录
|
||||
|
||||
### Requirement: 可用率计算
|
||||
系统 SHALL 计算目标在指定时间范围内的可用率。
|
||||
|
||||
#### Scenario: 可用率定义
|
||||
- **THEN** 系统 SHALL 返回 matched=true 的记录数占总记录数的百分比
|
||||
|
||||
#### Scenario: 平均耗时
|
||||
- **THEN** 系统 SHALL 返回 duration_ms 的平均值(仅计算 matched=true 的记录)
|
||||
|
||||
### Requirement: 目标排序
|
||||
系统 SHALL 按分组排序返回目标列表。
|
||||
|
||||
#### Scenario: 分组排序规则
|
||||
- **WHEN** 查询目标列表
|
||||
- **THEN** "default" 分组 SHALL 排在最前,其余分组 SHALL 按 YAML 配置中首次出现的顺序(即 id 自增顺序)排列
|
||||
|
||||
### Requirement: 最近采样查询
|
||||
系统 SHALL 提供获取目标最近 N 条采样记录的方法。
|
||||
|
||||
#### Scenario: 采样记录返回字段
|
||||
- **THEN** 系统 SHALL 返回最多 N 条记录,每条包含 timestamp、duration_ms、matched
|
||||
|
||||
### Requirement: 汇总查询
|
||||
系统 SHALL 提供全局汇总统计。
|
||||
|
||||
#### Scenario: UP/DOWN 判定
|
||||
- **THEN** 系统 SHALL 基于 latestCheck.matched 判定目标 UP 或 DOWN:matched=true 为 UP,matched=false 为 DOWN
|
||||
@@ -0,0 +1,21 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 结果记录
|
||||
系统 SHALL 在每次 checker 完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、matched、duration_ms、status_detail、failure 字段。
|
||||
|
||||
#### Scenario: 执行成功且 expect 全部匹配
|
||||
- **WHEN** checker 执行成功且所有 expect 规则匹配
|
||||
- **THEN** 系统 SHALL 记录 matched=true、duration_ms、status_detail,failure 为 null
|
||||
|
||||
#### Scenario: 执行失败(网络错误、超时、进程异常)
|
||||
- **THEN** 系统 SHALL 记录 matched=false、failure.kind="error" 和具体错误信息
|
||||
|
||||
#### Scenario: expect 不匹配
|
||||
- **THEN** 系统 SHALL 记录 matched=false、failure.kind="mismatch" 和具体不匹配信息
|
||||
|
||||
### Requirement: 输出读取限制
|
||||
系统 SHALL 对 command checker 的 stdout/stderr 输出设置大小限制。
|
||||
|
||||
#### Scenario: 输出超过 maxOutputBytes
|
||||
- **WHEN** 子进程输出超过 maxOutputBytes 限制
|
||||
- **THEN** 系统 MUST 停止读取并记录 matched=false 和结构化输出超限错误
|
||||
@@ -0,0 +1,8 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 分组排序
|
||||
系统 SHALL 对非 default 分组按 YAML 配置中的首次出现顺序排列。
|
||||
|
||||
#### Scenario: 非默认分组排序
|
||||
- **WHEN** 查询目标列表
|
||||
- **THEN** 非 default 分组 SHALL 按 id 自增顺序排列(即 YAML 配置中的首次出现顺序),而非字母序
|
||||
@@ -0,0 +1,49 @@
|
||||
## 1. 核心类型变更
|
||||
|
||||
- [x] 1.1 从 `src/server/checker/types.ts` 的 CheckResult 和 StoredCheckResult 类型中移除 `success` 字段
|
||||
- [x] 1.2 从 `src/shared/api.ts` 的 CheckResult 类型中移除 `success` 字段
|
||||
|
||||
## 2. 数据存储层变更
|
||||
|
||||
- [x] 2.1 修改 `src/server/checker/store.ts`:DDL 移除 `success` 列,INSERT 语句移除 success 绑定,所有查询中移除 success 引用
|
||||
- [x] 2.2 修改 `src/server/checker/store.ts`:getSummary 中 UP 判定改为 `latest.matched`
|
||||
- [x] 2.3 修改 `src/server/checker/store.ts`:getTargetStats 可用率计算改为 `matched = 1`
|
||||
- [x] 2.4 修改 `src/server/checker/store.ts`:getTrend 中 availability 和 avgDurationMs 改为基于 `matched = 1`
|
||||
- [x] 2.5 修改 `src/server/checker/store.ts`:getRecentSamples 返回类型移除 success,SELECT 移除 success 列
|
||||
- [x] 2.6 修改 `src/server/checker/store.ts`:分组排序 ORDER BY 移除 `grp`,改为 `ORDER BY CASE WHEN grp='default' THEN 0 ELSE 1 END, id`
|
||||
|
||||
## 3. 拨测执行层变更
|
||||
|
||||
- [x] 3.1 修改 `src/server/checker/fetcher.ts`:所有 CheckResult 返回值中移除 `success` 字段
|
||||
- [x] 3.2 修改 `src/server/checker/command-runner.ts`:所有 CheckResult 返回值中移除 `success` 字段
|
||||
- [x] 3.3 修改 `src/server/checker/command-runner.ts`:Bun.spawn 添加 `stdin: "ignore"`
|
||||
- [x] 3.4 修改 `src/server/checker/engine.ts`:writeResult 调用中移除 `success` 传递
|
||||
|
||||
## 4. API 路由层变更
|
||||
|
||||
- [x] 4.1 修改 `src/server/app.ts`:mapCheckResult 移除 `success` 字段映射
|
||||
- [x] 4.2 修改 `src/server/app.ts`:recentSamples.up 判定改为 `s.matched === 1`
|
||||
|
||||
## 5. 前端组件变更
|
||||
|
||||
- [x] 5.1 修改 `src/web/components/TargetCard.tsx`:isUp 判定改为 `target.latestCheck?.matched`
|
||||
- [x] 5.2 修改 `src/web/components/TargetGroup.tsx`:up 计数改为 `t.latestCheck?.matched`
|
||||
- [x] 5.3 修改 `src/web/components/TargetDetailModal.tsx`:isUp 和 history 行状态改为基于 `matched`
|
||||
|
||||
## 6. 测试同步
|
||||
|
||||
- [x] 6.1 更新 `tests/server/checker/fetcher.test.ts`:移除所有 `success` 相关断言,改为 `matched` 断言
|
||||
- [x] 6.2 更新 `tests/server/checker/command-runner.test.ts`:移除所有 `success` 相关断言,改为 `matched` 断言
|
||||
- [x] 6.3 更新 `tests/server/checker/engine.test.ts`:移除所有 `success` 相关断言,改为 `matched` 断言
|
||||
- [x] 6.4 更新 `tests/server/checker/store.test.ts`:插入数据移除 `success` 字段,查询断言移除 `success` 检查
|
||||
- [x] 6.5 更新 `tests/server/app.test.ts`:API 响应断言移除 `success` 字段
|
||||
|
||||
## 7. 质量验证
|
||||
|
||||
- [x] 7.1 执行 `bun run check`(typecheck + lint + format + test)确保全部通过
|
||||
- [x] 7.2 执行 `bun run verify`(check + build + smoke test)确保全部通过
|
||||
|
||||
## 8. 文档更新
|
||||
|
||||
- [x] 8.1 更新 README.md:目标状态判定模型改为 matched 单层判定,移除 success 说明
|
||||
- [x] 8.2 更新 README.md:响应字段中移除 CheckResult 的 success 字段描述
|
||||
Reference in New Issue
Block a user