fix(worker): re-check document after the legacy claim_ingestion_jobs row lock
Merged
BI
25d ago+209+209+012345678901234567890123456789-6-6-0123456789
Summary
- Follow-up to #3633. The legacy three-argument
claim_ingestion_jobs(text, integer, integer)picks candidates from a snapshot, takes the picked document's authority lock and sibling-lease locks, then locks the job row withFOR UPDATE SKIP LOCKED, but never checked that the locked row still belonged to the picked document. A job re-pointed at another document in that window was leased under the old document's authority and sibling-lease check. - New migration
supabase/migrations/20261018062736_recheck_legacy_ingestion_claim_document_after_job_lock.sqlreplaces the three-argument function in place. After the job row lock it skips the job unlessv_job.document_idstill equals the picked document. A skipped job stays pending with no lease and no consumed attempt, the same as every other skip in the loop. Signature, defaults, grants, return shape and every other branch are unchanged. The five-argument function (#3633) is not touched. - New
tests/sql/ingestion-legacy-claim-recheck-after-lock.sql, modelled ontests/sql/ingestion-claim-recheck-after-lock.sql(run by the Migration replay job with everytests/sql/*.sql). It wrapslock_ingestion_document_authorityinside its rolled-back transaction so the job is re-pointed between the pick and the job row lock. It covers a re-point to a same-type document and to a different file type, plus a control and a follow-up claim that leases the job under its new document. - Companion files:
supabase/schema.sqlmirrors the migration (same pattern as #3633),supabase/applied-migration-hashes.jsonvianpm run migrations:seal(append-only, no existing entry changed).supabase/drift-manifest.jsonwill be taken byte-for-byte from this PR's Migration replay artifact (npm run drift:manifestneeds Docker, which isn't available locally).database.types.tsis unchanged because the signature is unchanged.
Areas touched: Data platform and access
Verification
- Checks run:
- Offline harness (PGlite with a minimal stub of the touched tables and authority lock): against the 20261013075944 function the new test fails (
legacy claim: a job whose document changed after the pick must not be leased under the old document's authority, and the file-type case); the control passes. With the new migration applied the whole file passes. node scripts/check-migration-immutability.mjs:Migration immutability guard passed: 321 shipped migrations unchanged.check:migration-role: passed.check:function-grants:OK — all 130 SECURITY DEFINER public function(s) are revoked from PUBLIC.- Vitest
supabase-schema,migration-sql,migration-immutability,migration-history-guards,hosted-migration-role-guard,worker-row-contract,drift-detection: 300/301 passed; the one failure is the expected stale drift manifest before the CI artifact lands. - The first push used the documented scoped
SKIP_DRIFT_GUARD=1override, as in #3633, because the manifest can only be generated in CI. No other guard was skipped. - Hosted proof: the Migration replay job runs the full chain and the new SQL test against the real
lock_ingestion_document_authority.
- Offline harness (PGlite with a minimal stub of the touched tables and authority lock): against the 20261013075944 function the new test fails (
Risk and rollout
- Risk: Low. One extra predicate on the locked row inside an existing SECURITY DEFINER function. It can only make the claim skip a job it previously leased by mistake.
- Rollback: a later migration restoring the 20261013075944 body.
- Merge gate: this PR adds a migration under
supabase/migrations/**; merging applies it to the live database. The owner approved this PR including auto-merge (relayed 14:26 AWST 2026-10-18). - Provider or production effects: merging applies this migration to the live database immediately. No provider settings or other live data were changed from this branch.