import { RECEIPT_STATUS_CODE } from './receipt-status'; export function workReceiptClaimableStatusCodes(input: { forceRefresh?: boolean; forceFailedRefresh?: boolean; reclaimRunning?: boolean; }): number[] { if (input.forceRefresh === true) { return [ RECEIPT_STATUS_CODE.queued, RECEIPT_STATUS_CODE.failed, ...(input.reclaimRunning === true ? [RECEIPT_STATUS_CODE.running] : []), RECEIPT_STATUS_CODE.completed, RECEIPT_STATUS_CODE.skipped, ]; } if (input.reclaimRunning === true) { return [ RECEIPT_STATUS_CODE.queued, RECEIPT_STATUS_CODE.running, RECEIPT_STATUS_CODE.failed, ]; } return [RECEIPT_STATUS_CODE.queued, RECEIPT_STATUS_CODE.failed]; } export function workReceiptClaimConflictPredicateSql(input: { receiptTable: string; claimableStatusesSql: string; claimantRunIdSql: string; claimantRunAttemptSql?: string; reclaimRunningSql?: string; forceRefreshSql: string; forceFailedRefreshSql?: string; }): string { const table = input.receiptTable; const claimantRunAttemptSql = input.claimantRunAttemptSql ?? '0'; const reclaimRunningSql = input.reclaimRunningSql ?? 'false'; const forceFailedRefreshSql = input.forceFailedRefreshSql ?? 'false'; return `${table}.status = ANY(${input.claimableStatusesSql}::smallint[]) AND ( ( ${input.forceRefreshSql}::boolean AND ( ${table}.status NOT IN ( ${RECEIPT_STATUS_CODE.completed}::smallint, ${RECEIPT_STATUS_CODE.skipped}::smallint ) OR ${table}.run_id IS DISTINCT FROM ${input.claimantRunIdSql} ) ) OR ( ${forceFailedRefreshSql}::boolean AND ${table}.status = ${RECEIPT_STATUS_CODE.failed}::smallint ) OR ${table}.status <> ${RECEIPT_STATUS_CODE.failed}::smallint OR ( ${table}.status = ${RECEIPT_STATUS_CODE.failed}::smallint AND ${table}.run_id IS DISTINCT FROM ${input.claimantRunIdSql} ) ) AND ( ${table}.status = ${RECEIPT_STATUS_CODE.failed}::smallint OR ( ${table}.status = ${RECEIPT_STATUS_CODE.queued}::smallint AND ${table}.lease_id IS NULL AND ${table}.lease_expires_at IS NULL ) OR ( ${reclaimRunningSql}::boolean AND ${table}.status = ${RECEIPT_STATUS_CODE.running}::smallint AND ${table}.lease_id IS NULL AND ${table}.lease_expires_at IS NULL ) OR ${table}.lease_expires_at <= now() OR ( ${input.forceRefreshSql}::boolean AND ${table}.status IN ( ${RECEIPT_STATUS_CODE.completed}::smallint, ${RECEIPT_STATUS_CODE.skipped}::smallint ) AND ${table}.run_id IS DISTINCT FROM ${input.claimantRunIdSql} AND ( ( ${table}.lease_id IS NULL AND ${table}.lease_expires_at IS NULL ) OR ${table}.lease_expires_at <= now() OR ( ${table}.lease_expires_at IS NOT NULL AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.claimantRunIdSql} AND COALESCE(${table}.lease_owner_attempt, 0) < ${claimantRunAttemptSql}::integer ) ) ) OR ( ( ${table}.status = ${RECEIPT_STATUS_CODE.queued}::smallint OR ${reclaimRunningSql}::boolean OR ${input.forceRefreshSql}::boolean ) /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */ AND ${table}.lease_id IS NOT NULL AND ${table}.lease_expires_at IS NOT NULL AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.claimantRunIdSql} AND COALESCE(${table}.lease_owner_attempt, 0) < ${claimantRunAttemptSql}::integer ) )`; } export function workReceiptRunningOwnerPredicateSql(input: { receiptTable: string; ownerRunIdSql: string; ownerRunAttemptSql?: string; leaseIdSql: string; }): string { const table = input.receiptTable; const ownerRunAttemptSql = input.ownerRunAttemptSql ?? '0'; return `${table}.status = ${RECEIPT_STATUS_CODE.running}::smallint /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */ AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.ownerRunIdSql} AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer AND ( ${table}.lease_id IS NULL OR ( ${input.leaseIdSql} IS NOT NULL AND ${table}.lease_id = ${input.leaseIdSql} ) )`; } export function workReceiptActiveOwnerPredicateSql(input: { receiptTable: string; ownerRunIdSql: string; ownerRunAttemptSql?: string; leaseIdSql: string; }): string { const table = input.receiptTable; const ownerRunAttemptSql = input.ownerRunAttemptSql ?? '0'; return `${table}.status IN ( ${RECEIPT_STATUS_CODE.queued}::smallint, ${RECEIPT_STATUS_CODE.running}::smallint ) /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */ AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.ownerRunIdSql} AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer AND ( ${table}.lease_id IS NULL OR ( ${input.leaseIdSql} IS NOT NULL AND ${table}.lease_id = ${input.leaseIdSql} ) )`; } /** * Fence for a controlled work-receipt lease RELEASE on run-fatal teardown. * Matches running receipts still leased to the releasing run+attempt, regardless * of the specific lease id (teardown does not thread per-receipt lease ids). It * never matches a receipt owned by a different run+attempt, so releasing back to * `pending` can never steal a live competitor's claim. */ export function workReceiptReleaseOwnerPredicateSql(input: { receiptTable: string; ownerRunIdSql: string; ownerRunAttemptSql?: string; }): string { const table = input.receiptTable; const ownerRunAttemptSql = input.ownerRunAttemptSql ?? '0'; return `${table}.status IN ( ${RECEIPT_STATUS_CODE.queued}::smallint, ${RECEIPT_STATUS_CODE.running}::smallint ) /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */ AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.ownerRunIdSql} AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer AND ${table}.lease_id IS NOT NULL AND ${table}.lease_expires_at IS NOT NULL`; } export function workReceiptHeartbeatPredicateSql(input: { receiptTable: string; ownerRunIdSql: string; ownerRunAttemptSql?: string; leaseIdSql: string; }): string { const table = input.receiptTable; const ownerRunAttemptSql = input.ownerRunAttemptSql ?? '0'; return `${table}.status IN ( ${RECEIPT_STATUS_CODE.queued}::smallint, ${RECEIPT_STATUS_CODE.running}::smallint ) /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */ AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.ownerRunIdSql} AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer AND ${table}.lease_id = ${input.leaseIdSql}`; }