1
0

feat: 新增两个 OpenSpec 变更提案 — CMD Checker 增强与前端指标增强

This commit is contained in:
2026-05-14 01:39:26 +08:00
parent 6e485cc991
commit 0fa2c0c811
18 changed files with 952 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,112 @@
## Context
当前 command checker 使用 `"command"` 作为 type 和 configKey对应源码目录 `src/server/checker/runner/command/`、测试目录 `tests/server/checker/runner/command/`、spec 目录 `openspec/specs/command-checker/`
测试中使用了 `true``false``sleep``bash``yes | head` 等 Unix 系统命令,在纯 Windows 环境(无 Git Bash下无法运行。probes.example.yaml 中的示例命令(`uname -a``ls /tmp``date`)同样不跨平台。
项目未上线,无向前兼容负担。
## Goals / Non-Goals
**Goals:**
- 将 type/configKey 从 `"command"` 统一重命名为 `"cmd"`包括源码目录、测试目录、spec 目录、YAML 配置键名
- 测试改用 `bun -e "..."` 替代系统命令,确保 Windows/macOS/Linux 三平台通过
- probes.example.yaml 提供跨平台示例
**Non-Goals:**
- 不加 shell 模式(现有 exec + args 已覆盖所有 shell 场景)
- 不加重试机制(失败是拨测指标)
- 不精简 resolve() 中 intervalMs/timeoutMs收益小接口改动大
- 不加 successExitCodes 别名(已有 expect.exitCode
## Decisions
### D1: type 与 configKey 统一为 `cmd`
YAML 配置形态变为:
```yaml
defaults:
cmd:
maxOutputBytes: "100MB"
targets:
- name: "test"
type: cmd
cmd:
exec: "bun"
args: ["-e", "console.log('hello')"]
```
**理由:** `cmd` 简洁,且 type 与 configKey 保持一致(与 HTTP checker 的 `http`/`http` 对称)。
**替代方案:** 只改 type 不改 configKey → 会出现 `type: cmd` + `command: {...}` 的不一致,否决。
### D2: 内部属性名统一为 `cmd`
`ResolvedCommandTarget` 接口中的 `command` 属性名也改为 `cmd`
```typescript
// Before
interface ResolvedCommandTarget {
command: ResolvedCommandConfig;
type: "command";
}
// t.command.exec
// After
interface ResolvedCommandTarget {
cmd: ResolvedCommandConfig;
type: "cmd";
}
// t.cmd.exec
```
**理由:** 内外一致,避免 configKey 是 `cmd` 但内部属性是 `command` 的割裂。
### D3: 源码目录重命名 `runner/command/` → `runner/cmd/`
所有 import 路径同步更新。测试目录 `tests/server/checker/runner/command/``tests/server/checker/runner/cmd/`
**理由:** 目录名与 type/configKey 保持一致,降低认知负担。
### D3: 跨平台测试命令替换表
| 原命令 | 替换为 |
|---|---|
| `true` | `bun -e "process.exit(0)"` |
| `false` | `bun -e "process.exit(1)"` |
| `echo hello` | `bun -e "console.log('hello')"` |
| `sleep 10` | `bun -e "await Bun.sleep(10000)"` |
| `bash -c "echo error >&2"` | `bun -e "process.stderr.write('error\n')"` |
| `bash -c "yes \| head -1000"` | `bun -e "process.stdout.write('y\n'.repeat(1000))"` |
**理由:** `bun` 是项目唯一运行时依赖,三平台均可用,无需额外安装。
### D4: probes.example.yaml 示例策略
示例命令改用 `bun -e "..."` 或跨平台命令(如 `bun --version`),不再使用 `uname``ls /tmp` 等 Unix 专属命令。
### D5: spec 目录重命名
`openspec/specs/command-checker/``openspec/specs/cmd-checker/`,与 type 名称对齐。
### D6: 不加 shell 模式
用户需要管道/重定向时,用现有参数即可:
```yaml
cmd:
exec: "/bin/bash"
args: ["-c", "df -h | grep /dev/sda1"]
```
shell 模式本质是语法糖——内部仍然是 `Bun.spawn([shell, "-c", exec])`。增加代码复杂度shell 检测、参数推断、互斥校验)但收益有限。
## Risks / Trade-offs
- [全量重命名可能遗漏引用] → 通过全局搜索 `"command"` 字面量确保无遗漏CI 类型检查兜底
- [测试中 `bun -e` 启动开销比原生命令大] → 拨测场景不敏感,测试可接受毫秒级差异
- [probes.example.yaml 示例不如 Unix 命令直观] → 加注释说明用途,保持可读性

View File

@@ -0,0 +1,34 @@
## Why
`command` 作为 checker type 名称过长,且测试依赖 Unix 系统命令导致 Windows 环境无法运行。需要统一重命名为 `cmd` 并实现跨平台测试适配。
## What Changes
- **BREAKING** type 字面量 `"command"``"cmd"`configKey `"command"``"cmd"`
- **BREAKING** YAML 配置中 `type: command``type: cmd``command:` 块 → `cmd:`
- **BREAKING** `defaults.command``defaults.cmd`
- 源码目录 `runner/command/``runner/cmd/`
- spec 目录 `command-checker/``cmd-checker/`
- 测试全部改用 `bun -e "..."` 替代系统命令true/false/sleep/bash
- probes.example.yaml 更新为跨平台示例
## Capabilities
### New Capabilities
(无)
### Modified Capabilities
- `probe-config`: `type: command``type: cmd``command` 分组 → `cmd` 分组,`defaults.command``defaults.cmd`,所有校验中的 `"command"` 字面量更新
- `command-checker`: type/configKey 重命名为 `cmd`spec 目录重命名为 `cmd-checker`
- `checker-runner-abstraction`: registry 注册的 type 从 `"command"` 变为 `"cmd"``supportedTypes` 返回 `["http", "cmd"]`
- `windows-test-compat`: 测试命令全面改用 `bun -e "..."`probes.example.yaml 使用跨平台示例
## Impact
- 后端:`src/server/checker/runner/command/` 整个目录重命名及内部所有 `"command"` 字面量
- 配置probes.example.yaml、probe-config.schema.json 中的 type 枚举和分组名
- 测试:`tests/server/checker/runner/command/` 目录重命名及测试命令替换
- 前端:无影响(动态显示 type 值)
- 数据库stored_targets.type 列值变更(项目未上线,无迁移负担)

View File

@@ -0,0 +1,43 @@
## MODIFIED Requirements
### Requirement: CheckerRegistry 注册中心
系统 SHALL 在 `src/server/checker/runner/registry.ts` 中提供 `CheckerRegistry` 类,支持 `register(checker)``get(type)``supportedTypes`。重复注册同一 type SHALL 抛出错误。registry 内部 SHALL 存储 `CheckerDefinition`(使用默认泛型参数),对外提供类型擦除后的接口。
#### Scenario: 查询支持的 type 列表
- **WHEN** 注册了 "http" 和 "cmd" 两个 checker 后查询 `registry.supportedTypes`
- **THEN** 返回的数组 SHALL 包含 `["http", "cmd"]`(按注册顺序)
### Requirement: Command checker 提供契约片段
系统 SHALL 支持 checker 提供自身 TypeBox 配置契约片段,用于描述该 checker 的 defaults 分组、target 领域分组和 expect 分组。
#### Scenario: Cmd checker 提供契约片段
- **WHEN** Cmd checker 被注册
- **THEN** registry SHALL 能提供 Cmd defaults、Cmd target 和 Cmd expect 的 TypeBox 契约片段
### Requirement: 配置解析通过 registry 委托 checker
系统 SHALL 在 `config-loader.ts` 的配置加载流程中通过 `checkerRegistry` 发现已注册 checker组合公共 TypeBox 契约与 checker 契约,并将 checker 专属语义校验和解析委托给对应 checker。
#### Scenario: 配置解析委托 checker
- **WHEN** config-loader 解析一个 type 为 "cmd" 的 target
- **THEN** config-loader SHALL 调用 `checkerRegistry.get("cmd")` 获取对应 checker并委托该 checker 执行语义校验和 resolve
### Requirement: Command text 断言位于 Cmd 目录
系统 SHALL 在 checker 专用目录中提供 text 断言函数。
#### Scenario: Command text 断言位于 Cmd 目录
- **WHEN** Cmd checker 需要对 stdout/stderr 执行文本规则校验
- **THEN** SHALL 调用 `runner/cmd/text.ts` 中的 `checkTextRules(text, rules, phase)`
### Requirement: Command 专用 expect
系统 SHALL 在 checker 专用目录中提供 exitCode 断言函数。
#### Scenario: Command 专用 expect
- **WHEN** Cmd checker 需要校验退出码
- **THEN** SHALL 调用 `runner/cmd/expect.ts` 中的 `checkExitCode()`
### Requirement: Checker 接口定义
系统 SHALL 在 `src/server/checker/runner/types.ts` 中定义面向扩展的泛型 `CheckerDefinition<TResolved extends ResolvedTargetBase = ResolvedTargetBase>`,包含 `type``configKey`、TypeBox 配置契约、启动期语义校验、`resolve``execute``serialize` 成员。
#### Scenario: type 与 configKey 默认一致
- **WHEN** checker 定义 `type: "cmd"`
- **THEN** checker 的 `configKey` SHALL 默认使用 `"cmd"`,对应 target 的 `cmd` 分组和 defaults.cmd 分组

View File

@@ -0,0 +1,33 @@
## MODIFIED Requirements
### Requirement: command target 配置
系统 SHALL 支持 `type: cmd` 的 target 配置,通过 `cmd.exec``cmd.args` 描述本地命令,并使用 cmd 专用字段配置工作目录、环境变量和输出限制。
#### Scenario: 解析 cmd target
- **WHEN** YAML 中 target 配置 `type: cmd``cmd.exec: "pgrep"``cmd.args: ["nginx"]`
- **THEN** 系统 SHALL 将其解析为 cmd checker并保留 exec、args、cwd、env、maxOutputBytes、interval、timeout 和 expect 配置
#### Scenario: cmd target 缺少 exec
- **WHEN** YAML 中 target 配置 `type: cmd` 但缺少 `cmd.exec`
- **THEN** 系统 SHALL 以配置错误退出,并提示该 target 缺少 cmd.exec 字段
#### Scenario: cwd 相对配置文件目录解析
- **WHEN** cmd target 配置 `cmd.cwd: "scripts"` 且配置文件位于 `/opt/checker/probes.yaml`
- **THEN** 系统 SHALL 将 cwd 解析为 `/opt/checker/scripts`
#### Scenario: cmd 不使用 shell
- **WHEN** cmd target 配置 `exec``args`
- **THEN** 系统 MUST 直接执行该程序和参数,不通过 shell 解释整段命令字符串
#### Scenario: env 默认继承并允许覆盖
- **WHEN** cmd target 配置 `cmd.env: {LANG: "C"}` 且当前进程环境包含 `PATH`
- **THEN** 系统 SHALL 继承当前进程的全部环境变量,并将 `LANG` 覆盖为 `"C"`
### Requirement: command checker 执行
系统 SHALL 按 cmd target 配置执行本地命令记录执行耗时、退出码、stdout 和 stderr并在执行失败时产生结构化错误信息。
### Requirement: command expect 校验
系统 SHALL 支持 cmd 专用 expect包括 `exitCode``stdout``stderr`,并按 exitCode、duration、stdout、stderr 的阶段顺序快速失败。
### Requirement: command checker 启动期配置校验
系统 SHALL 在启动期对 cmd checker 的配置契约和语义执行严格校验。Cmd target 的 `cmd` 分组 SHALL 只允许 `exec``args``cwd``env``maxOutputBytes` 字段Cmd expect SHALL 只允许 `exitCode``maxDurationMs``stdout``stderr` 字段。未知字段、非法类型和不可编译正则 MUST 导致启动期配置错误。`expect.exitCode` SHALL 保留原有有限整数数组语义,不限制到特定平台范围。

View File

@@ -0,0 +1,25 @@
## MODIFIED Requirements
### Requirement: YAML 配置文件格式
target MUST 使用 `type` 字段声明 checker 类型HTTP 领域字段 MUST 放在 `http` 分组cmd 领域字段 MUST 放在 `cmd` 分组。
#### Scenario: 最简 command 配置文件解析
- **WHEN** 系统读取只包含一个 `type: cmd` target 和 `cmd.exec` 的 YAML 配置文件
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段interval=30s, timeout=10s, cmd.cwd 为配置文件所在目录, cmd.maxOutputBytes=100MB
### Requirement: 配置校验
#### Scenario: command target 缺少 exec
- **WHEN** YAML 中某个 target 配置 `type: cmd` 但缺少 `cmd.exec`
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 cmd.exec 字段
#### Scenario: 动态 env 字段允许
- **WHEN** YAML 中 `cmd.env` 包含任意环境变量名称,且对应值为字符串
- **THEN** 系统 SHALL 接受这些动态 env 名称
### Requirement: expect 配置增强
系统 SHALL 支持 typed target 的领域专用 expect 配置,包括 HTTP 的 `status``headers``body` 和 cmd 的 `exitCode``stdout``stderr`
#### Scenario: 解析 command expect 配置
- **WHEN** YAML 配置文件中 cmd target 的 expect 包含 exitCode、stdout 和 stderr 规则数组
- **THEN** 系统 SHALL 正确解析并存储为 cmd target 的 expect 字段

View File

@@ -0,0 +1,41 @@
## MODIFIED Requirements
### Requirement: 命令检测器测试 SHALL 使用跨平台命令
命令检测器的测试 SHALL 使用 `bun -e` 脚本替代所有系统命令(包括 `true``false``sleep``bash``echo``yes | head`),确保测试在 Windows、macOS、Linux 三平台上行为一致。
#### Scenario: 进程退出码 0
- **WHEN** 测试需要一个正常退出的命令
- **THEN** 测试 SHALL 使用 `bun -e "process.exit(0)"` 替代 `true`
#### Scenario: 进程退出码非零
- **WHEN** 测试需要一个失败退出的命令
- **THEN** 测试 SHALL 使用 `bun -e "process.exit(1)"` 替代 `false`
#### Scenario: stdout 输出
- **WHEN** 测试需要一个输出文本到 stdout 的命令
- **THEN** 测试 SHALL 使用 `bun -e "console.log('text')"` 替代 `echo text`
#### Scenario: stderr 输出
- **WHEN** 测试需要一个输出文本到 stderr 的命令
- **THEN** 测试 SHALL 使用 `bun -e "process.stderr.write('error\n')"` 替代 `bash -c "echo error >&2"`
#### Scenario: 长时间运行命令
- **WHEN** 测试需要一个超时场景的长时间运行命令
- **THEN** 测试 SHALL 使用 `bun -e "await Bun.sleep(10000)"` 替代 `sleep 10`
#### Scenario: 大量输出
- **WHEN** 测试需要一个产生大量输出的命令
- **THEN** 测试 SHALL 使用 `bun -e "process.stdout.write('y\n'.repeat(N))"` 替代 `bash -c "yes | head -N"`
#### Scenario: 验证非 shell 模式下特殊字符不被展开
- **WHEN** 通过 `Bun.spawn` 执行 `bun -e "console.log('*')"` 并检查 stdout 包含 `*`
- **THEN** 测试 SHALL 在 Windows、macOS 和 Linux 上均返回 `matched: true`
## ADDED Requirements
### Requirement: probes.example.yaml 使用跨平台示例
probes.example.yaml 中的 cmd 类型示例 SHALL 使用跨平台命令(如 `bun -e "..."``bun --version`),不使用 Unix 专属命令(如 `uname``ls /tmp``date`)。
#### Scenario: 示例命令跨平台可执行
- **WHEN** 用户在 Windows、macOS 或 Linux 上直接使用 probes.example.yaml 中的 cmd 示例
- **THEN** 所有 cmd 示例 SHALL 能正常执行,不依赖平台特定命令

View File

@@ -0,0 +1,27 @@
## 1. 源码目录重命名
- [ ] 1.1 重命名 `src/server/checker/runner/command/``src/server/checker/runner/cmd/`,更新目录内所有文件的 type/configKey 字面量为 `"cmd"`
- [ ] 1.2 重命名 `tests/server/checker/runner/command/``tests/server/checker/runner/cmd/`
- [ ] 1.3 更新所有 import 路径中的 `runner/command``runner/cmd`(包括 runner/index.ts 等)
## 2. 类型与配置重命名
- [ ] 2.1 更新 `src/server/checker/runner/cmd/execute.ts``type = "cmd"``configKey = "cmd"``context.defaults["cmd"]`、所有 `t.command.xxx``t.cmd.xxx`
- [ ] 2.2 更新 `src/server/checker/runner/cmd/types.ts``ResolvedCommandTarget.command` 属性名改为 `cmd``type: "command"` 改为 `type: "cmd"`
- [ ] 2.3 更新 `src/server/checker/runner/cmd/validate.ts` 中所有 `"command"``"cmd"` 字面量
- [ ] 2.4 更新 `src/server/checker/runner/cmd/schema.ts` 中 TypeBox 契约的分组名(如有 `"command"` 字面量)
- [ ] 2.5 更新 `probes.example.yaml``type: command``type: cmd``command:``cmd:`,示例命令改为跨平台命令
- [ ] 2.6 更新 `tests/server/app.test.ts``tests/server/bootstrap.test.ts``tests/server/checker/config-loader.test.ts``tests/server/checker/engine.test.ts` 中所有 `"command"` 字面量为 `"cmd"`
- [ ] 2.7 重新生成 `probe-config.schema.json`(执行 schema 生成脚本或手动更新)
## 3. 跨平台测试改造
- [ ] 3.1 更新 `tests/server/checker/runner/cmd/runner.test.ts` 中所有系统命令为 `bun -e "..."` 形式
- [ ] 3.2 更新 `tests/server/checker/runner/cmd/expect.test.ts` 中所有系统命令为 `bun -e "..."` 形式
## 4. Spec 文档与质量保障
- [ ] 4.1 重命名 `openspec/specs/command-checker/``openspec/specs/cmd-checker/`,更新 spec 内容中的 `command``cmd`
- [ ] 4.2 执行完整测试套件 `bun test`,确保所有测试通过
- [ ] 4.3 执行类型检查 `bunx tsc --noEmit`,确保无类型错误
- [ ] 4.4 更新 README.md 中涉及 command checker 的描述和配置示例(包括 defaults.command 段、type 枚举、配置字段说明)