LibreChat/api/server/services
Danny Avila c7f3f8983b feat: scheduled-run lifecycle epoch, durable resume lease, DB-enforced capacity
Foundation for the new merge blockers. Collapses B1/B3/B8/B10 (and B7's per-run half)
into one coherent ScheduleRun state machine rather than four overlapping patches, since
they all mutate the same row.

B1 - pause epoch + CAS: ScheduleRun.resumeSeq is a monotonic per-occurrence segment
counter, incremented by every resume reservation. recordRunOutcome's pause branch now
CASes on expectResumeSeq (both the guard read and the row flip), so a stale
requires_action callback from a superseded segment can no longer demote a run that a
resume already promoted. resume.js threads its segment's resumeSeq into the re-pause.

B3 - durable resuming state: promoteRunToStarted (whose lossy 'missing' -> 'ok' admitted
both racers) is replaced by acquireResumeLease/markResumeClaimed/commitResumeLease/
releaseResumeLease. The lease is holder-fenced and deadline-bounded, and records whether
the approval was already consumed: a PRE-claim crash is adoptable (rolled back to
requires_action, slot freed, approval stays actionable), a POST-claim crash must roll
forward. resume.js drives all four transitions.

B8 - atomic global capacity: a unique partial index on {capacitySlot} where
status:'started' makes fireConcurrency a DB-enforced bound instead of a read-then-compare
count. Both admissions (fire insert, resume promotion) claim a slot in the SAME write via
withCapacitySlot, so two admissions of DIFFERENT schedules can no longer both pass a
cap-1 check. Capacity is now refused BEFORE inserting, so the old reserve-then-rollback
path is gone.

B10 - settlement: requestRunAbort records abortRequestedAt WITHOUT freeing the slot; the
slot is released only by a terminal outcome, i.e. when the generation owner confirms the
run actually stopped. Aborted runs therefore keep counting capacity until settled.

B7 (per-run half) - ScheduleRun.configRevision captures the schedule's revision at claim
time; applyTerminalBookkeeping filters on it so a run started under older config cannot
apply counters or auto-disable a schedule the owner has since edited. Schedule.
configRevision is bumped ONLY by updateScheduleById (owner edits), atomically with the
claimToken rotation.

B2 (field only) - User.deletionRequestedAt added as the durable deletion-barrier field;
the barrier logic itself is not wired yet.

Also fixes a latent discriminator bug: isActiveRunConflict matched any duplicate-key
lacking scheduledFor, so the new {capacitySlot} index would have been misread as a
per-schedule overlap. It now matches scheduleId exactly.

Absent fields disable each fence, so existing rows/schedules keep current behavior and
no migration is required.
2026-07-22 23:51:28 -04:00
..
__tests__
Agents
Artifacts
Config
Endpoints
Files
Runs
Schedules feat: scheduled-run lifecycle epoch, durable resume lease, DB-enforced capacity 2026-07-22 23:51:28 -04:00
Skills
start
Threads
Tools
ActionService.js
ActionService.spec.js
AssistantService.js
AuthService.js
AuthService.spec.js
cleanup.js
createRunBody.js
GraphApiService.js
GraphApiService.spec.js
GraphTokenService.js
initializeMCPs.js
initializeMCPs.spec.js
initializeOAuthReconnectManager.js
MCP.js
MCP.spec.js
MCPRequestContext.js
OboPolicyService.js
OboTokenService.js
OboTokenService.spec.js
PermissionService.js
PermissionService.spec.js
PluginService.js
systemGrant.spec.js
ToolService.js
twoFactorService.js