Phase 0: arq task queue, durable webhooks, single-active scheduler - #3
Merged
Merged
Conversation
…igrations - Add arq>=0.26.0 + config settings (redis_url, worker_role, dashboard_url, stripe_portal_config_id) - New backend/task_queue.py (arq WorkerSettings, get_arq_pool, record_dead_letter, inbound processing tasks, durable outbound wrappers for all 5 integrations Twilio/Resend/GBP/Square/DataForSEO, cron entrypoints) + backend/worker.py (module named task_queue, not queue, to avoid shadowing stdlib queue that redis imports) - Migrations 010_webhook_events, 011_dead_letter (both +RLS service_role_all), 012_booking_credentials (moved to Phase 0 per C1) - Refactor routers/webhooks.py: persist inbound events to webhook_events, dedupe by idempotency key, return 200 fast, enqueue processing. GBP inbound decodes Pub/Sub push envelope, uses messageId as key, fetches review resource before drafting (C3) - Refactor scheduler.py to enqueue-only; main.py lifespan branches on worker_role (web = arq pool only; scheduler = APScheduler enqueue-only + arq pool) - Mandatory utils/reconcile.py reconcile_pending_webhooks (every 5 min, C4) - Deploy: localmate-redis (AOF, noeviction, 128MB), localmate-worker, localmate-scheduler on deploy_default; WORKER_ROLE=web on backend; update deploy_backend.sh, DEPLOY.md, Dockerfile, .env.example (REDIS_URL, WORKER_ROLE, STRIPE_PORTAL_CONFIG_ID, DASHBOARD_URL) - Tests: webhook_events, dead_letter, queue_tasks, scheduler_enqueue, outbound_durable, reconcile, gbp_envelope_decode; new stub env in conftest - pytest pythonpath config so tests collect without PYTHONPATH
- task_queue: drop unused _mark_event bump param; hoist datetime import - reconcile: fix stale queue.WorkerSettings docstring ref -> task_queue - main: dedupe get_arq_pool import across lifespan branches; cache logger - tests: remove unused imports/params (json, insert_id, redundant webhooks imports)
… gate)
Item 1 (CRITICAL): arq retries + dead-lettering — RETRY_BACKOFF, _retry_defer,
raise arq.Retry on non-final failures, dead-letter on MAX_TRIES; drop
reliance on ctx["max_tries"] (not present in arq ctx).
Item 2: GBP fetch decrypts access+refresh tokens, refreshes empty, retries once
on 401; raises on transport/auth failure instead of silently returning {}.
Item 3: dedup webhook_events SELECT before provider fetch in inbound_review.
Item 4: keep secrets out of Redis/AOF — tasks take client_id, decrypt in-worker
(post_gbp_reply_task, square_sync_task, _load_client, _resolve_gbp_access_token).
Item 5: single reconcile trigger (arq cron only); deterministic _job_id dedup,
stale-processing recovery, atomic pending claim.
Item 6: only ack duplicate on unique-violation (23505); re-raise other DB errors.
Item 7: propagate outbound failures — strict email/dataforseo variants, menu
dispatch inspects sync result, holiday-skip treated as success.
Item 8 (C10): real Postgres+Redis staging gate (scripts/phase0_staging_gate.*),
asyncpg dev dep, DEPLOY.md acceptance checks.
Add test_lifespan.py for main.py worker_role branching; update affected tests.
…ery, real staging gate Fix 1 (REVIEW BLOCKER): move client loading + credential resolution INSIDE the coroutine passed to _run_outbound for post_gbp_reply_task and square_sync_task, so DB outages / missing clients / decrypt / token-refresh failures get arq retry + dead-letter instead of failing bare. Add retry-then-dead-letter tests for client-load and decrypt failures on both tasks. Fix 2 (REVIEW BLOCKER): add processing_started_at lease column to webhook_events (migration 010, amended in place). Record it on the atomic pending->processing claim, clear it on retry reset, and base stale-processing recovery on it instead of created_at so an actively-running event is never reclaimed. Add test that a row with old created_at but fresh lease stays processing. Fix 3 (REVIEW BLOCKER): rewrite phase0_staging_gate to import and run the real task_queue.FUNCTIONS (process_stripe_event, post_gbp_reply_task) and the real reconcile_pending_webhooks against real Redis/Postgres via a psycopg Supabase shim, instead of substitute tasks / replicated SQL. Add the single-scheduler enqueue-exactly-once + restart/resume check using the real create_scheduler. Fix 4 (TESTING FIND): change FOR INSERT USING to WITH CHECK in 003_reviews.sql so the INSERT policy is valid Postgres and a clean-DB migration apply works. Tests: 88 passed.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phased foundations for the LocalMate Feature-Parity Initiative: arq/Redis task queue, durable webhook processing, single-active scheduler, and dead-letter reliability.
Changes
task_queue.py,worker.py): durable inbound processing tasks (Stripe/GBP/menu), outbound wrappers for all 5 integrations (Twilio, Resend, GBP, Square, DataForSEO) with retry+backoff+dead-letter, 6 cron entrypoints, and mandatory 5-min reconciliation job.routers/webhooks.py): persist inbound events towebhook_eventswith idempotency (unique onprovider, idempotency_key), dedup before provider work, return 200 fast, enqueue processing via arq. GBP inbound decodes the Pub/Sub push envelope (base64message.data) usingmessageIdas the idempotency key, fetches the review resource with token decrypt+refresh before enqueue.scheduler.py): all 6 cron jobs are now enqueue-only (APScheduler triggers arq enqueue, no inline business logic).main.pyrole-branches viaWORKER_ROLE:webstarts only the arq pool;schedulerstarts APScheduler enqueue-only + arq pool;workerruns viaarq worker.WorkerSettings.utils/reconcile.py): mandatoryreconcile_pending_webhooksrecovers stalependingand staleprocessingleases (usingprocessing_started_at, notcreated_at). Runs every 5 minutes via arq cron.010–012:webhook_events(withprocessing_started_atlease + RLS),dead_letter, 5 booking-credential columns onclients. Pre-existing003_reviews.sqlRLS policy fix (FOR INSERT USING→WITH CHECK).localmate-redis(AOF,noeviction) +localmate-worker+localmate-schedulerservices indeploy/docker-compose.yml; deploy script + DEPLOY.md updated; Doppler vars documented.Testing
cd backend && uv run pytest tests/ -q001–012in order on isolatedpostgres:16containerarq worker WorkerSettings --burst/--checkagainst real Redis--burstdrains (exit 0);--checkcorrecttest_lifespan.py(2 tests)docker compose -f deploy/docker-compose.yml configWORKER_ROLEbash scripts/phase0_staging_gate.sh(real Redis + Postgres)C10 staging gate checks (automated, real services)
001–012applied, tables + RLS present_job_iddedupes duplicate enqueueprocess_stripe_eventdrains todone+ records leasepost_gbp_reply_tasksetup-failure retries → dead-letter (attempts=5)reconcile_pending_webhooksrecovers stale processing + re-enqueues stale pendingDeferred to post-deploy: real Stripe/GBP live calls, multi-container scheduler behavior, actual droplet deployment. Per agreed workflow the pre-PR gate is green unit tests + static verification — all passed.
Attached Images
[unit_test_run_045234a.log]
[migration_apply_045234a.log]
[worker_load_check_045234a.log]
[test_lifespan_045234a.log]
[compose_config_045234a.log]
[phase0_staging_gate_045234a.log]
View in Vorflux Session
Session Details
(aside)to your comment to have me ignore it.