## Purpose

定义 Cmd 类型拨测目标：通过 `type: cmd` 配置执行本地命令（如进程检查、脚本健康检测），捕获 exit code、stdout、stderr，按 expect 规则校验并生成 matched 判定。

## Requirements

### Requirement: cmd 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"`

#### Scenario: 不支持 stdin
- **WHEN** cmd target 配置并执行命令
- **THEN** 系统 MUST NOT 向子进程 stdin 写入数据，避免命令因等待输入而阻塞

### 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

### Requirement: cmd expect 校验
系统 SHALL 支持 cmd 专用 expect，包括 `exitCode`、`durationMs`、`stdout` 和 `stderr`，并按 exitCode、durationMs、stdout、stderr 的阶段顺序快速失败。`exitCode` SHALL 保持有限整数数组语义，未配置时在 Resolved expect 中默认 `[0]`。`durationMs` SHALL 使用共享 `RawValueExpectation` 输入并在运行期使用 `ValueExpectation` 校验完整命令执行耗时。`stdout` 和 `stderr` MUST 使用共享 `RawContentExpectations` 数组输入并在运行期使用 `ContentExpectations`，直接 matcher 作用于对应输出文本，`json` extractor SHALL 支持对 JSON CLI 输出执行 JSONPath 断言。

#### Scenario: 默认 exitCode 成功语义
- **WHEN** cmd target 未显式配置 `expect.exitCode`
- **THEN** 系统 SHALL 在 Resolved cmd expect 中使用默认 `exitCode: [0]` 进行校验

#### Scenario: 显式 exitCode 校验
- **WHEN** cmd target 配置 `expect.exitCode: [0, 2]` 且实际 exit code 为 2
- **THEN** 系统 SHALL 判定 exitCode 阶段通过，并继续后续 expect 阶段

#### Scenario: exitCode 不匹配快速失败
- **WHEN** cmd target 配置 `expect.exitCode: [0]` 且实际 exit code 为 1
- **THEN** 系统 SHALL 立即返回 `matched=false`，并在 failure 中写入 phase=`exitCode`、path=`exitCode`、expected 和 actual

#### Scenario: durationMs 校验
- **WHEN** cmd target 配置 `expect.durationMs: {lte: 1000}` 且实际执行耗时为 1500ms
- **THEN** 系统 SHALL 返回 `matched=false`，failure 的 phase 为 `duration`

#### Scenario: stdout 按配置顺序校验
- **WHEN** cmd target 配置 `expect.stdout` 为两个 ContentExpectations，第一条通过且第二条失败
- **THEN** 系统 SHALL 先执行第一条 stdout expectation，再执行第二条，并将 failure.path 指向失败的 `stdout[1]`

#### Scenario: stderr 校验为空
- **WHEN** cmd target 配置 `expect.stderr: [{empty: true}]` 且实际 stderr 为空字符串
- **THEN** 系统 SHALL 判定 stderr 阶段通过

#### Scenario: stdout JSON 输出校验
- **WHEN** cmd target 输出 stdout 为 `{"status":"ok"}` 且配置 `expect.stdout: [{json: {path: "$.status", equals: "ok"}}]`
- **THEN** 系统 SHALL 判定 stdout 阶段通过

#### Scenario: stdout 失败后不检查 stderr
- **WHEN** cmd target 同时配置 stdout 和 stderr expectation，且 stdout expectation 失败
- **THEN** 系统 SHALL 快速失败并 MUST NOT 继续执行 stderr expectation

### Requirement: cmd checker 启动期配置校验
系统 SHALL 在启动期对 cmd checker 的配置契约和语义执行严格校验。Cmd target 的 `cmd` 分组 SHALL 只允许 `exec`、`args`、`cwd`、`env`、`maxOutputBytes` 字段。Cmd expect SHALL 只允许 `exitCode`、`durationMs`、`stdout`、`stderr` 字段。未知字段、非法类型、不可编译正则和 ReDoS 风险正则 MUST 导致启动期配置错误。`expect.exitCode` SHALL 保留原有有限整数数组语义，不限制到特定平台范围。语义校验 MUST NOT 修改 Raw cmd expect 输入。

#### Scenario: cmd args 类型非法
- **WHEN** YAML 中 cmd target 配置 `cmd.args` 不是字符串数组
- **THEN** 系统 SHALL 以配置错误退出，提示 cmd.args 格式错误

#### Scenario: cmd env 值类型非法
- **WHEN** YAML 中 cmd target 配置 `cmd.env`，且任一环境变量值不是字符串
- **THEN** 系统 SHALL 以配置错误退出，提示 cmd.env 对应变量值必须为字符串

#### Scenario: cmd expect exitCode 类型非法
- **WHEN** YAML 中 cmd target 配置 `expect.exitCode` 不是整数数组
- **THEN** 系统 SHALL 以配置错误退出，提示 expect.exitCode 必须为整数数组

#### Scenario: cmd expect durationMs 非法
- **WHEN** YAML 中 cmd target 配置 `expect.durationMs` 不是合法 `RawValueExpectation`
- **THEN** 系统 SHALL 以配置错误退出，提示 expect.durationMs 格式错误

#### Scenario: stdout 必须为 ContentExpectations 数组
- **WHEN** YAML 中 cmd target 配置 `expect.stdout` 但其值不是数组
- **THEN** 系统 SHALL 以配置错误退出，提示 expect.stdout 必须为数组

#### Scenario: stderr 必须为 ContentExpectations 数组
- **WHEN** YAML 中 cmd target 配置 `expect.stderr` 但其值不是数组
- **THEN** 系统 SHALL 以配置错误退出，提示 expect.stderr 必须为数组

#### Scenario: stdout text expectation 空对象非法
- **WHEN** YAML 中 cmd target 配置 `expect.stdout: [{}]`
- **THEN** 系统 SHALL 以配置错误退出，提示 stdout expectation 必须包含至少一个合法 matcher 或 extractor

#### Scenario: stderr text expectation 未知字段非法
- **WHEN** YAML 中 cmd target 配置 `expect.stderr: [{foo: "bar"}]`
- **THEN** 系统 SHALL 以配置错误退出，提示 stderr expectation 包含未知 matcher 或未知 extractor

#### Scenario: stdout regex 正则非法
- **WHEN** YAML 中 cmd target 配置 `expect.stdout: [{regex: "[invalid"}]`
- **THEN** 系统 SHALL 在启动期配置校验失败，而不是延迟到运行期抛错

#### Scenario: cmd expect 未知字段失败
- **WHEN** YAML 中 cmd target 的 expect 包含 `status: [200]`、`maxDurationMs: 1000` 或其他非 cmd expect 字段
- **THEN** 系统 SHALL 以配置错误退出，提示 expect 包含未知字段
