-- Durable event-to-push matching follower. The trigger runs in the event insert -- transaction, so every accepted persistent event has a crash-safe match job and -- rejected/rolled-back events never do. Processing is idempotent through the -- push_wake_outbox endpoint/event unique key. CREATE TABLE push_match_queue ( community_id UUID NOT NULL REFERENCES communities(id), event_id BYTEA NOT NULL CHECK (length(event_id) = 32), state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending','matching')), attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0), next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(), lease_until TIMESTAMPTZ, claim_id UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (community_id, event_id) ); CREATE INDEX push_match_queue_due ON push_match_queue (next_attempt_at, created_at) WHERE state = 'pending'; CREATE INDEX push_match_queue_recovery ON push_match_queue (lease_until) WHERE state = 'matching'; CREATE FUNCTION enqueue_push_match_job() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN -- Keep this allowlist identical to the relay's validated NIP-PL descriptor. -- Centralizing it on the events table covers every durable producer, -- including internal paths that bypass live dispatch. IF NEW.kind IN (7, 9, 1059, 40007, 46010) THEN INSERT INTO push_match_queue (community_id, event_id) VALUES (NEW.community_id, NEW.id) ON CONFLICT DO NOTHING; END IF; RETURN NEW; END $$; CREATE TRIGGER events_enqueue_push_match AFTER INSERT ON events FOR EACH ROW EXECUTE FUNCTION enqueue_push_match_job();