## Purpose

定义 db 类型拨测目标的配置格式、执行逻辑、expect 断言规则和启动期校验。

## Requirements

### Requirement: db target 配置
系统 SHALL 支持 `type: db` 的 target 配置，通过 `db.url` 描述数据库连接字符串（遵循 Bun SQL 支持的格式），通过可选的 `db.query` 描述待执行的 SQL 语句。

#### Scenario: 解析仅连接的 db target
- **WHEN** YAML 中 target 配置 `type: db` 和 `db.url: "postgres://user:pass@localhost:5432/mydb"`，未配置 `db.query`
- **THEN** 系统 SHALL 将其解析为 db checker，仅执行连通性检测

#### Scenario: 解析带查询的 db target
- **WHEN** YAML 中 target 配置 `type: db`、`db.url: "mysql://user:pass@host:3306/app"` 和 `db.query: "SELECT count(*) as cnt FROM users"`
- **THEN** 系统 SHALL 将其解析为 db checker，执行连通性检测后执行指定 SQL 并进入 expect 校验

#### Scenario: db target 缺少 url
- **WHEN** YAML 中 target 配置 `type: db` 但缺少 `db.url`
- **THEN** 系统 SHALL 以配置错误退出，并提示该 target 缺少 db.url 字段

#### Scenario: db.url 为空字符串
- **WHEN** YAML 中 target 配置 `db.url: ""`
- **THEN** 系统 SHALL 以配置错误退出，并提示 db.url 不能为空

#### Scenario: db.query 为空字符串
- **WHEN** YAML 中 target 配置 `db.query: ""`
- **THEN** 系统 SHALL 以配置错误退出，并提示 db.query 不能为空字符串（如不需要查询则不配置该字段）

#### Scenario: db 分组未知字段失败
- **WHEN** YAML 中 db target 的 `db` 分组包含 `timeout: 5` 等未知字段
- **THEN** 系统 SHALL 以配置错误退出，提示 db 分组包含未知字段

#### Scenario: SQLite 连接字符串
- **WHEN** YAML 中 target 配置 `db.url: "sqlite:///data/app.db"`
- **THEN** 系统 SHALL 将其解析为 db checker，使用 SQLite 文件数据库

#### Scenario: url 格式由 Bun 运行时校验
- **WHEN** YAML 中 target 配置 `db.url` 为 Bun 不支持的格式
- **THEN** 系统 SHALL 在执行阶段捕获连接错误并作为 phase="connect" 的 failure 返回，而非在启动期校验 URL 格式

### Requirement: db checker 执行
系统 SHALL 按 db target 配置连接数据库并执行查询，每次执行都新建连接并在完成后关闭。连接能力本身作为监控指标。

#### Scenario: 仅连接测试成功
- **WHEN** db target 未配置 `db.query` 且数据库连接成功
- **THEN** 系统 SHALL 内部执行 `SELECT 1` 验证连通性，记录 `matched=true` 和 `durationMs`

#### Scenario: 连接失败
- **WHEN** db target 的数据库连接失败（网络不通、认证错误、数据库不存在等）
- **THEN** 系统 SHALL 记录 `matched=false`，failure 的 phase 为 `"connect"`，message 包含可读错误信息

#### Scenario: 查询执行成功
- **WHEN** db target 配置了 `db.query` 且 SQL 执行成功返回结果集
- **THEN** 系统 SHALL 记录 `durationMs`（从连接开始到查询完成），并进入 expect 校验

#### Scenario: 查询执行失败
- **WHEN** db target 配置了 `db.query` 且 SQL 执行报错（语法错误、权限不足、表不存在等）
- **THEN** 系统 SHALL 记录 `matched=false`，failure 的 phase 为 `"query"`，message 包含数据库返回的错误信息

#### Scenario: 执行超时
- **WHEN** db target 在 timeout 时间内未完成（连接或查询）
- **THEN** 系统 SHALL 关闭连接，记录 `matched=false`，failure 的 phase 为 `"connect"` 或 `"query"`（取决于超时发生的阶段），message 包含超时信息

#### Scenario: 每次执行新建连接
- **WHEN** db target 被引擎调度执行
- **THEN** 系统 SHALL 创建新的 SQL 连接实例（max: 1），执行完成后立即关闭连接（close timeout: 0）

#### Scenario: 使用 unsafe 执行用户 SQL
- **WHEN** db target 配置了 `db.query`
- **THEN** 系统 SHALL 使用 `sql.unsafe(query)` 执行用户配置的 SQL 文本，不限制 SQL 类型

#### Scenario: 响应 abort signal
- **WHEN** 引擎注入的 `ctx.signal` 被 abort
- **THEN** 系统 SHALL 立即关闭数据库连接

### Requirement: db expect 校验
系统 SHALL 支持 db 专用 expect，包括 `durationMs`、`rowCount`、`rows` 和 `result`，按 durationMs、rowCount、rows、result 的阶段顺序快速失败。`durationMs` 和 `rowCount` SHALL 使用共享 `RawValueExpectation` 输入，并在 resolve 阶段转换为运行期 `ValueExpectation`。`rows` SHALL 保留按行索引匹配列值的语义，Raw 类型为 `Array<RawKeyedExpectations>`（外层数组按行索引，内层每个元素表达该行的列值断言），Resolved 类型为 `Array<KeyedExpectations>`。每个行规则中列值 primitive 字面量等价于 `{equals: <literal>}`。`result` MUST 使用共享 `RawContentExpectations` 数组输入，并在运行期以 `ContentExpectations` 对查询结果对象 `{ rows, rowCount }` 执行断言。

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

#### Scenario: rowCount 校验通过
- **WHEN** db target 配置 `expect.rowCount: { gte: 1 }` 且查询返回 5 行
- **THEN** 系统 SHALL 判定 rowCount 阶段通过，继续后续 expect 阶段

#### Scenario: rowCount 校验失败
- **WHEN** db target 配置 `expect.rowCount: { gte: 1 }` 且查询返回 0 行
- **THEN** 系统 SHALL 返回 `matched=false`，failure 的 phase 为 `rowCount`，path 为 `rowCount`，expected 为 `{ gte: 1 }`，actual 为 0

#### Scenario: rows 按索引匹配列值 matcher 形式
- **WHEN** db target 配置 `expect.rows: [{ cnt: { gte: 100 } }]` 且查询首行 cnt 列值为 50
- **THEN** 系统 SHALL 返回 `matched=false`，failure 的 phase 为 `row`，path 为 `rows[0].cnt`

#### Scenario: rows 按索引匹配列值字面量形式
- **WHEN** db target 配置 `expect.rows: [{ status: "active" }]` 且查询首行 status 列值为 `"active"`
- **THEN** 系统 SHALL 在 resolve 阶段将该列值解析为 `{equals: "active"}` 并判定该行该列通过

#### Scenario: rows 只检查声明的列
- **WHEN** db target 配置 `expect.rows: [{ cnt: { gte: 1 } }]` 且查询首行包含 cnt、name、age 三列
- **THEN** 系统 SHALL 仅检查 cnt 列，忽略 name 和 age 列

#### Scenario: rows 结果行数不足
- **WHEN** db target 配置 `expect.rows` 包含 3 个元素但查询仅返回 2 行
- **THEN** 系统 SHALL 返回 `matched=false`，failure 的 phase 为 `row`，message 说明结果行数不足

#### Scenario: result JSONPath 校验
- **WHEN** db target 查询返回首行 `{status: "active"}` 且配置 `expect.result: [{json: {path: "$.rows[0].status", equals: "active"}}]`
- **THEN** 系统 SHALL 基于 `{rows, rowCount}` 结果对象执行 JSONPath，并判定 result 阶段通过

#### Scenario: result rowCount 校验
- **WHEN** db target 查询返回 2 行且配置 `expect.result: [{json: {path: "$.rowCount", equals: 2}}]`
- **THEN** 系统 SHALL 判定 result 阶段通过

#### Scenario: 无 query 时结果类 expect 被忽略
- **WHEN** db target 未配置 `db.query` 但配置了 `expect.rowCount`、`expect.rows` 或 `expect.result`
- **THEN** 系统 SHALL 忽略这些查询结果断言（仅 `durationMs` 生效）

#### Scenario: 快速失败顺序
- **WHEN** db target 同时配置 durationMs、rowCount、rows 和 result
- **THEN** 系统 SHALL 按 durationMs → rowCount → rows → result 顺序校验，任一阶段失败立即返回

### Requirement: db checker 启动期配置校验
系统 SHALL 在启动期对 db checker 的配置契约和语义执行严格校验。Db target 的 `db` 分组 SHALL 只允许 `url` 和 `query` 字段。Db expect SHALL 只允许 `durationMs`、`rowCount`、`rows` 和 `result` 字段。未知字段、非法 ValueExpectation、非法 ContentExpectations、非法 regex 和 ReDoS 风险正则 MUST 导致启动期配置错误。语义校验 MUST NOT 修改 Raw db expect 输入。

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

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

#### Scenario: db expect rows 非法
- **WHEN** YAML 中 db target 配置 `expect.rows` 不是对象数组
- **THEN** 系统 SHALL 以配置错误退出，提示 expect.rows 必须为对象数组

#### Scenario: db expect rows 元素列值非法
- **WHEN** YAML 中 db target 配置 `expect.rows: [{ cnt: { foo: 1 } }]`，其中 foo 不是合法 matcher
- **THEN** 系统 SHALL 以配置错误退出，提示 rows 中包含未知 matcher

#### Scenario: db expect rows 对象值必须显式 equals
- **WHEN** YAML 中 db target 配置 `expect.rows: [{ payload: { status: "ok" } }]` 且 payload 值不是合法 matcher 对象
- **THEN** 系统 SHALL 以配置错误退出，提示对象值必须显式写成 `{equals: {status: "ok"}}`

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

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

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