// src/engine/safety/lock-staleness.ts — Pure staleness predicates (SPEC-719)
// These are pure functions with no I/O — easy to unit test in isolation.
/**
 * A lock with lastHeartbeatAt older than this threshold is reclaimable.
 * 5 minutes balances long-running ops with crash recovery.
 */
export const STALE_THRESHOLD_MS = 5 * 60 * 1000;
/**
 * Returns true when the heartbeat is older than STALE_THRESHOLD_MS from `now`.
 */
export function isHeartbeatStale(lock, now) {
    const lastHb = new Date(lock.lastHeartbeatAt).getTime();
    return now - lastHb > STALE_THRESHOLD_MS;
}
/**
 * Returns true when the hard TTL has expired: acquiredAt + ttlMs < now.
 */
export function isTtlExpired(lock, now) {
    const acquired = new Date(lock.acquiredAt).getTime();
    return now > acquired + lock.ttlMs;
}
/**
 * Returns true when the pid is on the same hostname as `localHostname` and the
 * process no longer exists (`process.kill(pid, 0)` throws ESRCH).
 *
 * NOTE: Only meaningful when `lock.hostname === localHostname`. Remote PIDs
 * cannot be checked cross-machine.
 */
export function isPidDead(lock, localHostname) {
    if (lock.hostname !== localHostname) {
        return false;
    }
    try {
        process.kill(lock.pid, 0);
        return false;
    }
    catch (err) {
        if (err instanceof Error && 'code' in err && err.code === 'ESRCH') {
            return true;
        }
        // EPERM means the process exists but we lack permission to signal it — treat as alive
        return false;
    }
}
/**
 * Compute the stale reason for a lock, if any.
 * Returns undefined when the lock is live.
 * Order: TTL expiry is checked first (hardest deadline), then PID, then heartbeat.
 */
export function computeStaleReason(lock, localHostname, now) {
    if (isTtlExpired(lock, now)) {
        return 'ttl-expired';
    }
    if (isPidDead(lock, localHostname)) {
        return 'pid-dead';
    }
    if (isHeartbeatStale(lock, now)) {
        return 'heartbeat-expired';
    }
    return undefined;
}
/**
 * Enrich a raw lock metadata object with computed staleness fields.
 */
export function enrichWithStaleness(lock, localHostname, now) {
    const staleReason = computeStaleReason(lock, localHostname, now);
    return {
        ...lock,
        stale: staleReason !== undefined,
        ...(staleReason !== undefined ? { staleReason } : {}),
    };
}
//# sourceMappingURL=lock-staleness.js.map