- 合并 20+ 细粒度 spec 为粗粒度主题规范:dashboard、data-store、probe-engine、probe-api、probe-config 等 - 删除完全冗余规范:data-retention(被 probe-engine+data-store 覆盖)、backend-code-quality(DEVELOPMENT.md 已记录) - 补充 http-checker 规范至完整标准(配置+执行+expect+校验+observation),匹配代码 440 行实现 - 清理 tcp/udp/llm checker 规范中已废弃 defaults 配置段的残留 Scenario - 清理 checker-cohesion-structure 中的实现路径引用(src/server/...) - 统一所有 spec 格式(## Purpose 开头,去除 # Capability/Title 形式) - 更新 prompt-spec-review.md 审查提示文档
95 lines
5.5 KiB
Markdown
95 lines
5.5 KiB
Markdown
## Purpose
|
||
|
||
定义拨测调度引擎的行为:按 interval 分组定时、组内并发拨测、通用 expect 校验、结果持久化和定期数据清理。各 checker 类型的执行语义和专属 expect 校验规则定义在各自 checker 的规范中。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: 按 interval 分组调度
|
||
系统 SHALL 将拨测目标按 interval 值分组,每组使用独立的定时器进行调度。
|
||
|
||
#### Scenario: 相同 interval 的目标共享定时器
|
||
- **WHEN** 多个 target 配置了相同的 interval(如 30s)
|
||
- **THEN** 系统 SHALL 使用同一个 `setInterval` 定时器,每次 tick 并发拨测所有该组目标
|
||
|
||
#### Scenario: 不同 interval 的目标各自调度
|
||
- **WHEN** target A 配置 15s interval,target B 配置 30s interval
|
||
- **THEN** 系统 SHALL 创建两个独立定时器,分别按各自频率调度
|
||
|
||
### Requirement: 组内并发拨测
|
||
系统 SHALL 在每次调度 tick 时并发执行同组内目标的检查,但实际同时运行的检查数 MUST 受全局 `probes.execution.maxConcurrentChecks` 限制。当某个目标的 checker 执行 rejected(非正常 CheckResult 返回,而是 Promise reject)时,系统 SHALL 将该异常记录为 `matched: false` 的 check_result,而非仅 console.warn。
|
||
|
||
#### Scenario: 同组目标并发执行
|
||
- **WHEN** 调度器触发一次 tick,该组有 3 个目标,且全局并发余量至少为 3
|
||
- **THEN** 系统 SHALL 同时执行 3 个 checker,而非顺序执行
|
||
|
||
#### Scenario: 单个目标失败不影响同组其他目标
|
||
- **WHEN** 同组中某个目标的检查请求超时或失败(checker 正常返回 CheckResult)
|
||
- **THEN** 其他目标的检查 SHALL 正常完成并记录结果
|
||
|
||
#### Scenario: 同组中某个目标的 checker 执行 rejected
|
||
- **WHEN** 同组中某个目标的 checker 执行抛出未捕获异常(Promise rejected)
|
||
- **THEN** 系统 SHALL 为该目标写入一条 `matched: false` 的 check_result,failure 为 `{ kind: "error", phase: "internal", path: "engine", message: <rejected reason> }`,其他目标的检查 SHALL 不受影响
|
||
|
||
#### Scenario: rejected 结果通过索引关联 targetName
|
||
- **WHEN** checker 执行 rejected
|
||
- **THEN** 系统 SHALL 通过 Promise.allSettled 的索引关联回 target 数组,获取对应的 targetName 用于写入 check_result
|
||
|
||
#### Scenario: 全局并发限制生效
|
||
- **WHEN** 调度器同时触发 10 个目标且 probes.execution.maxConcurrentChecks 为 3
|
||
- **THEN** 系统 MUST 同时最多运行 3 个检查,其余检查等待并发槽位释放
|
||
|
||
### Requirement: 请求超时控制
|
||
系统 SHALL 对每次 checker 执行实施超时控制,超时时间使用目标配置的 timeout 值。引擎 SHALL 通过 AbortController 向 checker 注入超时 signal。
|
||
|
||
#### Scenario: checker 在超时前完成
|
||
- **WHEN** checker 在超时前完成执行
|
||
- **THEN** 系统 SHALL 正常记录执行结果并进入 expect 校验
|
||
|
||
#### Scenario: checker 执行超时
|
||
- **WHEN** checker 在 timeout 时间内未完成执行
|
||
- **THEN** 系统 SHALL 中止该检查,记录为失败并标注超时错误
|
||
|
||
### Requirement: expect 校验
|
||
系统 SHALL 在 checker 执行完成后根据目标类型的 Resolved expect 执行计划校验观测结果,校验结果和首个失败原因记入 check result。各 checker 类型 SHALL 定义各自的 expect 执行顺序、默认状态语义和快速失败策略。`durationMs` SHALL 表示完整 checker 执行耗时。
|
||
|
||
#### Scenario: 多条 expect 规则
|
||
- **WHEN** 目标同时配置状态、duration、元数据和内容 expectations
|
||
- **THEN** 系统 SHALL 所有 expectations 全部通过时 matched 为 true,任一不通过则为 false 并记录首个失败原因
|
||
|
||
### Requirement: 拨测结果记录
|
||
系统 SHALL 在每次 checker 完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、matched、duration_ms、observation、failure 字段。detail SHALL 为 API 层派生字段,不写入存储层;系统 SHALL NOT 写入 status_detail 字段。
|
||
|
||
#### Scenario: 成功检查结果记录
|
||
- **WHEN** checker 成功执行且 expect 全部匹配
|
||
- **THEN** 系统 SHALL 记录 matched=true、duration_ms、observation,failure 为 null
|
||
|
||
#### Scenario: 执行失败结果记录
|
||
- **WHEN** checker 执行失败(网络错误、超时、命令启动失败、输出超限等)
|
||
- **THEN** 系统 SHALL 记录 matched=false、failure.kind="error" 和具体错误信息,并在可收集领域观测数据时记录 observation
|
||
|
||
#### Scenario: expect 不匹配结果记录
|
||
- **WHEN** checker 执行成功但 expect 不匹配
|
||
- **THEN** 系统 SHALL 记录 matched=false、observation、failure.kind="mismatch" 和具体不匹配信息
|
||
|
||
### Requirement: runner 选择
|
||
系统 SHALL 根据 target.type 通过 CheckerRegistry 选择对应 checker 执行检查。
|
||
|
||
#### Scenario: 根据 type 选择 checker
|
||
- **WHEN** target.type 为已注册的 checker 类型
|
||
- **THEN** 系统 SHALL 通过 `checkerRegistry.get(type)` 获取对应 checker 并执行
|
||
|
||
### Requirement: 定期数据清理
|
||
ProbeEngine SHALL 在启动时注册数据清理定时器,定期调用 ProbeStore.prune() 清理过期数据和空壳非活跃目标。
|
||
|
||
#### Scenario: 引擎启动注册清理
|
||
- **WHEN** ProbeEngine.start() 被调用且 retentionMs > 0
|
||
- **THEN** 系统 SHALL 立即执行一次 prune,然后每隔 1 小时再次执行
|
||
|
||
#### Scenario: 引擎停止清除定时器
|
||
- **WHEN** ProbeEngine.stop() 被调用
|
||
- **THEN** 系统 SHALL 清除清理定时器,不再执行后续清理
|
||
|
||
#### Scenario: retentionMs 为 0 不注册清理
|
||
- **WHEN** ProbeEngine 构造时 retentionMs 为 0
|
||
- **THEN** 系统 SHALL 不注册清理定时器
|