fix(system): retired heartbeat rows no longer alarm on status page

- GET /system/health splits processes into current instances (hosts with
  a beat in the last 10 min, newest per host+kind, not stopped) and
  retiredProcesses[]; 45s stale and level only consider current ones
- heartbeat writer marks stopped_at on graceful exit (migration 0043),
  upsert clears it; pure api role now stops its writer on SIGTERM
- scheduled job system.heartbeat_cleanup (maintain, 1h) deletes rows
  without a beat for 24h and samples older than 7 days
- status page: per-role alarms ("io worker 45 秒没有心跳"), collapsible
  retired instances; glossary/runbook updated
- bump all members to 0.2.2, CHANGELOG [0.2.2]

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 23:31:21 +08:00
1 parent fa261eb6cd
commit b4c9b1ead2
32 files changed
+747 -91

No files matched your search

+29 -1
View File
@@ -240,8 +240,12 @@ export const BrowserProviderView = z.object({
baseUrl: z.string().nullable(),
/** 访问凭据的 secret 引用(明文永不出 API) */
secretRef: Id.nullable(),
/** env = 由 BROWSER_PROVIDER_URL / BROWSER_ACCESS_KEY 登记(地址与凭据只能改部署配置);null = 设置页登记 */
managedBy: z.enum(["env"]).nullable(),
/** 是否配置了访问凭据(secret 或环境变量) */
hasAccessKey: z.boolean(),
enabled: z.boolean(),
/** 最近一次声明/探测到的能力;verified 只由人工核验写入 config.verified */
/** 最近一次声明/探测到的能力;verified 只由人工核验写入(config.verified 或适配器内置的已核验版本表) */
capabilities: BrowserCapabilities.nullable(),
verifiedAt: Timestamp.nullable(),
healthStatus: InstanceHealth,
@@ -251,6 +255,30 @@ export const BrowserProviderView = z.object({
});
export type BrowserProviderView = z.infer<typeof BrowserProviderView>;
const HttpUrl = z
.string()
.url()
.refine((u) => /^https?:\/\//i.test(u), "只支持 http(s) 地址");
/** 设置页登记浏览器服务(camofox);accessKey 明文只在请求里出现一次,落库为 secret */
export const BrowserProviderCreate = z.object({
name: z.string().min(1).max(64),
baseUrl: HttpUrl,
accessKey: z.string().min(1).max(512).optional(),
enabled: z.boolean().optional(),
});
export type BrowserProviderCreate = z.infer<typeof BrowserProviderCreate>;
/** 改地址 / 凭据 / 启用;accessKey = null 清除凭据 */
export const BrowserProviderUpdate = z.object({
baseUrl: HttpUrl.optional(),
accessKey: z.string().min(1).max(512).nullable().optional(),
enabled: z.boolean().optional(),
});
export type BrowserProviderUpdate = z.infer<typeof BrowserProviderUpdate>;
export const BrowserProviderParams = z.object({ id: Id });
export const BrowserProvidersQuery = z.object({
/** true = 现场探测 /health 与 /openapi.json 并写回 */
probe: QueryBool,