Files
Alessandro Costa c5d52e5c40 fix(server-beta): re-pin payload.generation_job_id on idempotent re-create
A generation job whose payload points at a non-existent job id is dropped
silently: `ProviderObservationGenerator` resolves the row through
`payload.generation_job_id`, so `lockOutbox` finds nothing, logs at info level
and returns `{ status: "completed" }` to BullMQ without ever touching Postgres.
The queue reports success, the row stays `queued` forever, and nothing is
recorded as failed.

That pointer goes stale on the upsert path. Every re-create mints a fresh id
for `$1` and puts the same id in the payload it hands over. On
`ON CONFLICT (idempotency_key)` the existing row wins, so the minted id is
discarded for the `id` column -- but `payload || excluded.payload` still lets
it overwrite `generation_job_id`, leaving the row pointing at an id that was
never inserted.

This only bites `session_summary`. Its idempotency key is derived from
`sourceId`, which for summaries is the session, so every re-run of a session
summary collides. Event jobs key off the individual event id and never conflict.

Re-pin `generation_job_id` to the surviving row inside the same merge.

Notes for reviewers:
- An operator re-queue flow that "reuses the persisted payload" (#2459 /
  discussion #2460) does not recover these rows: replaying the stored payload
  replays the stale pointer, and the job is dropped again just as silently.
- Observed on a production deployment running server-beta since May: 190 summary
  jobs stranded in `queued` across four months, 190/190 with
  `payload->>generation_job_id` absent from the table. Event jobs on the same
  instance: 0 divergences in ~221k rows. After re-pointing the payload the same
  rows started draining immediately.

Co-authored-by: Alessandro <alessandropcostabr@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 20:29:46 -07:00
..