the run's OWN provider connection (per-run, never a shared engine-level provider — see the per-run-connection requirement).
CompanyIntegrationRun.ID this service governs.
OptionalleaseMinutes: numberlease length; callers pass max(DEFAULT_LEASE_MINUTES, options.MaxRuntimeMinutes ?? 0) so a long-batch override only ever EXTENDS protection.
OptionalcontextUser: UserInfoStatic ReadonlyDEFAULT_Default lease when the caller supplies no MaxRuntimeMinutes.
The fence token returned by the successful claim (null before claim).
Last lease expiry the DB confirmed for us (null before claim).
Lease length in minutes this service renews with.
The opaque owner token minted for this execution.
The CompanyIntegrationRun ID this service governs.
Batch-boundary fence: a cheap SELECT of the ownership columns. The sync loop calls this BEFORE each batch's writes; if we no longer own the row, the caller must throw RunOwnershipLostError and stop writing. Deliberately a plain read (no lease extension) — renewal belongs to the heartbeat timer, and a boundary check must stay cheap enough to run every batch.
Atomically claim the run row. Returns true iff WE now own it (the sproc's single UPDATE succeeded because the row was unowned or its lease had expired). On success the DB-assigned FenceToken (bumped by the claim) and lease expiry are cached for renewals and boundary checks.
Terminal release: set the final status, clear OwnerToken/LeaseExpiresAt, stamp EndedAt if unset. Token- AND fence-checked — a stale holder (lease reclaimed) releasing late is a harmless no-op (returns false).
The fence is sent for the same reason Renew() sends it: the owner token proves only that some context using this token owns the row, not that THIS context still does. Each instance mints its own token and claims once, so the two are equivalent today — but Claim() is re-callable and overwrites the fence, so passing it keeps the guarantee in the procedure rather than in call-site discipline.
Renew the lease (token+fence-checked). Zero rows back ⇒ ownership lost. Optionally piggybacks a progress snapshot onto the same write, and always surfaces the row's CancelRequestedAt so the heartbeat doubles as the cross-process cancel poll.
OptionalprogressJSON: stringStart the background renewal timer at interval ≈ lease/3 (so a renewal must fail ~3 consecutive times before the lease can lapse). The timer body is best-effort and never throws; renewal failures surface through opts.onLost exactly once. Idempotent — restarting replaces the timer.
Optionalopts: HeartbeatOptionsStop the renewal timer (idempotent).
Sync the entity's in-memory ownership columns to the service's last-known
authoritative values before ANY full-row run.Save(). The generated
spUpdate writes every column from the entity's in-memory state, so a
terminal Save without this sync would clobber the DB's live
FenceToken/OwnerToken/LeaseExpiresAt with the stale values the entity was
loaded with (typically pre-claim). Call this immediately before each
Save on the run row; Release() then clears ownership atomically.
Persist a progress snapshot to the run row's ProgressJSON (PR 1 item 4 — progress lives in the database; readers query the row). Ownership-guarded (WHERE OwnerToken AND FenceToken) so a reclaimed run can never overwrite the new owner's progress. Throttled internally (default once per 5s) so progress never becomes its own hot path; best-effort — a progress write failure must never fault the sync.
Per-run ownership manager for durable CompanyIntegrationRun execution (GH tasks.md PR 1). One instance is created per sync run and owns:
All sproc calls use the dialect-portable positional-parameter convention (same pattern as ScheduledJobEngine's lock sprocs): SQL Server binds the sproc's named params to positional
@pNplaceholders via EXEC; PostgreSQL calls the plpgsql port viaSELECT * FROM fn($1,...). The SAME value array serves both.