1
0
Files
DiAL/openspec/specs/probe-data-store/spec.md
lanyuanxiaoyao 60a54b483f refactor: expect 类型模型重构,Raw/Resolved 双层分离与断言基础设施内聚
- 重命名 ContentRules→ContentExpectations, KeyValueExpect→KeyedExpectations
- 新增 Raw/Resolved 双层模型:resolve 阶段物化为执行计划,store 持久化 Raw 快照
- HTTP body 按需读取:status/headers 失败或无 body expectation 时不读取 body
- 新增 displayValueExpectation() 解包 failure.expected 用户可读展示
- 修复 checkEarlyTimeout 独立 lte/lt 检查,修复 KeyedExpectations JSON Schema
- 新增 expect/value.ts(resolve/check/display)、keyed.ts、content.ts、headers.ts、status.ts
- 删除旧 normalize.ts/matcher.ts/validate-matcher.ts/key-value.ts
- 更新 DEVELOPMENT.md:expect 五层管线表、displayValueExpectation、1.7↔1.10 交叉引用
- 同步 13 个 main specs,归档 refactor-expect-type-model 变更(62/62 tasks)
2026-05-20 16:12:48 +08:00

13 KiB
Raw Blame History

Purpose

定义基于 SQLite 的拨测数据持久化存储targets 同步含分组信息、check_results 追加写入、Dashboard 和 Metrics 数据查询支持、延迟百分位取数、时间范围和分页查询、索引与聚合查询。

Requirements

Requirement: SQLite 数据库初始化

系统 SHALL 使用 Bun 内置 bun:sqlite 模块在配置的数据目录下创建 SQLite 数据库文件,并以 WAL 模式运行。数据库 schema MUST 支持 typed checker target 和结构化检查结果targets 表 MUST 包含 grp 列存储分组信息、active 列标记活跃状态,且 targets 表的 namedescription 列 MUST 允许 NULL。check_results 表的外键约束 SHALL 使用 ON DELETE RESTRICT

Scenario: 首次启动创建数据库

  • WHEN 指定的数据目录下不存在数据库文件
  • THEN 系统 SHALL 创建数据库文件并初始化 targets 表(含 active INTEGER NOT NULL DEFAULT 1 列)和 check_results 表(外键约束为 ON DELETE RESTRICTcheck_results 表包含 idINTEGER PRIMARY KEY AUTOINCREMENT、target_idTEXT NOT NULL、timestampTEXT NOT NULL、matchedINTEGER NOT NULL、duration_msREAL、observationTEXT、failureTEXT不包含 status_detail 列,不包含 success 列

Scenario: targets name 列允许 NULL

  • WHEN 系统首次创建 targets 表
  • THEN targets.name 列 SHALL 允许存储 NULL

Scenario: 数据目录不存在

  • WHEN 配置的数据目录路径不存在
  • THEN 系统 SHALL 自动创建该目录

Scenario: 数据库已存在时启动

  • WHEN 数据库文件已存在
  • THEN 系统 SHALL 直接打开数据库,不重新建表

Scenario: 外键约束

  • THEN 系统 SHALL 启用 PRAGMA foreign_keys = ON

Scenario: 级联删除改为限制删除

  • THEN check_results 表的外键约束 SHALL 使用 ON DELETE RESTRICT,确保删除 target 时数据库层面阻止操作而非级联删除关联记录

Requirement: targets 表同步

系统 SHALL 在启动时将 YAML 配置中的目标列表同步到 SQLite targets 表,并持久化 target 类型、展示名称元信息、展示摘要、领域配置、调度配置、变量替换后的 Raw expect 配置快照、分组信息和目标说明。配置中不存在的 target SHALL 被标记为非活跃而非删除。targets.expect SHALL 存储 Raw expect JSON系统 MUST NOT 将 Resolved expect 执行计划、ContentExpectation.kind union 或已归一化 matcher 包装结构写入该列。

Scenario: 首次同步目标

  • WHEN 数据库为空且 YAML 中定义了 N 个 typed target
  • THEN 系统 SHALL 将所有目标插入 targets 表,包含 name、description、type、target、config、interval_ms、timeout_ms、expect、grp 和 active=1其中 name 和 description 均可为 NULLexpect 列保存变量替换后的 Raw expect JSON

Scenario: 配置变更后重新同步

  • WHEN YAML 配置发生变更(新增、删除或修改目标)后重启
  • THEN 系统 SHALL 根据 id 字段匹配新增的插入active=1、删除的设置 active=0不删除行、修改的更新含 name、description 和 grp 字段),已存在的目标 SHALL 设置 active=1

Scenario: 配置中移除目标

  • WHEN YAML 配置中移除了某个 target该 target 在数据库中存在且 active=1
  • THEN 系统 SHALL 将该 target 的 active 设置为 0保留该行及所有关联的 check_results

Scenario: 配置中恢复已移除目标

  • WHEN YAML 配置中重新添加了之前移除的 target数据库中 active=0
  • THEN 系统 SHALL 将该 target 的 active 设置为 1并更新其他字段历史 check_results 保留不变

Scenario: 未配置 name

  • WHEN YAML target 未配置 name
  • THEN targets 表 SHALL 将该目标的 name 存储为 NULL

Scenario: name 显式 null

  • WHEN YAML target 配置 name: null
  • THEN targets 表 SHALL 将该目标的 name 存储为 NULL

Scenario: 未配置 description

  • WHEN YAML target 未配置 description
  • THEN targets 表 SHALL 将该目标的 description 存储为 NULL

Scenario: description 显式 null

  • WHEN YAML target 配置 description: null
  • THEN targets 表 SHALL 将该目标的 description 存储为 NULL

Scenario: expect 列保存 Raw expect

  • WHEN target 配置 expect.body: [{json: {path: "$.status"}}]expect.durationMs: 1000
  • THEN targets 表的 expect 列 SHALL 保存变量替换后的 Raw JSON包含原始 json.pathdurationMs: 1000MUST NOT 保存 resolved kind 字段或 {equals: 1000} 执行计划

Scenario: 未配置 expect 写入 NULL

  • WHEN target 未配置任何 expect
  • THEN targets 表的 expect 列 SHALL 写入 NULL即使 Resolved expect 中存在 checker 默认状态语义

Requirement: check_results 表追加写入

系统 SHALL 将每次检查结果追加写入 check_results 表,不更新或删除已有记录。

Scenario: 写入检查结果

  • WHEN 一次 checker 执行完成
  • THEN 系统 SHALL 插入一条包含 target_id、timestamp、matched、duration_ms、observation、failure 的记录,其中 observation 使用 JSON.stringify 序列化为 TEXT

Scenario: 查询检查结果

  • WHEN 系统查询 latest check 或历史 check_results
  • THEN 存储层 SHALL 返回 observation 字段而非 status_detail 字段,供 API 序列化层反序列化并构造 detail

Scenario: 写入结构化失败信息

  • WHEN checker 执行失败或 expect 不匹配
  • THEN 系统 SHALL 将首个失败原因序列化写入 failure 字段

Requirement: 时间范围查询索引

系统 SHALL 在 check_results 表上创建 (target_id, timestamp) 复合索引,加速按目标和时间范围的查询。

Scenario: 查询某目标的历史记录

  • WHEN 查询指定 target_id 的最近 N 条记录
  • THEN 系统 SHALL 使用索引快速定位,无需全表扫描

Requirement: 目标列表按分组排序

系统 SHALL 保证 targets 查询结果按分组排序返回,且仅返回活跃目标。

Scenario: 分组排序查询

  • WHEN 查询所有 targets
  • THEN 结果 SHALL 仅返回 active=1 的目标,将 "default" 分组目标排在首位,其余分组按 YAML 配置中首次出现的顺序(即 id 自增顺序)排列

Requirement: 聚合查询支持

数据存储 SHALL 支持按时间段获取指标计算所需数据,用于后端应用层计算可用率、平均耗时、延迟范围、趋势分桶和可靠性指标。

Scenario: 轻数据库计算边界

  • WHEN 实现指标相关数据查询
  • THEN 数据库 SHALL 主要负责存储、过滤、排序、分页、LIMIT 和标准 SQL 基础聚合,业务指标语义 SHALL 在后端应用层计算

Scenario: 可使用的基础 SQL 聚合

  • WHEN 查询需要减少返回数据量
  • THEN 系统 MAY 使用标准 SQL 的 COUNT、SUM(CASE)、AVG、MIN、MAX、GROUP BY 等基础能力

Scenario: 避免数据库承载业务语义

  • WHEN 实现状态翻转、故障段、MTTR、最长故障、连续状态、百分位或趋势分桶
  • THEN 系统 SHALL 在后端应用层实现这些规则,不依赖 SQLite 专有函数或复杂窗口函数承载业务语义

Scenario: UP/DOWN 判定

  • WHEN 系统需要判定目标当前状态
  • THEN 系统 SHALL 基于 latestCheck.matched 判定目标 UP 或 DOWNmatched=true 为 UPmatched=false 为 DOWN

Requirement: Dashboard 数据查询支持

ProbeStore SHALL 提供 Dashboard 聚合响应所需的批量取数能力,且所有查询 SHALL 仅涉及活跃 target。

Scenario: 批量获取最新检查

  • WHEN Dashboard API 需要计算当前 up/down 和 lastCheckTime
  • THEN Store SHALL 支持批量获取每个活跃 target 的最新检查记录,避免 N+1 查询

Scenario: 批量获取窗口统计基础数据

  • WHEN Dashboard API 需要计算各 target 在指定 window 内的 totalChecks、upChecks、downChecks 和 availability
  • THEN Store SHALL 支持按 target_id 批量返回指定时间窗口内的基础计数数据,仅包含活跃 target

Scenario: 批量获取最近样本

  • WHEN Dashboard API 需要展示 recentSamples 和计算 capped currentStreak
  • THEN Store SHALL 支持批量获取每个活跃 target 最近 recentLimit 条检查记录,按 target_id 分组且每组按 timestamp 降序排列

Scenario: 获取 Dashboard 异常事件序列

  • WHEN Dashboard API 需要计算 incidents
  • THEN Store SHALL 支持获取指定时间窗口内所有活跃 target 的 { target_id, timestamp, matched } 序列,按 target_id 和 timestamp 升序排列,供后端应用层计算状态翻转

Requirement: 单目标指标取数支持

ProbeStore SHALL 提供单目标 metrics 响应所需的取数能力。仅活跃 target 的指标 SHALL 可查询。

Scenario: 获取目标检查点序列

  • WHEN Metrics API 需要计算趋势分桶、故障段、MTTR、最长故障、故障次数和连续状态
  • THEN Store SHALL 支持获取指定活跃 target 在 from 到 to 时间范围内的 { timestamp, matched, duration_ms } 数组,按 timestamp 升序排列

Scenario: 目标不活跃

  • WHEN 查询 inactive target 的指标
  • THEN Store SHALL 返回 nullgetTargetById 不匹配 active=1 的条件)

Scenario: 无检查记录

  • WHEN 时间窗口内无检查记录
  • THEN Store SHALL 返回空数组

Requirement: 目标延迟百分位取数

ProbeStore SHALL 提供 getTargetDurations(targetId, from, to) 方法,返回时间窗口内所有成功检查的 duration_ms 数组。

Scenario: 获取延迟数据

  • WHEN 调用 getTargetDurations(targetId, from, to)
  • THEN 系统 SHALL 返回该目标在时间范围内所有 matched=1 且 duration_ms 不为 null 的 duration_ms 值数组

Scenario: 延迟数据排序

  • WHEN 获取延迟数据
  • THEN 返回数组 SHALL 按 duration_ms 升序排列,供后端应用层计算 P95/P99

Scenario: 无成功检查

  • WHEN 时间窗口内无 matched=1 且 duration_ms 不为 null 的记录
  • THEN 系统 SHALL 返回空数组

Requirement: 历史记录时间范围和分页查询

系统 SHALL 继续支持按时间范围筛选并分页查询历史记录。

Scenario: 按时间范围筛选历史记录

  • WHEN 查询指定 target 在 from 到 to 时间范围内的历史记录
  • THEN 系统 SHALL 返回该时间范围内的记录,按 timestamp 降序排列

Scenario: 分页查询历史记录

  • WHEN 查询指定 page 和 pageSize 的历史记录
  • THEN 系统 SHALL 返回对应页的数据和总记录数

Requirement: 目标展示摘要持久化

数据存储 SHALL 为每个 target 持久化一个领域无关的展示摘要字段 target

Scenario: HTTP target 展示摘要

  • WHEN 同步 HTTP target
  • THEN targets.target SHALL 存储该 target 的 URL

Scenario: cmd target 展示摘要

  • WHEN 同步 cmd target
  • THEN targets.target SHALL 存储由 exec 和 args 组成的命令摘要

Scenario: HTTP target config 序列化

  • WHEN 同步 HTTP target
  • THEN targets.config SHALL 存储 JSON包含 url、method、headers、body、maxBodyBytes、ignoreSSL、maxRedirects

Scenario: cmd target config 序列化

  • WHEN 同步 cmd target
  • THEN targets.config SHALL 存储 JSON包含 exec、args、cwd、env、maxOutputBytes

Requirement: 数据清理方法

ProbeStore SHALL 提供 prune(retentionMs: number) 方法,删除超过保留时长的历史检查结果并返回删除行数,同时清理已无关联检查结果的非活跃目标行。

Scenario: 清理过期数据

  • WHEN 调用 prune(604800000)7 天毫秒数)
  • THEN 系统 SHALL 删除 check_results 表中 timestamp 早于当前时间减去 604800000 毫秒的所有记录,并返回实际删除的行数

Scenario: 无过期数据

  • WHEN 调用 prune() 但所有记录都在保留期内
  • THEN 系统 SHALL 返回 0不删除任何记录

Scenario: 清理不影响保留期内数据

  • WHEN 调用 prune() 且存在保留期内和保留期外的记录
  • THEN 系统 SHALL 仅删除保留期外的记录,保留期内的记录 SHALL 不受影响

Scenario: 清理空壳非活跃目标

  • WHEN prune 执行完毕后,存在 active=0 的 target 且该 target 在 check_results 表中无任何关联记录
  • THEN 系统 SHALL 删除该空壳 target 行

Scenario: 非活跃目标仍有历史数据时不清理

  • WHEN 存在 active=0 的 target 但该 target 在 check_results 表中仍有关联记录
  • THEN 系统 SHALL NOT 删除该 target 行

Scenario: 活跃目标永不清理

  • WHEN 存在 active=1 的 target 且该 target 在 check_results 表中无关联记录
  • THEN 系统 SHALL NOT 删除该 target 行