import type { ExpiredRunLeaseCandidatePage, RecoverExpiredRunLeasesResult, RecoveredLeaseRunPage } from "../store.js"; import { Store } from "../store.js"; import type { PoolQueryClient } from "../../generated/storage-kit/query.js"; import type { LoopStorageContract, LoopStorageMethodName } from "./contract.js"; import { type LoopMutationEnvelope, type LoopMutationLookupCaps, type LoopMutationResult, type OperationAuthorityBinding } from "../operation-contract.js"; type M = Store[K] extends (...a: infer A) => infer R ? { args: A; result: R; } : never; export interface TenantStorageContext { tenantId: string; principalId: string; requestId: string; } export declare class PostgresLoopStorage implements LoopStorageContract { readonly backend = "postgresql"; readonly supportsRemoteRunners = true; readonly tenantId: string; readonly principalId: string; readonly requestId: string; private readonly client; constructor(client: PoolQueryClient, context: TenantStorageContext, opts?: { contextAlreadyBound?: boolean; }); close(): Promise; private assertDaemonLeaseFence; private lockWorkflowRun; private loadLoop; private loadRun; private loadRunBySlot; private loadDaemonLease; private setWorkItemsForLoop; private setWorkItemsForWorkflowRun; private cascadeWorkItemsForLoopRun; private maybeArchiveGeneratedRouteWorkflow; createLoop(...args: M<"createLoop">["args"]): Promise["result"]>; getLoop(...args: M<"getLoop">["args"]): Promise["result"]>; findLoopByName(...args: M<"findLoopByName">["args"]): Promise["result"]>; requireLoop(...args: M<"requireLoop">["args"]): Promise["result"]>; requireUniqueLoop(...args: M<"requireUniqueLoop">["args"]): Promise["result"]>; listLoops(...args: M<"listLoops">["args"]): Promise["result"]>; dueLoops(...args: M<"dueLoops">["args"]): Promise["result"]>; updateLoop(...args: M<"updateLoop">["args"]): Promise["result"]>; mutateLoop(envelope: LoopMutationEnvelope, authority: OperationAuthorityBinding, opts?: { now?: Date; leaseMs?: number; }): Promise; getLoopMutationResult(authority: OperationAuthorityBinding, operationId: string, stepId: string, caps?: LoopMutationLookupCaps): Promise; advanceLoopIfCurrent(...args: M<"advanceLoopIfCurrent">["args"]): Promise["result"]>; tripCircuitBreakerIfCurrent(...args: M<"tripCircuitBreakerIfCurrent">["args"]): Promise["result"]>; expireLoopIfCurrent(...args: M<"expireLoopIfCurrent">["args"]): Promise["result"]>; renameLoop(...args: M<"renameLoop">["args"]): Promise["result"]>; archiveLoop(...args: M<"archiveLoop">["args"]): Promise["result"]>; unarchiveLoop(...args: M<"unarchiveLoop">["args"]): Promise["result"]>; deleteLoop(...args: M<"deleteLoop">["args"]): Promise["result"]>; private requireLoopIn; private requireUniqueLoopIn; private requireArchiveMutationLoopIn; countLoops(...args: M<"countLoops">["args"]): Promise["result"]>; upsertMigrationWorkflow(...args: M<"upsertMigrationWorkflow">["args"]): Promise["result"]>; upsertMigrationLoop(...args: M<"upsertMigrationLoop">["args"]): Promise["result"]>; upsertMigrationRun(...args: M<"upsertMigrationRun">["args"]): Promise["result"]>; createSkippedRun(...args: M<"createSkippedRun">["args"]): Promise["result"]>; getRun(...args: M<"getRun">["args"]): Promise["result"]>; getRunBySlot(...args: M<"getRunBySlot">["args"]): Promise["result"]>; nextRetryableRun(...args: M<"nextRetryableRun">["args"]): Promise["result"]>; /** * Remote-safe liveness proxy shared by claimRun and * recoverExpiredRunLeasesDetailed. The Postgres backend cannot inspect the * original runner's process (it may be a different machine), so a run whose * LEASE lapsed within the expired-run grace window — MAX x GRACE (10 min), * the same post-expiry budget the sqlite path allows via its live-process * deferral ceiling — is treated as possibly-still-executing: steal and * abandon are both deferred until the window passes, bounding the * duplicate-execution window to the original runner's 3-heartbeat abort * (~1.5 lease periods) instead of stealing the instant the lease lapses. * * The anchor is lease expiry, deliberately not process start: a run executing * longer than the grace window at the moment its lease lapses (a transient * heartbeat outage, a machine suspend, a long-running loop) would otherwise * receive zero post-expiry protection and have its slot stolen mid-execution * while the original runner keeps running. Anchoring on the recorded process * start consumes the whole budget up front and defeats the fix for exactly * those runs. Time-bounded by construction, so a genuinely dead runner is * still reclaimed once the window passes — the "expired lease is reclaimable" * fence stays intact without a defer counter column. */ private static expiredLeaseWithinGrace; /** * Claim a specific loop slot for a runner. * * Divergence from the sqlite Store (documented, not accidental): the sqlite * path also consults LOCAL process liveness (`isRecordedProcessAlive`, * `hasLiveWorkflowStepProcesses`) before stealing an expired-lease run, * because the daemon and the run's child process share a host. On the * Postgres/remote backend the claiming runner may be a different machine than * the one that holds the (possibly still-live) process, so local pid checks * are meaningless. Ownership here is governed by lease expiry plus a bounded * grace window: an expired lease whose lapse is older than the expired-run * grace window is reclaimable, and an expired lease whose lapse is still * inside that window is deferred (see * {@link PostgresLoopStorage.expiredLeaseWithinGrace}) so a runner hit by a * transient heartbeat outage cannot have its slot stolen mid-execution. The * lease/heartbeat contract (plus `FOR UPDATE` row locks) is the remote * correctness boundary. */ claimRun(...args: M<"claimRun">["args"]): Promise["result"]>; finalizeRun(...args: M<"finalizeRun">["args"]): Promise["result"]>; heartbeatRunLease(...args: M<"heartbeatRunLease">["args"]): Promise["result"]>; recordRunProcess(...args: M<"recordRunProcess">["args"]): Promise["result"]>; listRuns(...args: M<"listRuns">["args"]): Promise["result"]>; listRecoveredLeaseRunsPage(...args: M<"listRecoveredLeaseRunsPage">["args"]): Promise; writeRunReceipt(...args: M<"writeRunReceipt">["args"]): Promise["result"]>; getRunReceipt(...args: M<"getRunReceipt">["args"]): Promise["result"]>; listRunReceipts(...args: M<"listRunReceipts">["args"]): Promise["result"]>; countRuns(...args: M<"countRuns">["args"]): Promise["result"]>; recoverExpiredRunLeases(...args: M<"recoverExpiredRunLeases">["args"]): Promise["result"]>; listExpiredRunLeaseCandidates(expiredBefore?: Date, opts?: { limit?: number; }): Promise; /** * Recover expired run leases. Divergence from sqlite (documented): the remote * backend cannot inspect local process liveness, so an expired lease is * abandoned unless its lapse is still inside the expired-run grace window * (`expiredLeaseWithinGrace`) — such runs are deferred, not abandoned, because * the original runner may still be executing on another machine. Abandoning * within the window would let the next claim pass mint a new attempt while the * original runner is live. The window is time-bounded (MAX x GRACE = 10 min), * so a genuinely dead runner is still abandoned once it passes. */ recoverExpiredRunLeasesDetailed(...args: M<"recoverExpiredRunLeasesDetailed">["args"]): Promise; pruneHistory(...args: M<"pruneHistory">["args"]): Promise["result"]>; acquireDaemonLease(...args: M<"acquireDaemonLease">["args"]): Promise["result"]>; heartbeatDaemonLease(...args: M<"heartbeatDaemonLease">["args"]): Promise["result"]>; releaseDaemonLease(...args: M<"releaseDaemonLease">["args"]): Promise["result"]>; getDaemonLease(...args: M<"getDaemonLease">["args"]): Promise["result"]>; private loadWorkflow; getWorkflow(...args: M<"getWorkflow">["args"]): Promise["result"]>; listWorkflows(...args: M<"listWorkflows">["args"]): Promise["result"]>; countWorkflows(...args: M<"countWorkflows">["args"]): Promise["result"]>; getWorkflowInvocation(...args: M<"getWorkflowInvocation">["args"]): Promise["result"]>; listWorkflowInvocations(...args: M<"listWorkflowInvocations">["args"]): Promise["result"]>; getWorkflowWorkItem(...args: M<"getWorkflowWorkItem">["args"]): Promise["result"]>; listWorkflowWorkItems(...args: M<"listWorkflowWorkItems">["args"]): Promise["result"]>; countActiveWorkflowWorkItems(...args: M<"countActiveWorkflowWorkItems">["args"]): Promise["result"]>; getWorkflowRun(...args: M<"getWorkflowRun">["args"]): Promise["result"]>; listWorkflowRuns(...args: M<"listWorkflowRuns">["args"]): Promise["result"]>; listWorkflowStepRuns(...args: M<"listWorkflowStepRuns">["args"]): Promise["result"]>; getWorkflowStepRun(...args: M<"getWorkflowStepRun">["args"]): Promise["result"]>; listWorkflowEvents(...args: M<"listWorkflowEvents">["args"]): Promise["result"]>; getGoal(...args: M<"getGoal">["args"]): Promise["result"]>; listGoals(...args: M<"listGoals">["args"]): Promise["result"]>; listGoalPlanNodes(...args: M<"listGoalPlanNodes">["args"]): Promise["result"]>; listGoalRuns(...args: M<"listGoalRuns">["args"]): Promise["result"]>; createWorkflow(...args: M<"createWorkflow">["args"]): Promise["result"]>; archiveWorkflow(...args: M<"archiveWorkflow">["args"]): Promise["result"]>; createWorkflowInvocation(...args: M<"createWorkflowInvocation">["args"]): Promise["result"]>; upsertWorkflowWorkItem(...args: M<"upsertWorkflowWorkItem">["args"]): Promise["result"]>; admitWorkflowWorkItem(...args: M<"admitWorkflowWorkItem">["args"]): Promise["result"]>; createGoal(...args: M<"createGoal">["args"]): Promise["result"]>; requireGoal(...args: M<"requireGoal">["args"]): Promise["result"]>; findGoalByContext(...args: M<"findGoalByContext">["args"]): Promise["result"]>; private mapGoalPlanNode; createGoalPlanNodes(...args: M<"createGoalPlanNodes">["args"]): Promise["result"]>; updateGoalStatus(...args: M<"updateGoalStatus">["args"]): Promise["result"]>; updateGoalPlanNode(...args: M<"updateGoalPlanNode">["args"]): Promise["result"]>; recordGoalEvent(...args: M<"recordGoalEvent">["args"]): Promise["result"]>; requireWorkflow(...args: M<"requireWorkflow">["args"]): Promise["result"]>; createWorkflowRun(...args: M<"createWorkflowRun">["args"]): Promise["result"]>; requireWorkflowRun(...args: M<"requireWorkflowRun">["args"]): Promise["result"]>; isWorkflowRunTerminal(...args: M<"isWorkflowRunTerminal">["args"]): Promise["result"]>; startWorkflowStepRun(...args: M<"startWorkflowStepRun">["args"]): Promise["result"]>; markWorkflowStepPid(...args: M<"markWorkflowStepPid">["args"]): Promise["result"]>; recordWorkflowStepProgress(...args: M<"recordWorkflowStepProgress">["args"]): Promise["result"]>; recoverWorkflowRun(...args: M<"recoverWorkflowRun">["args"]): Promise["result"]>; finalizeWorkflowStepRun(...args: M<"finalizeWorkflowStepRun">["args"]): Promise["result"]>; skipWorkflowStepRun(...args: M<"skipWorkflowStepRun">["args"]): Promise["result"]>; private setWorkflowWorkItemsForWorkflowRun; private demoteNonProductiveWorkItems; finalizeWorkflowRun(...args: M<"finalizeWorkflowRun">["args"]): Promise["result"]>; private appendWorkflowEventWithClient; appendWorkflowEvent(...args: M<"appendWorkflowEvent">["args"]): Promise["result"]>; } export declare function createPostgresLoopStorage(client: PoolQueryClient, context: TenantStorageContext, opts?: { contextAlreadyBound?: boolean; }): PostgresLoopStorage; export {};