You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(knowledge): guard the completed sync log and free a deleted run's lock
`executeSync`'s success path closed its sync-log row before
`writeTerminalConnectorState` ran its ownership check, and the close was
guarded only on `status = 'started'`. That guard defers to the scheduler's
sweep, but the sweep is not the only writer that strands a live run: the
knowledge-base-deleted writers clear the token unconditionally, a user
pausing a connector flips it out of `syncing`, and the reaper's reclaim and
its log-close are two statements that can commit apart. In each case the
run's connector write is refused while its log row is still `started`, so
the run published a `completed` row for bookkeeping that was discarded —
and `loadPreviousListingObservation` reads exactly those rows as
corroboration for the next run's reconciliation.
The close now takes the ownership condition itself, reusing
`stillHoldsSyncLock` as an EXISTS predicate so the log row and the connector
row are written under the same condition and cannot disagree. Swapping the
two calls was considered and rejected: a `completeSyncLog` failure would then
leave a `started` row on a connector already recorded `active`, the reaper
would later mark it `failed`, and a legitimate observation would be lost
silently. Only the success path is guarded — a `failed` row is never read
back as evidence, and both failure paths legitimately close a run whose lock
is already gone. A refused close short-circuits to the superseded result the
terminal write would have produced two statements later.
The `ConnectorDeletedException` handler hard-deleted leftover documents and
closed its log, but wrote nothing to the connector row, leaving it `syncing`
with a live token. Nothing else could clear it: the reaper requires
`isNull(archivedAt)` and `isNull(deletedAt)`, so the one writer able to
recover a stranded lock skips exactly the rows this path creates. It now
releases token and lease and makes the transition terminal, matching the two
knowledge-base-deleted writers. Guarded on ownership alone rather than
`stillHoldsSyncLock`, for the same reason the heartbeat is: the connector
being archived is this path's precondition, so a liveness clause would
reject every write the release exists to make. A no-op when the row was hard
deleted rather than archived — a user-initiated connector delete removes the
row outright, leaving nothing to unwedge.
0 commit comments