/** * Deterministic presence-lease selection — pure rules, no filesystem I/O. * * WHY THIS MODULE EXISTS. The canonical presence read used to look at ONE * session tree — the tree the caller is bound to — and answered "inactive" * whenever that tree had no in-flight lease. A peaks run whose lease landed * in a different tree (a sibling session, a rotated session, a session the * caller-id resolution could not reach) was therefore invisible: * `peaks skill presence --json` reported `{"active": false}` while a live * lease sat on disk. The rules live here, free of I/O, so they can be * unit-tested directly against fixture lease sets. * * THE RULES (in order): * * 1. ELIGIBLE = not terminal AND not stale. * * - terminal: `terminalAt` is set, or `status` is outside * {`running`, `preparing`} — a lease that already reached its end * state is never reported as driving the work. * - stale: `now - lastHeartbeat` is past the lease's OWN window * (`staleThresholdMs`). Nothing refreshes a lease heartbeat on a * cadence, so a lease left behind by a finished session would * otherwise keep reporting "active" forever. * * 2. SCOPE. When the BOUND tree (the tree the caller is bound to) holds * at least one eligible lease, only bound-tree leases are selectable: * the bound tree is the caller's own state and keeps its precedence. * Only when the bound tree has NO eligible lease do the other trees' * leases become selectable — that is the cross-session fallback. * * 3. ORDER. Freshest heartbeat first, then freshest start, then * sessionId, callerId, workflowId. The tail is a total order, so the * same lease set always names the same winner — no `readdirSync` * order leaks into the answer. */ import { type SkillPresenceLease } from './presence-lease-types.js'; /** Which tree a candidate lease was read from. */ export type PresenceLeaseScope = 'bound' | 'other'; export type PresenceLeaseCandidate = { readonly lease: SkillPresenceLease; /** The session tree the lease was read from. */ readonly sessionId: string; readonly scope: PresenceLeaseScope; }; export type PresenceLeaseSelection = { readonly lease: SkillPresenceLease; readonly sessionId: string; readonly scope: PresenceLeaseScope; }; /** Why the read found no active lease. */ export type PresenceInactiveReason = 'no-leases' | 'all-terminal' | 'all-stale' | 'terminal-and-stale' /** Defensive: the caller passed a candidate set that DOES hold an eligible lease. */ | 'eligible-present'; export type PresenceInactiveDiagnostic = { readonly reason: PresenceInactiveReason; readonly leaseCount: number; readonly terminalCount: number; readonly staleCount: number; readonly eligibleCount: number; }; /** * The stale window that applies to THIS lease. * * `24h` leases get the 24h run's own window (`STALE_STARTED_AFTER_MS`, * 24h30m — the canonical lease-GC start predicate); every other lease * gets the canonical heartbeat window (`STALE_HEARTBEAT_MS`, 1h). A 24h * run must not lose its presence after one hour just because nothing * refreshes the lease heartbeat while the run is in flight. */ export declare function staleThresholdMs(lease: SkillPresenceLease): number; /** A lease that reached its end state — never reported as active. */ export declare function isTerminalPresenceLease(lease: SkillPresenceLease): boolean; /** A lease whose heartbeat is older than its window — never reported as active. */ export declare function isStalePresenceLease(lease: SkillPresenceLease, nowMs: number): boolean; /** A lease that may be reported as the active presence. */ export declare function isEligiblePresenceLease(lease: SkillPresenceLease, nowMs: number): boolean; /** * Pick the lease that represents "who is driving peaks right now". * * Returns `null` when no candidate is eligible — the caller answers * "inactive" (never an optimistic default). */ export declare function selectActivePresenceLease(candidates: readonly PresenceLeaseCandidate[], nowMs: number): PresenceLeaseSelection | null; /** * Explain why the read found nothing active. * * Precondition: called only when `selectActivePresenceLease` returned * `null` (so `eligibleCount` is 0). The `eligible-present` reason is a * defensive branch for a caller that violates that precondition. */ export declare function explainInactivePresence(candidates: readonly PresenceLeaseCandidate[], nowMs: number): PresenceInactiveDiagnostic;