<h1 align="center">DiAL</h1>

<p align="center">
  <strong>轻量级多类型拨测监控工具</strong>
</p>

<p align="center">
  基于 Bun + TypeScript 构建 · YAML 配置驱动 · 内置 Dashboard
</p>

---

DiAL 是一个自托管的拨测监控工具，支持 **HTTP**、**命令行**、**数据库**、**TCP**、**UDP**、**ICMP** 和 **LLM** 多种拨测类型。通过 YAML 配置文件定义拨测目标，后端定时并发执行拨测并将结果持久化到本地 SQLite，前端 Dashboard 展示各目标的实时状态、可用率和耗时趋势。

**功能亮点：**

- 多种拨测类型：HTTP（GET/POST/PUT 等）、Cmd（命令行执行）、DB（PostgreSQL/MySQL/SQLite）、TCP（端口可达性 + Banner 探测）、UDP（自定义 payload 请求-响应）、ICMP（存活检测、延迟、丢包率）、LLM（大模型服务应用层健康检查）
- 丰富的校验规则：状态码、响应头、JSONPath、CSS 选择器、XPath、正则匹配、数值比较等
- 结构化观测数据：检查结果保留按需读取的 HTTP body 预览、TCP/UDP 响应摘要、ICMP 丢包率、CMD 输出预览、LLM token 用量等 observation，便于排障和后续分析
- 响应式 Dashboard：实时状态、可用率统计、耗时趋势图、手动/自动刷新、版本号展示
- 多主题支持：系统、明亮、黑暗三种主题模式
- 零外部依赖：数据存储使用 SQLite，无需额外数据库服务

## 版本管理

DiAL 使用 `package.json.version` 作为唯一版本源，Dashboard Header 展示当前运行实例版本号（如 `v0.1.0`）。

**版本升迁命令：**

```bash
bun run version:patch  # 升迁 patch 版本（0.1.0 -> 0.1.1）
bun run version:minor  # 升迁 minor 版本（0.1.0 -> 0.2.0）
bun run version:major  # 升迁 major 版本（0.1.0 -> 1.0.0）
bun run version:set 0.2.0  # 显式设置版本
```

版本升迁仅更新 `package.json`，不自动创建 git commit、tag 或 changelog。

## 应用截图

|        | 亮色                                                | 暗色                                              |
| ------ | --------------------------------------------------- | ------------------------------------------------- |
| 主页   | ![index-light](assets/screenshot/light_index.png)   | ![index-dark](assets/screenshot/dark_index.png)   |
| 详情页 | ![detail-light](assets/screenshot/light_detail.png) | ![detail-dark](assets/screenshot/dark_detail.png) |

## 快速开始

**前置条件：** [Bun](https://bun.sh/) >= 1.0

ICMP checker 依赖系统 `ping` 命令。精简容器镜像需额外安装，例如 Alpine 可安装 `iputils-ping`。

```bash
# 克隆仓库
git clone https://github.com/your-org/DiAL.git
cd DiAL

# 安装依赖
bun install

# 复制示例配置并按需修改
cp probes.example.yaml probes.yaml

# 启动开发服务器
bun run dev probes.yaml
```

`bun run dev` 会同时启动 Vite 开发服务器（`http://127.0.0.1:5173`）和 API 服务器（`http://127.0.0.1:3000`），访问前端地址即可使用 Dashboard。

## 生产部署

```bash
# 构建
bun run build

# 运行
./dist/dial-server ./probes.yaml
```

构建产物为独立可执行文件，只需一个 YAML 配置文件即可运行。

### 跨平台发布打包

```bash
# 编译全部 7 个目标平台
bun run release

# 编译指定平台
bun run release --target linux-x64
bun run release --target linux-x64,windows-x64,darwin-arm64
```

支持的目标平台：`linux-x64`、`linux-arm64`、`linux-x64-musl`、`linux-arm64-musl`、`windows-x64`、`darwin-x64`、`darwin-arm64`

**产出物结构：**

```
dist/release/
├── binaries/                    ← 裸二进制（带版本号和平台标识）
│   ├── dial-server-0.1.0-linux-x64
│   ├── dial-server-0.1.0-windows-x64.exe
│   └── ...
└── packages/                    ← tar.gz 压缩包 + SHA256 校验和
    ├── dial-server_0.1.0_linux_x64.tar.gz
    ├── dial-server_0.1.0_linux_x64.tar.gz.sha256
    └── ...
```

压缩包内含可执行文件、`probes.example.yaml` 和 `LICENSE`，解压后可直接使用。

## 配置文件

程序通过 YAML 配置文件定义所有运行参数，完整示例参见 [`probes.example.yaml`](probes.example.yaml)。

### 配置文件结构

```yaml
# yaml-language-server: $schema=./probe-config.schema.json

server: # 服务配置（均可省略）
  listen:
    host: "127.0.0.1"
    port: 3000
  storage:
    dataDir: "/tmp/probes_data"
    retention: "7d"
  logging:
    level: "info"
    file:
      path: "<dataDir>/logs/dial.log"

probes: # 拨测运行时配置（可省略）
  execution:
    maxConcurrentChecks: 20

variables: # 配置变量（可省略）
  env_name: "生产"
  base_url: "https://api.example.com"

defaults: # 全局默认值（均可省略）
  interval: "30s"
  timeout: "10s"
  # http: ...
  # cmd: ...
  # llm: ...

targets: # 拨测目标列表（必填）
  - id: "baidu-home"
    name: "Baidu"
    type: http
    http:
      url: "https://www.baidu.com"
    expect:
      # ...
  - id: "my-cmd"
    name: "脚本检查"
    type: cmd
    cmd:
      # ...
    expect:
      # ...
  # ... 更多 targets
```

### server.listen — 监听配置

| 字段   | 说明     | 必填 | 默认值      |
| ------ | -------- | ---- | ----------- |
| `host` | 监听地址 | 否   | `127.0.0.1` |
| `port` | 监听端口 | 否   | `3000`      |

### server.storage — 存储配置

| 字段        | 说明                                             | 必填 | 默认值   |
| ----------- | ------------------------------------------------ | ---- | -------- |
| `dataDir`   | 数据目录，相对路径基于配置文件所在目录解析       | 否   | `./data` |
| `retention` | 历史数据保留时长，支持 `ms`/`s`/`m`/`h`/`d` 单位 | 否   | `7d`     |

### probes.execution — 拨测运行时配置

| 字段                  | 说明           | 必填 | 默认值 |
| --------------------- | -------------- | ---- | ------ |
| `maxConcurrentChecks` | 最大并发拨测数 | 否   | `20`   |

### server.logging — 日志配置

| 字段                                     | 说明                                           | 必填 | 默认值                    |
| ---------------------------------------- | ---------------------------------------------- | ---- | ------------------------- |
| `server.logging.level`                   | 全局日志等级，console 和 file 未指定时继承此值 | 否   | `info`                    |
| `server.logging.console.level`           | 控制台日志等级                                 | 否   | 继承 `level`              |
| `server.logging.file.level`              | 文件日志等级                                   | 否   | 继承 `level`              |
| `server.logging.file.path`               | 日志文件路径，相对路径基于配置文件目录解析     | 否   | `<dataDir>/logs/dial.log` |
| `server.logging.file.rotation.size`      | 按大小滚动，支持 `KB`/`MB`/`GB` 单位           | 否   | `50MB`                    |
| `server.logging.file.rotation.frequency` | 按时间滚动：`hourly`、`daily`、`weekly`        | 否   | `daily`                   |
| `server.logging.file.rotation.maxFiles`  | 保留的归档文件数量（不含活跃日志）             | 否   | `14`                      |

日志等级支持：`trace`、`debug`、`info`、`warn`、`error`、`fatal`。

控制台始终输出（pretty 格式），文件始终输出 JSONL 格式并支持滚动。`rotation.size` 和 `rotation.frequency` 任一条件触发即滚动。

### defaults — 全局默认值

| 字段       | 说明     | 必填 | 默认值 |
| ---------- | -------- | ---- | ------ |
| `interval` | 拨测间隔 | 否   | `30s`  |
| `timeout`  | 超时时间 | 否   | `10s`  |

各 checker 专属的默认配置见对应章节。

### variables — 配置变量

`variables` 是顶层动态键值表，key 必须符合 `[a-zA-Z_][a-zA-Z0-9_]*`，value 仅支持 string、number、boolean。target 中的字符串值可引用变量：

- `${key}`：引用 variables 或环境变量
- `${key|default}`：变量和环境变量都不存在时使用默认值，第一个 `|` 后的内容为默认值
- `$${key}`：转义输出字面量 `${key}`

解析优先级为 `variables -> process.env -> 默认值`。字段值完整等于单个变量引用时会保留 number/boolean/string 类型；部分拼接时统一转为字符串。变量替换仅作用于 `targets`，且不会替换 `id` 和 `type` 字段。

### targets — 拨测目标列表（必填）

每个 target 的通用字段：

| 字段          | 说明                                                                                 | 必填 | 默认值    |
| ------------- | ------------------------------------------------------------------------------------ | ---- | --------- |
| `id`          | 目标唯一标识，最长 30 字符，支持字母数字、下划线、连字符，不参与变量替换             | 是   |           |
| `name`        | 展示名称，最长 30 字符，支持变量替换，可省略或显式 null；前端展示时 null 回退到 `id` | 否   |           |
| `description` | 目标描述，最长 500 字符，支持变量替换，可省略或显式 null，允许空字符串               | 否   |           |
| `type`        | 目标类型：`http`、`cmd`、`db`、`tcp`、`udp`、`icmp`、`llm`                           | 是   |           |
| `group`       | 分组名称                                                                             | 否   | `default` |
| `interval`    | 覆盖全局拨测间隔                                                                     | 否   |           |
| `timeout`     | 覆盖全局超时时间                                                                     | 否   |           |

---

### HTTP Checker（`type: http`）

**全局默认值（`defaults.http`）**

| 字段           | 说明                                               | 必填 | 默认值  |
| -------------- | -------------------------------------------------- | ---- | ------- |
| `maxBodyBytes` | 响应体最大字节数                                   | 否   | `100MB` |
| `headers`      | 默认请求头（target 中的 headers 会合并覆盖同名头） | 否   |         |

**配置项**

| 字段                | 说明                                    | 必填 | 默认值  |
| ------------------- | --------------------------------------- | ---- | ------- |
| `http.url`          | 目标 URL                                | 是   |         |
| `http.method`       | HTTP 方法                               | 否   | `GET`   |
| `http.headers`      | 请求头（与 defaults.http.headers 合并） | 否   |         |
| `http.body`         | 请求体                                  | 否   |         |
| `http.ignoreSSL`    | 忽略 HTTPS 证书校验                     | 否   | `false` |
| `http.maxRedirects` | 最大重定向跟随次数                      | 否   | `0`     |

**expect 校验项**

| 字段         | 说明                                               | 必填 | 默认值  |
| ------------ | -------------------------------------------------- | ---- | ------- |
| `status`     | 可接受的状态码列表，支持精确码和范围（如 `"2xx"`） | 否   | `[200]` |
| `headers`    | 响应头校验，使用动态键名和 `KeyedExpectations`     | 否   |         |
| `body`       | 响应体校验，使用 `ContentExpectations` 数组        | 否   |         |
| `durationMs` | 完整执行耗时校验，使用 `ValueMatcher`              | 否   |         |

**配置示例**

```yaml
- id: "json-api"
  name: "JSON API 示例"
  type: http
  http:
    url: "https://httpbin.org/json"
    headers:
      Authorization: "Bearer token"
  expect:
    status: [200]
    headers:
      Content-Type:
        contains: "application/json"
    body:
      - json:
          path: "$.slideshow.title"
          equals: "Sample Slide Show"
    durationMs:
      lte: 10000
```

---

### Cmd Checker（`type: cmd`）

**全局默认值（`defaults.cmd`）**

| 字段             | 说明                                   | 必填 | 默认值  |
| ---------------- | -------------------------------------- | ---- | ------- |
| `maxOutputBytes` | 输出最大字节数                         | 否   | `100MB` |
| `cwd`            | 默认工作目录（相对于配置文件所在目录） | 否   | `.`     |

**配置项**

| 字段       | 说明                                   | 必填 | 默认值 |
| ---------- | -------------------------------------- | ---- | ------ |
| `cmd.exec` | 可执行文件名或路径                     | 是   |        |
| `cmd.args` | 命令行参数列表                         | 否   | `[]`   |
| `cmd.env`  | 环境变量覆盖（继承进程环境变量并合并） | 否   |        |
| `cmd.cwd`  | 工作目录（相对于配置文件所在目录）     | 否   |        |

**expect 校验项**

| 字段         | 说明                                          | 必填 | 默认值 |
| ------------ | --------------------------------------------- | ---- | ------ |
| `exitCode`   | 可接受的退出码列表                            | 否   | `[0]`  |
| `stdout`     | 标准输出校验，使用 `ContentExpectations` 数组 | 否   |        |
| `stderr`     | 标准错误校验，使用 `ContentExpectations` 数组 | 否   |        |
| `durationMs` | 完整执行耗时校验，使用 `ValueMatcher`         | 否   |        |

**配置示例**

```yaml
- id: "bun-script"
  name: "Bun 脚本检查"
  type: cmd
  cmd:
    exec: "bun"
    args: ["-e", "console.log('ok')"]
  expect:
    exitCode: [0]
    stdout:
      - contains: "ok"
```

---

### DB Checker（`type: db`）

**配置项**

| 字段       | 说明                                                          | 必填 | 默认值 |
| ---------- | ------------------------------------------------------------- | ---- | ------ |
| `db.url`   | 数据库连接字符串，支持 `postgres://`、`mysql://`、`sqlite://` | 是   |        |
| `db.query` | SQL 查询语句，不配置时仅测试连接                              | 否   |        |

**expect 校验项**

| 字段         | 说明                                                                    | 必填 | 默认值 |
| ------------ | ----------------------------------------------------------------------- | ---- | ------ |
| `rowCount`   | 查询返回行数校验，使用 `ValueMatcher`                                   | 否   |        |
| `rows`       | 查询结果逐行校验，数组内每行为列名到 `KeyedExpectations` 的映射         | 否   |        |
| `result`     | 完整查询结果 `{ rows, rowCount }` 校验，使用 `ContentExpectations` 数组 | 否   |        |
| `durationMs` | 完整执行耗时校验，使用 `ValueMatcher`                                   | 否   |        |

**配置示例**

```yaml
- id: "sqlite-query"
  name: "SQLite 数据库检查"
  type: db
  db:
    url: "sqlite:///path/to/db.sqlite"
    query: "SELECT COUNT(*) as cnt FROM users WHERE status = 'active'"
  expect:
    durationMs:
      lte: 5000
    rowCount: { gte: 1 }
    rows:
      - cnt: { gte: 0 }
```

---

### TCP Checker（`type: tcp`）

**配置项**

| 字段                    | 说明                                        | 必填 | 默认值  |
| ----------------------- | ------------------------------------------- | ---- | ------- |
| `tcp.host`              | 目标主机地址                                | 是   |         |
| `tcp.port`              | 目标端口（1-65535）                         | 是   |         |
| `tcp.readBanner`        | 是否读取服务端 banner                       | 否   | `false` |
| `tcp.bannerReadTimeout` | banner 读取超时（毫秒）                     | 否   | `2000`  |
| `tcp.maxBannerBytes`    | banner 最大字节数，支持 `KB`/`MB`/`GB` 单位 | 否   | `4KB`   |

**expect 校验项**

| 字段         | 说明                                                                      | 必填 | 默认值 |
| ------------ | ------------------------------------------------------------------------- | ---- | ------ |
| `connected`  | 期望连接结果，`true` 可达或 `false` 期望不可达                            | 否   | `true` |
| `banner`     | Banner 内容校验，使用 `ContentExpectations` 数组，需开启 `tcp.readBanner` | 否   |        |
| `durationMs` | 完整执行耗时校验，使用 `ValueMatcher`                                     | 否   |        |

**配置示例**

```yaml
- id: "redis-port"
  name: "Redis 端口可达"
  type: tcp
  tcp:
    host: "127.0.0.1"
    port: 6379
  expect:
    durationMs:
      lte: 3000
```

---

### UDP Checker（`type: udp`）

**全局默认值（`defaults.udp`）**

| 字段               | 说明                                     | 必填 | 默认值 |
| ------------------ | ---------------------------------------- | ---- | ------ |
| `encoding`         | payload 编码                             | 否   | `text` |
| `responseEncoding` | 响应解码                                 | 否   | `text` |
| `maxResponseBytes` | 响应最大字节数，支持 `KB`/`MB`/`GB` 单位 | 否   | `4KB`  |

**配置项**

| 字段                   | 说明                                     | 必填 | 默认值 |
| ---------------------- | ---------------------------------------- | ---- | ------ |
| `udp.host`             | 目标主机地址                             | 是   |        |
| `udp.port`             | 目标端口（1-65535）                      | 是   |        |
| `udp.payload`          | 发送数据                                 | 否   | `""`   |
| `udp.encoding`         | payload 编码：`text`、`hex`、`base64`    | 否   | `text` |
| `udp.responseEncoding` | 响应解码：`text`、`hex`、`base64`        | 否   | `text` |
| `udp.maxResponseBytes` | 响应最大字节数，支持 `KB`/`MB`/`GB` 单位 | 否   | `4KB`  |

**expect 校验项**

| 字段           | 说明                                          | 必填 | 默认值 |
| -------------- | --------------------------------------------- | ---- | ------ |
| `responded`    | 期望是否收到响应                              | 否   | `true` |
| `response`     | 响应内容校验，使用 `ContentExpectations` 数组 | 否   |        |
| `responseSize` | 响应字节数校验，使用 `ValueMatcher`           | 否   |        |
| `sourceHost`   | 响应来源地址校验，使用 `ValueMatcher`         | 否   |        |
| `sourcePort`   | 响应来源端口校验，使用 `ValueMatcher`         | 否   |        |
| `durationMs`   | 完整执行耗时校验，使用 `ValueMatcher`         | 否   |        |

**配置示例**

```yaml
- id: "udp-heartbeat"
  name: "UDP 心跳检测"
  type: udp
  udp:
    host: "127.0.0.1"
    port: 9000
    payload: "PING"
  expect:
    responded: true
    response:
      - contains: "PONG"
    durationMs:
      lte: 100
```

---

### ICMP Checker（`type: icmp`）

**配置项**

| 字段              | 说明                      | 必填 | 默认值 |
| ----------------- | ------------------------- | ---- | ------ |
| `icmp.host`       | 目标主机地址              | 是   |        |
| `icmp.count`      | ICMP 包数量，范围 `1-100` | 否   | `3`    |
| `icmp.packetSize` | ICMP 包大小（bytes）      | 否   | `56`   |

ICMP checker 通过系统 `ping` 命令执行 ICMP 探测，支持 Linux、macOS 和 Windows 输出解析。

**expect 校验项**

| 字段                | 说明                                                | 必填 | 默认值 |
| ------------------- | --------------------------------------------------- | ---- | ------ |
| `alive`             | 期望主机可达性                                      | 否   | `true` |
| `packetLossPercent` | 丢包率百分比校验，范围 `0-100`，使用 `ValueMatcher` | 否   |        |
| `avgLatencyMs`      | 平均延迟校验，使用 `ValueMatcher`                   | 否   |        |
| `maxLatencyMs`      | 最大单次延迟校验，使用 `ValueMatcher`               | 否   |        |
| `durationMs`        | 完整执行耗时校验，使用 `ValueMatcher`               | 否   |        |

**配置示例**

```yaml
- id: "gateway-icmp"
  name: "网关 ICMP 可达"
  type: icmp
  icmp:
    host: "10.0.0.1"
    count: 3
    packetSize: 56
  expect:
    alive: true
    packetLossPercent:
      lte: 10
    avgLatencyMs:
      lte: 100
    maxLatencyMs:
      lte: 300
    durationMs:
      lte: 5000
```

---

### LLM Checker（`type: llm`）

**全局默认值（`defaults.llm`）**

| 字段              | 说明                | 必填 | 默认值  |
| ----------------- | ------------------- | ---- | ------- |
| `mode`            | 调用模式            | 否   |         |
| `headers`         | 默认请求头          | 否   |         |
| `ignoreSSL`       | 忽略 HTTPS 证书校验 | 否   | `false` |
| `options`         | 生成选项            | 否   |         |
| `providerOptions` | Provider 专属选项   | 否   |         |

不支持 `provider`、`url`、`model`、`key`、`authToken`、`prompt`。

**配置项**

| 字段                  | 说明                                                        | 必填 | 默认值  |
| --------------------- | ----------------------------------------------------------- | ---- | ------- |
| `llm.provider`        | 模型提供方：`openai`、`openai-responses`、`anthropic`       | 是   |         |
| `llm.url`             | API base URL                                                | 是   |         |
| `llm.model`           | 模型名称                                                    | 是   |         |
| `llm.prompt`          | 单轮 prompt                                                 | 是   |         |
| `llm.mode`            | 调用模式：`http`（非流式）或 `stream`（流式）               | 否   | `http`  |
| `llm.key`             | API key，支持 `${VAR}` 变量替换                             | 否   | `""`    |
| `llm.authToken`       | Bearer token（仅 `anthropic` provider，与 `key` 互斥）      | 否   |         |
| `llm.headers`         | 附加请求头（与 `defaults.llm.headers` 合并）                | 否   |         |
| `llm.ignoreSSL`       | 忽略 HTTPS 证书校验                                         | 否   | `false` |
| `llm.options`         | 生成选项（与 `defaults.llm.options` 合并）                  | 否   |         |
| `llm.providerOptions` | Provider 专属选项（与 `defaults.llm.providerOptions` 合并） | 否   |         |

`llm.options` 支持 `maxOutputTokens`（默认 `16`）、`temperature`（默认 `0`）、`topP`、`topK`、`presencePenalty`、`frequencyPenalty`、`stopSequences`、`seed`。

**expect 校验项**

| 字段              | 说明                                                                   | 必填 | 默认值  |
| ----------------- | ---------------------------------------------------------------------- | ---- | ------- |
| `status`          | 可接受的状态码列表，支持精确码和范围（如 `"2xx"`）                     | 否   | `[200]` |
| `headers`         | 响应头校验，使用动态键名和 `KeyedExpectations`                         | 否   |         |
| `output`          | 模型输出校验，使用 `ContentExpectations` 数组                          | 否   |         |
| `finishReason`    | finish reason 校验，使用 `ValueMatcher`                                | 否   |         |
| `rawFinishReason` | 原始 finish reason 校验，使用 `ValueMatcher`                           | 否   |         |
| `usage`           | Token usage 校验（`inputTokens`/`outputTokens`/`totalTokens` matcher） | 否   |         |
| `stream`          | 流式断言（`completed`、`firstTokenMs` matcher，仅 `mode: stream`）     | 否   |         |
| `durationMs`      | 完整执行耗时校验，使用 `ValueMatcher`                                  | 否   |         |

**配置示例**

```yaml
- id: "llm-openai-probe"
  name: "OpenAI 健康检查"
  type: llm
  llm:
    provider: openai
    url: "https://api.openai.com/v1"
    model: "gpt-4o-mini"
    prompt: "Say OK"
    key: "${OPENAI_API_KEY}"
  expect:
    status: [200]
    finishReason: "stop"
    output:
      - contains: "OK"
```

---

### 通用校验规则

#### ContentExpectations 校验项

`body`、`stdout`、`stderr`、`banner`、`response`、`output`、`result` 均使用数组：

- `contains` — 响应体包含指定文本
- `regex` — 正则匹配（启动期会拒绝存在 ReDoS 风险的模式）
- `json` — JSONPath 提取值比较（`path` 必填，如 `$.slideshow.title`）
- `css` — CSS 选择器提取 HTML 元素（`selector` 必填，`attr` 可选提取属性）
- `xpath` — XPath 提取 XML/HTML 节点（`path` 必填，如 `/html/body/h1/text()`）

#### ValueMatcher

`equals`、`contains`、`regex`、`empty`、`exists`、`gte`、`lte`、`gt`、`lt`。`equals` 支持 JSON 深度相等；`regex` 固定使用无 flags 正则；提取器未配置 matcher 时等价于 `exists: true`。ValueMatcher expect 字段也可直接写 string、number、boolean 或 null，等价于 `{ equals: value }`；数组和对象必须显式写成 `{ equals: ... }`。

#### KeyedExpectations

`headers`、`rows` 中每行使用的校验结构，支持 ValueMatcher 的全部字段。

### 补充说明

- **大小说明**：`maxBodyBytes`、`maxOutputBytes`、`maxResponseBytes`、`maxBannerBytes` 支持 `KB`、`MB`、`GB` 单位，也可直接使用数字
- **时长格式**：`500ms`、`30s`、`5m`、`2h`、`7d`
- **JSON Schema**：仓库根目录导出 `probe-config.schema.json`，在 YAML 文件顶部添加 `# yaml-language-server: $schema=./probe-config.schema.json` 即可在编辑器中获得提示和校验
- **旧字段移除**：`maxDurationMs`、`maxPacketLoss`、`maxAvgLatencyMs`、`maxMaxLatencyMs` 和旧正则字段 `match` 已移除，请分别改用 `durationMs`、ICMP matcher 字段和 `regex`

> **注意：** 配置校验在启动时执行，非法配置会阻止启动并输出错误信息。除动态键值表（`headers`、`env`、`variables`）外，未知字段会导致启动失败，请使用 YAML 注释。

## 目标状态判定

采用单层判定模型：

- **UP** = 拨测结果符合 expect 规则
- **DOWN** = 拨测结果不符合 expect 规则

执行失败（网络错误、超时、进程崩溃）和 expect 不匹配都统一为 DOWN，通过 `failure.kind` 区分原因（`"error"` vs `"mismatch"`）。

API 返回的检查结果包含 `detail` 和 `observation`：`detail` 是后端按 checker 类型从结构化 observation 动态生成的人可读摘要，`observation` 保存该次检查的结构化观测数据。`detail` 不写入 SQLite，存储层仅持久化 `observation` JSON、`failure` JSON、匹配状态、耗时和时间戳。

## 开发

```bash
bun run check   # schema:check + typecheck + lint + test
bun run verify  # check + build
```

开发相关文档（项目结构、构建、测试、代码规范等）请参阅 [DEVELOPMENT.md](DEVELOPMENT.md)。

## License

Apache-2.0
