1
0

feat: 结构化 observation 替代 statusDetail,API 层动态构造 detail

- CheckResult: statusDetail -> observation (持久化) + detail (API 动态派生)
- 存储: status_detail 列 -> observation TEXT (JSON)
- CheckerDefinition: 新增 buildDetail(observation) 方法
- 各 checker 返回结构化 observation,API 层通过 registry 调用 buildDetail
- HTTP: bodyPreview 在 status/header 失败时也提前采集
- UDP: observation 包含 durationMs,未响应归为 error failure
- CMD: 超时/输出超限时保留已收集 observation
- TCP: connectTimeMs 仅含连接建立耗时,不含 banner 等待
- 新增 buildDetail 单测和 mapCheckResult 覆盖测试
- 同步 openspec 主规范,归档 checker-observation 变更
This commit is contained in:
2026-05-19 22:49:00 +08:00
parent 22c06820fa
commit 375dd3492b
64 changed files with 915 additions and 965 deletions

View File

@@ -60,7 +60,7 @@ describe("UdpChecker execute", () => {
cleanup();
expect(result.matched).toBe(true);
expect(result.failure).toBeNull();
expect(result.statusDetail).toContain("responded");
expect(result.observation).toMatchObject({ responded: true });
expect(result.durationMs).toBeGreaterThanOrEqual(0);
} finally {
server.close();
@@ -103,7 +103,10 @@ describe("UdpChecker execute", () => {
const result = await checker.execute(target, { signal: controller.signal });
clearTimeout(timer);
expect(result.matched).toBe(false);
expect(result.failure).not.toBeNull();
expect(result.failure).toMatchObject({ kind: "error", phase: "response" });
expect(result.observation?.["durationMs"]).toBeGreaterThanOrEqual(0);
expect(result.observation?.["error"]).toBeTruthy();
expect(result.observation).toMatchObject({ responded: false });
} finally {
server.close();
}
@@ -131,7 +134,8 @@ describe("UdpChecker execute", () => {
const result = await checker.execute(target, { signal: controller.signal });
clearTimeout(timer);
expect(result.matched).toBe(true);
expect(result.statusDetail).toContain("no response");
expect(result.observation?.["durationMs"]).toBeGreaterThanOrEqual(0);
expect(result.observation).toMatchObject({ error: null, responded: false });
} finally {
server.close();
}
@@ -223,7 +227,7 @@ describe("UdpChecker execute", () => {
const result = await checker.execute(target, { signal });
cleanup();
expect(result.matched).toBe(true);
expect(result.statusDetail).toContain("5 bytes");
expect(result.observation).toMatchObject({ responded: true, responseSize: 5 });
} finally {
server.close();
}