/** * daemon/todoist-claims.ts — releasing a claim nobody came back for. * * The webhook claims a trigger before dispatching it, so two mechanisms * watching one checkbox cannot both fire. That is correct while the run is * alive and a trap when it is not: a session that dies mid-turn leaves the task * claimed, the webhook then skips it, and the trigger is dead until a human * notices a routine that quietly stopped running. Permanent silence is worse * than a duplicate — it is the failure this whole subsystem exists to prevent. * * PAI's poller ages its own claims. This exists so the webhook path does not * DEPEND on that: a machine running the webhook trigger with no poller must * still recover. Releasing twice is harmless — removing an absent label is a * no-op — so two releasers converge, where two dispatchers would not. The * window here is deliberately longer than PAI's, so its more informed decision * lands first and this is only ever the backstop. * * Elapsed time only, never "the session looks gone". The hub has returned an * empty session list while nineteen sessions were running; a recovery that * trusts that spawns a duplicate per task. Three hours of wall clock is not a * matter of opinion. */ /** * The invariant, and why it is not a constant. * * A flat window cannot stay behind an adaptive one. PAI releases at * max(expected x 10, 120 min); a flat 180 min sat BEHIND it only for tasks * expected to run under 18 minutes, and in front of it for every real routine — * so the less informed release fired first. A four-hour sweep would have had * its claim released at three hours, and the next poll, seeing an unclaimed * overdue task, would have dispatched a second run alongside the first: the * duplicate the interlock exists to prevent, produced by the backstop. * * The honest bound is not a duration at all. A claim must not survive INTO THE * NEXT SCHEDULED RUN — past that point the next trigger arrives and the claim * blocks it, which is the silence again. So the deadline is the next occurrence * itself, which Todoist has already told us: on completion of a recurring task * the due date has advanced, and the payload carries it. * * That is always outside any per-task timer shorter than one period, without * having to guess anyone else's arithmetic. */ export declare const CLAIM_DEADLINE_MARGIN_MS: number; /** A run is allowed to be slow. Nothing is released in its first two hours. */ export declare const CLAIM_MIN_AGE_MS: number; /** Used only when the next occurrence is unknown — comfortably past PAI's default. */ export declare const CLAIM_FALLBACK_AGE_MS: number; /** * A ceiling on the occurrence rule. Nothing here recurs less often than daily, * so a claim older than a day is stale whatever the due date says. */ export declare const CLAIM_MAX_AGE_MS: number; export declare function recordClaim(taskId: string, at?: string, nextDue?: string): void; export declare function forgetClaim(taskId: string): void; export declare function listClaims(): Array<{ taskId: string; claimedAt: string; nextDue?: string; }>; /** * When a claim stops being credible. * * The next occurrence, less a small margin, so the claim is gone before the * trigger it would block. With no known occurrence, a flat twelve hours — * chosen to clear an adaptive poller's default rather than to be right. * * THE OCCURRENCE CAN ITSELF BE WRONG, so it needs a ceiling. Ticking a * recurring task by hand consumes the occurrence already scheduled, and Todoist * advances the due date past it: a manual run in the afternoon of a daily * routine moves the next occurrence to the day after tomorrow. Following that * date alone kept a claim alive 39 hours — straight across a scheduled run it * then suppressed, which is the silence this module exists to end, produced by * the module's own arithmetic. * * A day is the ceiling because nothing here recurs less often than daily, so * past that the claim cannot still be describing a live run. It stays behind an * adaptive poller for anything expected under a couple of hours, which is every * routine this actually guards; a genuinely day-long run would need its own * answer, and does not exist yet. */ export declare function claimDeadline(c: { claimedAt: string; nextDue?: string; }): number; /** Claims whose deadline has passed — whatever took them is not coming back. */ export declare function expiredClaims(now?: number): Array<{ taskId: string; ageMs: number; }>; /** * Release every claim nobody came back for. * * `release` is injected so the sweep is testable without Todoist, and so a * failed release leaves the record in place to be retried next time rather than * forgetting a claim that is still on the task. */ export declare function sweepAbandonedClaims(release: (taskId: string) => Promise, now?: number): Promise; /** * May this claim be released yet? * * The risk being guarded is precise: a task that is UNCLAIMED AND OVERDUE is * ordinary overdue work, and the next poll dispatches it. So a session that * drops its claim before the completion lands makes the same run happen again, * looking spontaneous. Finishing is two steps that are not atomic and the * ordering is not ours to enforce, so we check the state instead of trusting it. * * The test is therefore "is the task still overdue", not "has the due date * advanced past the occurrence I recorded". The first version compared against * the recorded occurrence, which breaks the moment anything legitimately moves * the date BACKWARDS — PAI restores the occurrence a manual trigger consumed, * so a schedule repair would have made this refuse a release on a run that * genuinely finished. A stuck claim caused by a correct repair. * * Refusing inverts the failure in the direction both sides agreed on: a claim * stuck until a timer releases it, rather than a run nobody asked for. */ export declare function mayRelease(taskId: string, currentDue: string | undefined): { ok: true; } | { ok: false; reason: string; }; //# sourceMappingURL=todoist-claims.d.ts.map