"""v3 second_brain: sources.claimed_by / claimed_at Revision ID: c8f1d9e34b7a Revises: 7b3e8a5c1f29 Create Date: 2026-05-25 12:30:00.000000 Adds a lightweight claim mechanism so the tower and dev sides never grab the same row off the queue. The claim flow used by both workers is: BEGIN; SELECT id, status, source_type FROM second_brain.sources WHERE AND claimed_by IS NULL ORDER BY ingested_at ASC LIMIT 1 FOR UPDATE SKIP LOCKED; UPDATE second_brain.sources SET claimed_by = :worker, claimed_at = now() AT TIME ZONE 'UTC' WHERE id = :id; COMMIT; `FOR UPDATE SKIP LOCKED` is the actual race-safety primitive — the column pair is observability (who has it, since when, for stale-claim recovery). A partial index would help once we have a real queue depth; deliberately not added now per Travis's "no gold-plating". """ from __future__ import annotations from collections.abc import Sequence from alembic import op revision: str = "c8f1d9e34b7a" down_revision: str | Sequence[str] | None = "7b3e8a5c1f29" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.execute("SET search_path TO second_brain") op.execute("ALTER TABLE sources ADD COLUMN claimed_by VARCHAR(100)") op.execute("ALTER TABLE sources ADD COLUMN claimed_at TIMESTAMP") def downgrade() -> None: op.execute("SET search_path TO second_brain") op.execute("ALTER TABLE sources DROP COLUMN IF EXISTS claimed_at") op.execute("ALTER TABLE sources DROP COLUMN IF EXISTS claimed_by")