import type { WebGuiRegistration } from '../web-gui/registry.js'; /** gui 侧最小结构面(WebGuiServer 结构满足;测试注入 fake)。 */ export interface RestartGuiHandle { /** 实际监听端口(start() 定格的实际值,交接的"旧端口"真源)。 */ readonly port: number; /** 登记文件同源 startedAt——removeRegistrationVerified 的期望值必须与登记文件 * 逐字一致(WebGuiServer.start() 写登记与暴露本值用的是同一 string 引用), * 另 new Date() 会因时间戳不同导致 verified 永远 false。 */ readonly registrationStartedAt: string; /** §3.7 步骤 1:只关 HTTP listener 释放端口(不清登记/不置 inactive/进程不退)。 */ closeListener(): Promise; /** closeListener 的逆操作:同端口重新监听(不变式 1 的回滚侧兑现)。 */ relisten(): Promise; } /** 单步审计注入面:ok=false 表示该步失败;details 的 step/oldPid/port 由序列统一垫。 */ export type ControlledRestartAudit = (ok: boolean, details: Record) => void | Promise; /** 受控交接依赖(全注入,main.ts 接真实实现,测试注入 fake——不真跑 runDaemon: * main.ts 有不 unref 的保活 setInterval,真跑会拖死测试进程)。 */ export interface ControlledRestartDeps { gui: RestartGuiHandle; /** 有序 close 链的 server 段(GodotServer 结构满足)。 */ server: { close(): Promise; }; /** spawn 新 daemon(注入式;flags = ['--port', N, '--respawn-of', 旧pid],不含入口)。 */ spawnDaemon: (flags: string[]) => { pid: number; }; /** 进程树强杀(core/process-state killPidTree 同源)。 */ killTree: (pid: number) => void; /** registry 读取注入(轮询交接成功判定/回滚确认登记消失)。 */ listRegistrations: () => Promise; /** Task 5:校验内容后删登记(PID 复用防误删)。 */ removeRegistrationVerified: (pid: number, expectedStartedAt: string) => Promise; /** 轮询 sleep 注入(测试零等待)。 */ sleep: (ms: number) => Promise; /** 进程退出注入(测试 fake 防杀测试进程;生产传 process.exit)。 */ exit: (code: number) => void; /** 旧进程 pid(缺省 process.pid;测试注入定值)。 */ oldPid?: number; /** 就绪轮询预算注入(缺省 READY_TIMEOUT_MS)。 */ readyTimeoutMs?: number; /** 回滚登记消失预算注入(缺省 GONE_TIMEOUT_MS)。 */ goneTimeoutMs?: number; /** 轮询间隔注入(缺省 POLL_INTERVAL_MS)。 */ pollIntervalMs?: number; /** exit 前收尾钩子(main.ts 注入 logger flush;server.close 之后调用)。 */ beforeExit?: () => Promise; /** 审计注入(缺省直调 appendMachineAuditLine,形态对齐 defaultAuditKill)。 */ audit?: ControlledRestartAudit; } /** * default spawn(§3.7 步骤 2):detached 新 daemon + stdout/stderr 落日志文件 * (spec §3.9:防 Windows detached 输出悬空)+ unref(旧进程退出不拖新实例)。 * flags 已含 --port=旧端口(不变式 1)与 --respawn-of=旧 pid(单例检测豁免,§3.4)。 */ export declare function spawnDaemonDetached(flags: string[]): { pid: number; }; /** * §3.7 受控交接序列(daemon 收到 restart 指令后自执行;失败以 throw 上报, * 回滚完成后旧进程继续服务): * 1. 先关 HTTP listener(不 exit,进程仍在),端口释放; * 2. detached-spawn 新 daemon,显式 --port=旧端口 + --respawn-of=旧pid; * 3. 交接成功判定:registry 出现新登记且 port===旧端口 且 kind==='daemon' * (按登记文件比对,不用无鉴权 /api/health)→ removeRegistrationVerified * 删自身登记 → 有序 close → exit(0);端口不符立即回滚(不等满超时); * 4. 失败回滚:先 killTree 新实例并轮询确认其登记消失,再 relisten 原端口 * (不留双活)→ throw 失败详情。 */ export declare function controlledRestart(deps: ControlledRestartDeps): Promise;