import type { VideoProjectWorkspace } from './workspace.js'; export declare const EXECUTION_RUNS_DIRNAME = "execution-runs"; /** A heartbeat older than this means the writer is gone (killed, hung, or the machine slept). */ export declare const EXECUTION_RUN_LIVE_WINDOW_MS: number; /** How often a running `produce` touches its marker. Well inside the live window. */ export declare const EXECUTION_RUN_HEARTBEAT_MS: number; export interface ExecutionRunMarker { schemaVersion: 1; pid: number; /** ISO time the run wrote its marker, i.e. immediately before the provider submit. */ startedAt: string; /** ISO time of the last heartbeat; equals `startedAt` until the first touch. */ heartbeatAt: string; routeId: string | null; productionMode: string; /** Scene subset of a `--scene` run; `null` means the whole storyboard. */ sceneIndexes: number[] | null; /** The lane request hash of the exact payload about to be submitted. */ payloadHash: string; } export interface ExecutionRunMarkerHandle { path: string; marker: ExecutionRunMarker; /** The heartbeat write currently in flight, if any — `clear` awaits it. */ pending?: Promise; /** Set by `clear`; a heartbeat that started before it must not resurrect the file. */ cleared?: boolean; } export type ExecutionRunLiveness = 'live' | 'stale'; export interface ExecutionRunDescription { state: ExecutionRunLiveness; /** Why the verdict landed where it did — surfaced verbatim to the operator. */ reason: 'heartbeat-fresh' | 'heartbeat-expired' | 'process-gone'; elapsedSeconds: number; heartbeatAgeSeconds: number; } export type PidProbe = (pid: number) => void; /** Default probe: `kill(pid, 0)` sends no signal but throws ESRCH when the pid is gone. */ export declare function defaultPidProbe(pid: number): void; export declare function executionRunsDir(workspace: Pick): string; export declare function isExecutionRunMarker(value: unknown): value is ExecutionRunMarker; export declare function writeExecutionRunMarker(workspace: Pick, input: { routeId: string | null; productionMode: string; sceneIndexes: number[] | null; payloadHash: string; pid?: number; now?: Date; }): Promise; /** * Refresh `heartbeatAt`. Best-effort: a heartbeat that fails to write must never * fail the render it is describing — the worst case is the marker reads stale * ten minutes early, which the reader handles. (The INITIAL write in * `writeExecutionRunMarker` is deliberately NOT best-effort: without a marker * the in-flight guarantee is void, so a run that cannot write one refuses * before submitting rather than submitting blind.) * * Ordered against `clearExecutionRunMarker`: `clearInterval` cannot cancel a * touch that has already started, and the atomic write is `writeFile(tmp)` then * `rename` — a rename landing after the clear's `rm` would resurrect the marker * with a fresh heartbeat (live for ten minutes in a long-lived process such as * `render-scenes`). So a touch records itself on the handle for `clear` to * await, skips entirely once `cleared` is set, and re-removes the file if the * clear raced past it anyway. */ export declare function touchExecutionRunMarker(handle: ExecutionRunMarkerHandle, now?: Date): Promise; export declare function clearExecutionRunMarker(handle: Pick & Partial>): Promise; /** * Every marker currently on disk for the project. Tolerates a missing directory * (no run has ever been started) and skips files that are not a valid marker (a * torn write, or something else that landed in the directory) — one bad file * must not hide a real live run. */ export declare function readExecutionRunMarkers(workspace: Pick): Promise; /** Pure liveness verdict — see the module docblock for the one rule. */ export declare function describeExecutionRun(marker: ExecutionRunMarker, nowMs?: number, probePid?: PidProbe): ExecutionRunDescription; /** Convenience: every marker on disk, each paired with its liveness verdict. */ export declare function describeExecutionRuns(workspace: Pick, options?: { nowMs?: number; probePid?: PidProbe; }): Promise>; //# sourceMappingURL=execution-run-marker.d.ts.map