/** A lane is keyed `route:account` — limits are per ACCOUNT, not per route. */ export type LaneId = string; export type TicketState = 'queued' | 'held' | 'done'; export interface LaneTicket { id: string; lane: LaneId; project: string; scene: number | null; promptHash: string | null; state: TicketState; createdAt: number; grantedAt: number | null; expiresAt: number | null; holder: string | null; /** The driver's claim (remote coordinator only): two sessions on one machine * share `holder`; this is what tells them apart. Absent on the local queue. */ claimId?: string; /** The render log (remote coordinator): what the driver reported on release, * `expired` from the coordinator's reap, null before the log existed. */ outcome?: string | null; providerJobId?: string | null; note?: string | null; } /** * A lane-wide pause every computer honours. Today one reason: the provider put * up a human check (Cloudflare Turnstile) in front of a driver. That is anti-bot * rate limiting, and it is triggered by CADENCE across the whole account, so the * right response is for every other driver on the lane to stop submitting for a * while — not for each of them to discover the wall and burn a draw on it. The * challenged driver's own fingerprint stays challenged regardless (a cooldown * cannot clear it); the cooldown exists to protect the OTHERS. */ export type LaneCooldownReason = 'provider-human-check'; export interface LaneCooldown { reasonCode: LaneCooldownReason; /** Epoch seconds when submissions may resume. */ until: number; remainingSeconds: number; /** Who reported it: the machine id on the shared coordinator, the pid locally. */ machineId: string | null; ticketId: string; reportedAt: number; } export declare const MIN_COOLDOWN_SECONDS = 30; export declare const MAX_COOLDOWN_SECONDS = 7200; export interface AcquireResult { status: 'granted' | 'queued'; ticketId: string; /** 1-based place in the fair ordering. 0 when granted. */ position: number; /** Rough wait from observed job durations on this lane; null when unknown. */ etaSeconds: number | null; lane: LaneId; /** True when an identical in-flight request already existed (dedupe hit). */ deduped: boolean; coordinator?: 'local-sqlite' | 'cloudflare-durable-object'; /** Set on a queued result when the lane is paused; `etaSeconds` already * includes the remaining pause. */ cooldown?: LaneCooldown | null; } export interface LaneStatus { lane: LaneId; limit: number | null; held: LaneTicket[]; queued: Array; medianJobSeconds: number | null; /** Bounded terminal rows retained for ETA/history diagnostics. */ terminalRetained: number; coordinator?: 'local-sqlite' | 'cloudflare-durable-object'; /** The active lane-wide pause, null when none. */ cooldown?: LaneCooldown | null; /** * The evidence trail, newest first — shared coordinator only (the local queue * has none). Absent unless the caller asked for it. */ recentReceipts?: LaneStatusReceipt[]; /** * Rows the coordinator sent that this client could not read — an unrecognised * phase, or a row missing a field this client needs. Present whenever * `recentReceipts` is, including as `0`, so its absence is never ambiguous with * "asked, and none were dropped". Read a non-zero count as a CLI older than the * writer, NEVER as missing evidence: without it a dropped row would make an * evidence surface quietly under-report, and the reader would see "no receipt * for that step" with no way to learn the receipt exists and was not parsed. * One number and one rule on purpose — two counters would invite a consumer to * treat one cause as benign, re-creating the silent drop in miniature. */ unreadableReceipts?: number; } /** One receipt as the coordinator stored it. See `docs/SHARED_QUEUE.md`. */ export interface LaneStatusReceipt { receiptId: string; ticketId: string; phase: 'lease-acquired' | 'references-applied' | 'provider-submitted' | 'provider-terminal'; contentHash: string; /** * The coordinator recomputed the hash and it matched. `null` has two causes an * operator cannot tell apart from here: a coordinator too old to check, and a * receipt stored before THIS coordinator gained the check — the verified column * arrives by migration, so rows written earlier hold no verdict. */ contentHashVerified: boolean | null; machineId: string; /** Fractional epoch SECONDS, not milliseconds — `new Date(createdAt * 1000)`. */ createdAt: number; payload: Record; } /** Injectable so tests can drive the clock and target a temp database. */ export interface LaneQueueOptions { dbPath?: string; now?: () => number; /** Escape hatch for tests; defaults to the sqlite3 CLI. */ runSql?: (sql: string, dbPath: string) => string; /** Retained terminal rows per lane; injectable only for compact regression tests. */ maxTerminalPerLane?: number; } export declare function defaultLaneDbPath(env?: NodeJS.ProcessEnv): string; export declare function promptHashOf(text: string): string; export declare class LaneQueue { readonly coordinator: "local-sqlite"; private readonly dbPath; private readonly now; private readonly exec; private readonly maxTerminalPerLane; private ready; constructor(opts?: LaneQueueOptions); private init; /** Expire leases whose holder died without releasing. Never a PID check: * `kill -0 0` signals the process group and returns success, which hung a * runner for 20 minutes on 2026-08-11. A TTL cannot lie that way. */ private reapSql; /** SQL truthy when the lane is paused at `now`; used to gate promotion. */ private activeCooldownSql; /** Keep queue history useful but bounded under feature-scale throughput. */ private pruneSql; /** Fill every free slot while rotating projects before taking their next task. */ private promoteSql; acquire(params: { lane: LaneId; project: string; scene?: number | null; promptHash?: string | null; limit: number | null; ttlSec?: number; holder?: string; }): AcquireResult; /** * Pause the lane for every caller. Only an ACTIVE lease may report one: a * queued or done ticket never spoke to the provider, so it has no wall to * report, and refusing it keeps a stray retry from pausing a healthy lane. * A longer pause replaces a shorter one; a shorter report never trims an * active pause. */ cooldown(ticketId: string, lane: LaneId, reasonCode: LaneCooldownReason, retryAfterSeconds: number): LaneCooldown; private readCooldown; /** Refresh a lease. A holder that stops heartbeating loses the slot at TTL. */ heartbeat(ticketId: string, ttlSec?: number): boolean; /** Release the slot and immediately promote the fair next ticket. */ release(ticketId: string, lane?: LaneId, limit?: number | null): boolean; /** * Backward-compatible escape hatch for old callers that did not persist a * ticket. It is intentionally safe only when exactly one lease matches; * ambiguity is refused so parallel attempts can never release one another. * New execution paths must call `release(ticketId, ...)` directly. */ releaseByProject(lane: LaneId, project: string, limit?: number | null): number; status(lane: LaneId, limit?: number | null): LaneStatus; /** Median of completed job durations — the basis for a real ETA. */ medianJobSeconds(lane: LaneId): number | null; private etaFor; } //# sourceMappingURL=lane-queue.d.ts.map