Skip to content

UN-3445 [GATED-FEAT] PG-queue auxiliary-worker migration (OSS): loader fix + notification seam - #2217

Open
muhammad-ali-e wants to merge 13 commits into
mainfrom
feat/UN-3445-pg-queue-aux-workers
Open

UN-3445 [GATED-FEAT] PG-queue auxiliary-worker migration (OSS): loader fix + notification seam#2217
muhammad-ali-e wants to merge 13 commits into
mainfrom
feat/UN-3445-pg-queue-aux-workers

Conversation

@muhammad-ali-e

Copy link
Copy Markdown
Contributor

What

The OSS slice of the PG-queue auxiliary-worker migration wave (epic UN-3445). Two flag-gated changes, both inert with pg_queue_enabled off:

  • UN-3798 — fix the PG pluggable-worker task loader (workers/worker.py): skip the broken file-path task load for pluggable workers (their tasks are already registered by build_celery_app's package import). Non-pluggable workers keep the file-path load. Fixes a startup crash-loop for cloud PG pluggable workers; the Celery path is untouched.
  • UN-3753 — route the buffered webhook-notification dispatch through the PG-queue transport seam (notification_v2/notification_dispatch.py): PG when pg_queue_enabled for the org, else celery_app.send_task (byte-identical). PermanentDispatchError (raised only on the PG branch) dead-letters permanent enqueue errors; the Celery error path is unchanged.

Why

  • Part of migrating the remaining dispatch/auxiliary workers off Celery onto the PG queue so Celery can eventually be decommissioned (sub-tasks of UN-3445).

Can this PR break any existing features?

  • No. Both changes are gated by the pg_queue_enabled Flipt flag; with it off (the production default) resolve_transport fail-closes to Celery and the seams call the same send_task as before — byte-identical. The loader fix only skips a load path for pluggable workers (not deployed with the flag off). Dev-tested on a live stack: flag-off produced 0 PG rows (Celery taken); PG round-trips delivered end-to-end.

Notes on Testing

  • notification_v2/tests/ (12 tests) pass; pipeline_dispatch regression suite green; worker enum/loader tests pass. Merged current main in (clean, no conflicts).

Related Issues or PRs

  • UN-3445 (epic), UN-3798, UN-3753. Cloud counterpart PR covers UN-3752/3754/3779 + the chart. Flag-gated wave; merge-safe with the flag off.

muhammad-ali-e and others added 3 commits July 23, 2026 20:07
#2197)

* UN-3798 [FIX] Skip broken file-path task load for PG pluggable workers

The top-level worker.py loaded a pluggable worker's tasks.py via
spec_from_file_location("tasks", ...) — a bare module name with no parent
package — so the plugin's relative imports (from .clients import ...) failed
with "attempted relative import with no known parent package", crash-looping
every cloud PG pluggable worker (agentic_callback/UN-3754, agentic_studio/
UN-3779, bulk_download/UN-3752) on startup under `python -m pg_queue_consumer`.

The tasks are already registered by that point: WorkerBuilder.build_celery_app()
(called just above) verifies a pluggable type by importing
pluggable_worker.{type}.worker as a proper package (_verify_pluggable_worker_exists
→ import_module), which runs the plugin's `from . import tasks` and registers the
tasks on this same app. So the file-path load is both redundant and broken.

Fix: skip the file-path load for pluggable workers; non-pluggable (top-level)
workers keep it unchanged. The Celery path is unaffected — it runs via
`celery -A pluggable_worker.{type}.worker` (a dotted package import) and never
touches this loader; the is_pluggable() file-path branch never ran successfully.

Validated on a running dev stack via `python -m pg_queue_consumer`:
agentic_callback and agentic_studio now start clean ("tasks already registered
… skipping file-path task load" → "ready for Celery"); general (non-pluggable)
loads unchanged. Prerequisite for UN-3752 / UN-3754 / UN-3779.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* UN-3798 [FIX] Address #2197 review: accurate loader comment, to_directory(), zero-task guard

- Extract the task-load block into load_worker_tasks(worker_type) with guard-clause
  returns (depth 3 -> 1) and an accurate docstring: pluggable tasks register via
  WorkerBuilder's package import and Celery binds them on app finalize; the
  file-path load is skipped for them because it breaks any relative imports in the
  plugin's tasks.py. Softened the overstated "every worker crashes" and marked the
  cloud-plugin `from . import tasks` example as illustrative (contract, not internals).
- Add WorkerType.to_directory() as the single source for the underscore->hyphen dir
  mapping; to_import_path() and the file-path loader both read it (no more slicing
  the import path). + tests.
- Add a post-load zero-task check as a WARNING (not a hard raise): pluggable tasks
  bind on app finalize which can be after this point, so a raise would false-positive
  on a correctly-configured pluggable worker (the exact regression this PR fixes).
- Move `import importlib.util` to the module-top imports.

Deferred (per reviewer's hotfix note): the "spec_from_file_location never called for
pluggable" regression test — worker.py runs infra init + builds the Celery app at
import, so there is no clean seam to exercise the loader in isolation without a
larger main()-guard refactor. The extraction creates that seam for a fast-follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…nsport (flag-gated) (#2198)

* UN-3753 [GATED-FEAT] Route webhook notifications through PG-queue transport (flag-gated)

PG-queue analogue for the buffered-webhook dispatch, gated by the pg_queue_enabled
Flipt flag. Flag OFF (prod default) keeps the existing Celery send_task
byte-unchanged; flag ON enqueues send_webhook_notification onto the PG
`notifications` queue, drained by the PG notification consumer.

- Dispatch seam: notification_dispatch.py routes send_webhook_notification through
  resolve_transport — PG via enqueue_task when enabled for the org, else the prior
  celery_app.send_task (byte-identical, zero regression). _send_clubbed (the
  buffer-flush path) uses the seam; _org_identifier resolves the org pk -> string
  for the Flipt decision, keeping the pk in kwargs (the buffer/worker mark
  contract). + test_notification_dispatch.py (6 cases).
- Sites 2 & 3 (WebhookSend / WebhookBatch internal endpoints) stay on Celery: they
  use countdown stagger, which the PG queue has no delayed-visibility for. Follow-up.

Dev-tested on a live stack: flag-off took the Celery branch (0 PG rows); an enqueued
send_webhook_notification was drained by the pg_queue_consumer
(WORKER_TYPE=notification), delivered to a sink (200), and the row acked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* UN-3753 [GATED-FEAT] Address #2198 review: error-classing, org-id naming, call-site tests

- Robustness (P1): _send_clubbed's broad `except` mislabeled permanent dispatch
  errors as broker_failure and reverted to PENDING → retry-forever + mislabeled
  Sentry tracebacks until the attempt cap. Branch on class: ValueError/TypeError
  (enqueue_task validation / payload serialization) → dead-letter now with a
  distinct `result=dispatch_error` metric; keep revert-to-PENDING only for genuine
  transport/broker exceptions.
- Type design (P2): renamed the seam's routing param organization_id → org_string_id
  so it can't be conflated with the org pk in `kwargs["organization_id"]` (the
  worker buffer-mark contract) — swapping them was a silent mis-route to Celery.
- Observability (P2): _org_identifier now logs a warning (with org_pk) when the
  lookup returns None — a dangling FK is a data anomaly (CASCADE makes it otherwise
  unreachable), not an expected "org deleted" path; reworded the docstring and
  narrowed org_pk: int. Fixed the "before the webhook HTTP call" wording.
- Comment accuracy (P2): dropped the aspirational "usable by callers that surface
  it in an API response" from the Returns block (the sole caller discards it).
- Simplify (P3): dropped the redundant `or None` on the routing arg (resolve_transport
  already normalizes falsy); kept the load-bearing `or ""` on enqueue_task's org_id.
- Tests (P1): new test_send_clubbed.py locks the two-org-identifier contract
  (string routes, pk in kwargs), the transient→PENDING vs permanent→DEAD_LETTER
  recovery split, and _org_identifier (string id / None+warn). 11 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* UN-3753 [GATED-FEAT] Gate notification dead-letter classing to the PG path only

Keep the flag-off (Celery) error flow byte-identical, per the "no change to the
Celery path unless flag-gated" rule. The seam now raises a typed
PermanentDispatchError ONLY on the PG branch (when enqueue_task rejects the
message for a permanent reason — priority/exclusivity validation or a payload
that won't JSON-serialize). _send_clubbed dead-letters on that exception; every
other failure — including any Celery send_task error — falls to the transient
PENDING branch exactly as before UN-3753.

+ seam test that a permanent enqueue ValueError/TypeError is wrapped; the
call-site test now raises PermanentDispatchError (the real contract) instead of a
raw ValueError. 12 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • New Features

    • Webhook notifications now use organization-aware delivery routing.
    • Added reliable delivery through multiple supported transport paths.
  • Bug Fixes

    • Invalid numeric values are rejected before storage.
    • Permanent delivery failures are dead-lettered, while transient failures can be retried.
    • Improved worker startup validation and delivery failure logging.
  • Refactor

    • Standardized worker task loading and directory naming.
  • Tests

    • Expanded coverage for routing, retries, validation, and worker configuration.

Walkthrough

Webhook notifications now select PG or Celery using an organization string identifier, classify dispatch failures, and reject non-standard JSON numbers. Worker startup centralizes directory mapping, strictly loads non-pluggable tasks, and validates task registration.

Changes

Webhook dispatch and queue validation

Layer / File(s) Summary
Transport dispatch routing
backend/notification_v2/notification_dispatch.py, backend/notification_v2/tests/test_notification_dispatch.py
dispatch_webhook_notification resolves PG or Celery, preserves task arguments, shares a dispatch ID, and wraps permanent PG enqueue errors.
Organization-aware buffer dispatch
backend/notification_v2/internal_api_views.py, backend/notification_v2/tests/test_send_clubbed.py
_send_clubbed resolves the organization string identifier for routing, retains the organization PK for workers, and separates dead-letter handling from transient buffer recovery.
PG queue serialization validation
backend/pg_queue/producer.py, backend/pg_queue/tests/test_producer.py
PG queue serialization rejects NaN and Infinity before database insertion and logs construction failures with enqueue context.

Worker startup and diagnostics

Layer / File(s) Summary
Worker directory mapping
workers/shared/enums/worker_enums_base.py, workers/tests/test_worker_enums_directory.py
WorkerType.to_directory() centralizes directory naming, including the API_DEPLOYMENT hyphenated directory, and to_import_path() derives from it.
Worker task loading and validation
workers/worker.py
Worker startup skips file loading for pluggable workers, fails on missing non-pluggable task files, dynamically loads tasks.py, and validates task registration.
Consumer diagnostics and configuration
workers/queue_backend/pg_queue/consumer.py, workers/tests/test_pg_queue_consumer.py
Poison-drop logs include queue context and normalized attempt counts. Tests cover max_attempts overrides, defaults, and invalid values.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant _send_clubbed
  participant dispatch_webhook_notification
  participant resolve_transport
  participant PGQueue
  participant Celery
  _send_clubbed->>dispatch_webhook_notification: dispatch webhook with org string id
  dispatch_webhook_notification->>resolve_transport: resolve transport
  resolve_transport-->>dispatch_webhook_notification: PG or Celery
  dispatch_webhook_notification->>PGQueue: enqueue with dispatch id
  dispatch_webhook_notification->>Celery: send task with original arguments
Loading
sequenceDiagram
  participant Worker
  participant WorkerType
  participant tasks_py
  participant CeleryRegistry
  Worker->>WorkerType: resolve worker directory
  Worker->>tasks_py: load non-pluggable tasks.py
  tasks_py->>CeleryRegistry: register tasks
  Worker->>CeleryRegistry: validate task registration
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the PG-queue auxiliary-worker migration and its two main changes: the loader fix and notification dispatch seam.
Description check ✅ Passed The description covers the changes, rationale, impact, testing, and related issues; the required breakage section is filled, although several template sections are omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/UN-3445-pg-queue-aux-workers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@muhammad-ali-e muhammad-ali-e left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Toolkit — automated multi-agent review

Ran six specialist agents (Code Reviewer, Silent-Failure Hunter, Type-Design Analyzer, Test Analyzer, Comment Analyzer, Code Simplifier) plus an independent verification pass against enqueue_task / resolve_transport / is_pg_transport.

Verdict: no blocking issues. The transport-routing seam is correct, the flag-off (Celery) path is byte-identical to the prior send_task, the permanent-vs-transient error taxonomy is sound and SENDING-guarded, and the two-org-identifier contract is well-documented and pinned by tests. Everything below is an improvement, not a defect — the two [Medium] items are the only ones I'd act on before merge.

Findings are inline. Priority: 2 Medium, 6 Low, 1 Nit.

Comment thread workers/worker.py Outdated
Comment thread backend/notification_v2/notification_dispatch.py
Comment thread backend/notification_v2/internal_api_views.py
Comment thread workers/shared/enums/worker_enums_base.py Outdated
Comment thread backend/notification_v2/notification_dispatch.py Outdated
Comment thread workers/worker.py Outdated
Comment thread backend/notification_v2/internal_api_views.py Outdated
Comment thread backend/notification_v2/internal_api_views.py Outdated
Comment thread workers/worker.py
Comment thread workers/worker.py Outdated
…istry + notification NaN/typing/comment fixes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@muhammad-ali-e

Copy link
Copy Markdown
Contributor Author

Review feedback addressed (commit 02dcdcb)

Worked through the automated review. All changes are either new-in-this-PR code or gated to the PG branch, so the flag-off (Celery) path stays byte-identical.

Addressed (9):

  • [Medium] loader empty-registry — split by `is_pluggable()`: non-pluggable now `raise RuntimeError` on empty registry / missing dir / missing `tasks.py` (terminal — no late finalize binding); pluggable stays a WARN. Celery hot path unaffected (general/executor ship a `tasks.py`).
  • [Medium] NaN → re-dispatch loop — `_json_safe` now uses `json.dumps(..., allow_nan=False)` so NaN/Infinity raise `ValueError` at the enqueue seam and funnel into `PermanentDispatchError`, instead of surfacing later as a permanent `django.db.DataError` at the JSONB insert. Except left narrow (transient DB errors still propagate). New unit test added. PG-branch only.
  • [Low] org-id typing — `_send_clubbed` / `_penalize_render_failure` / `_dispatch_group` now type `org_id: int` to match `_org_identifier(org_pk: int)` (annotation-only).
  • [Low] observability — dangling-FK org resolution bumped WARNING → `logger.error` (Sentry-routed); fail-closed-to-Celery unchanged.
  • [Low/Nit] comments + short-circuit — corrected the loader docstring (import runs registration; binding completes at `app.finalize`; skip exists because a bare `"tasks"` spec breaks relative imports), the `or ""` note, and the `PermanentDispatchError` call-site comment; replaced the materialized registry list with a short-circuiting `any(...)`; re-keyed `directory_mapping` on the enum member.

Deferred (follow-up ticket): `worker.py` loader test-gap — needs a `main()`/`name` guard refactor to make the module import-safe for unit testing.

Tests: 28 backend + 3 worker green; pre-commit clean.

@muhammad-ali-e
muhammad-ali-e marked this pull request as ready for review July 29, 2026 08:25
…N test

Hoist float("nan") out of the pytest.raises block so only enqueue_task is
under assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR extends the feature-gated PostgreSQL queue migration while preserving the existing Celery fallback.

  • Routes buffered webhook notifications through the organization-scoped transport resolver and adds explicit transient and permanent enqueue-failure handling.
  • Normalizes PG queue payloads, preserves stable task IDs during consumption, and redacts sensitive notification data from poison-message logs.
  • Updates worker task loading so pluggable workers rely on package registration while non-pluggable workers validate and load their task files directly.
  • Adds focused tests for notification routing, terminal behavior, queue consumption, payload serialization, and worker-directory mapping.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
backend/notification_v2/internal_api_views.py Routes clubbed notification dispatch through the transport seam and adds bounded transient recovery plus permanent enqueue dead-lettering.
backend/notification_v2/notification_dispatch.py Introduces organization-gated Celery/PG routing while adapting notification retry semantics for eager PG consumption.
backend/pg_queue/producer.py Rejects non-finite JSON values before insertion and logs message-construction failures with dispatch context.
workers/queue_backend/pg_queue/consumer.py Preserves producer task IDs during eager execution and redacts credentials and customer payloads from poison-drop logs.
workers/worker.py Separates pluggable package registration from validated file-based loading for non-pluggable workers.
workers/shared/enums/worker_enums_base.py Centralizes worker enum-to-directory mapping and rejects invalid directory resolution for pluggable worker types.

Sequence Diagram

sequenceDiagram
    participant Flush as Notification buffer flush
    participant Resolver as Transport resolver
    participant Celery as Celery broker
    participant PG as PG queue
    participant Worker as Notification worker
    participant Backend as Buffer status API

    Flush->>Resolver: Resolve using org string ID
    alt PG queue enabled
        Resolver-->>Flush: PG transport
        Flush->>PG: Enqueue notification task
        PG->>Worker: "apply(task_id, max_retries=0)"
    else Celery fallback
        Resolver-->>Flush: Celery transport
        Flush->>Celery: send_task with original kwargs
        Celery->>Worker: Execute notification task
    end
    Worker->>Backend: Mark dispatched or dead-letter
Loading

Reviews (9): Last reviewed commit: "Revert "UN-3893 [FIX] Make ConcurrencyMo..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/notification_v2/tests/test_send_clubbed.py (1)

75-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Permanent-error test doesn't verify the SENDING guard on the dead-letter update.

Unlike test_transient_failure_reverts_sending_rows_to_pending, this test only checks .update() kwargs, not .filter() kwargs — so the documented status=BufferStatus.SENDING.value clobber-guard on the dead-letter path is unpinned.

♻️ Proposed addition
     def test_permanent_pg_error_dead_letters(self):
         ...
             _send()
+        fkw = buf.objects.filter.call_args.kwargs
+        assert fkw["status"] == BufferStatus.SENDING.value
         # Permanent error → terminal DEAD_LETTER, no PENDING revert / refund.
         ukw = buf.objects.filter.return_value.update.call_args.kwargs
         assert ukw == {"status": BufferStatus.DEAD_LETTER.value}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/notification_v2/tests/test_send_clubbed.py` around lines 75 - 91, The
test_permanent_pg_error_dead_letters test must also verify the SENDING status
guard used when dead-lettering. Inspect buf.objects.filter.call_args.kwargs and
assert it includes status=BufferStatus.SENDING.value, while preserving the
existing update kwargs assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/notification_v2/tests/test_send_clubbed.py`:
- Around line 75-91: The test_permanent_pg_error_dead_letters test must also
verify the SENDING status guard used when dead-lettering. Inspect
buf.objects.filter.call_args.kwargs and assert it includes
status=BufferStatus.SENDING.value, while preserving the existing update kwargs
assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17fd0acc-5a58-48f3-98c2-17484311087e

📥 Commits

Reviewing files that changed from the base of the PR and between a642b44 and 3e7961c.

📒 Files selected for processing (10)
  • backend/notification_v2/internal_api_views.py
  • backend/notification_v2/notification_dispatch.py
  • backend/notification_v2/tests/__init__.py
  • backend/notification_v2/tests/test_notification_dispatch.py
  • backend/notification_v2/tests/test_send_clubbed.py
  • backend/pg_queue/producer.py
  • backend/pg_queue/tests/test_producer.py
  • workers/shared/enums/worker_enums_base.py
  • workers/tests/test_worker_enums_directory.py
  • workers/worker.py

@muhammad-ali-e muhammad-ali-e left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up automated review pass (PR Review Toolkit). One net-new finding below; everything else surfaced was observability-level, a comment nit, a test-coverage gap, or on a file outside this diff — reported to the author out-of-band rather than posted here to keep the bar high.

Comment thread backend/pg_queue/producer.py
…odeRabbit)

Assert the dead-letter update filters on status=SENDING, mirroring the
transient-revert test's guard assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@muhammad-ali-e

Copy link
Copy Markdown
Contributor Author

Addressed the CodeRabbit nitpick (commit 617b2fb): test_permanent_pg_error_dead_letters now also asserts the dead-letter .update() is guarded to rows still status=SENDING (via filter.call_args.kwargs), mirroring the transient-revert test's clobber-guard assertion. 5 tests green.

…queue_task

Move the _json_safe message construction inside the try/except so a
serialization ValueError (now reachable via allow_nan=False) gets the same
task/queue/org breadcrumb as a DB insert failure, instead of propagating
context-free on the orchestrator path. Test asserts the breadcrumb fires and
the DB insert is never reached.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@muhammad-ali-e

Copy link
Copy Markdown
Contributor Author

Addressed the follow-up review finding (commit 1eb15c6): moved the _json_safe message construction inside enqueue_task's try/except, so a serialization ValueError (now reachable via allow_nan=False) gets the same task=/queue=/org= breadcrumb as a DB-insert failure — no more context-free permanent drop on the orchestrator path. The comment's "serialization" coverage claim is now accurate. Test asserts the breadcrumb fires and the DB insert is never reached; 16 producer tests green.

…default 5) + enrich poison-drop log

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
workers/tests/test_pg_queue_consumer.py (1)

275-291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the queue context added by the log change.

The test checks poison-dropped, read_ct, and org_id, but it does not check queue. Add an assertion for the expected queue value so a regression cannot remove this diagnostic context while the test remains green.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workers/tests/test_pg_queue_consumer.py` around lines 275 - 291, The test
method test_poison_log_enriched_with_org_and_read_ct must also verify the queue
context in the poison-drop log. Add an assertion that caplog.text contains the
expected queue value for the consumer initialized with ["q"], while preserving
the existing poison-dropped, read_ct, and org_id assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@workers/tests/test_pg_queue_consumer.py`:
- Around line 275-291: The test method
test_poison_log_enriched_with_org_and_read_ct must also verify the queue context
in the poison-drop log. Add an assertion that caplog.text contains the expected
queue value for the consumer initialized with ["q"], while preserving the
existing poison-dropped, read_ct, and org_id assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e6d8bfd-e63f-4ae0-8299-e93f86f4790d

📥 Commits

Reviewing files that changed from the base of the PR and between 1eb15c6 and 533cf78.

📒 Files selected for processing (2)
  • workers/queue_backend/pg_queue/consumer.py
  • workers/tests/test_pg_queue_consumer.py

Comment thread backend/notification_v2/notification_dispatch.py Outdated
muhammad-ali-e and others added 2 commits July 31, 2026 16:18
…o terminal failure doesn't redeliver

On the PG branch of dispatch_webhook_notification, kwargs were forwarded verbatim
including raise_on_final_failure=True. On PG that re-raise means the OPPOSITE of
Celery: the worker already marks the buffers DEAD_LETTER, so re-raising only leaves
the row for vt-expiry redelivery — re-POSTing the subscriber up to max_attempts
times and tripping a false poison-drop. Override it to False on the PG branch only
(the Celery branch keeps kwargs verbatim — byte-identical) so a terminal failure
returns None -> the consumer acks -> single POST, matching Celery's external behaviour.

Also: the poison-drop log test now asserts the source queue is surfaced (CodeRabbit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@vishnuszipstack vishnuszipstack left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Toolkit — consolidated findings

Ran the Code Reviewer, Silent Failure Hunter, Type Design Analyzer, PR Test Analyzer, Comment Analyzer and Code Simplifier agents over the 12 changed files, then verified each surviving finding against the source. Findings already raised on this PR are omitted.

Two blockers before the flag can be turned on for any org:

  1. The raise_on_final_failure=False override is unreachable for any subscriber with max_retries >= 1 — verified by running the consumer's exact task.apply(throw=True) shape. The buffers are never dead-lettered and the subscriber is re-POSTed on every vt-expiry redelivery.
  2. Nothing in this repo drains the PG notifications queue — none of the seven pg-queue-consumer compose services polls it, and run-worker.sh has no notification PG role. Flag-on means every buffered webhook for that org is enqueued and silently lost.

Also flagged: an unconditional attempt-refund that makes the dispatch cap unreachable, subscriber credentials reaching Sentry and PG-at-rest via the poison-drop path, an empty-registry guard whose stated rationale is contradicted by Celery's auto-finalize, and several test/comment accuracy gaps.

Nice work on the seam tests themselves — they assert forwarded payloads and negatives rather than mock tautologies, and test_permanent_pg_error_dead_letters proving the absence of a refund via exact-dict equality is the right instinct.

Comment thread backend/notification_v2/notification_dispatch.py Outdated
Comment thread backend/notification_v2/notification_dispatch.py Outdated
Comment thread backend/notification_v2/notification_dispatch.py Outdated
Comment thread backend/notification_v2/notification_dispatch.py Outdated
Comment thread backend/notification_v2/internal_api_views.py
Comment thread workers/worker.py Outdated
Comment thread workers/shared/enums/worker_enums_base.py
Comment thread backend/notification_v2/tests/test_send_clubbed.py Outdated
Comment thread workers/tests/test_worker_enums_directory.py Outdated
Comment thread workers/tests/test_worker_enums_directory.py
muhammad-ali-e and others added 3 commits August 3, 2026 21:07
…l-branch, task_id passthrough, credential redaction

All 14 review findings. Flag-off/Celery stays byte-identical throughout.

HIGH - raise_on_final_failure override was unreachable for max_retries>=1: under the
consumer's eager task.apply(throw=True) request.retries is always 0, so the in-task
retry guard fires on the FIRST failure and Retry propagates out of apply() - the
terminal branch never ran, buffers were never dead-lettered, and the row was left for
vt-expiry redelivery (re-POSTing the subscriber each time). Also force max_retries=0
on the PG branch so the terminal branch is reached. New workers-side test drives the
REAL task through apply() and asserts one POST + one DEAD_LETTER mark, and pins the
old broken shape as a regression guard.

HIGH - docstring claimed a PG notifications consumer drains the queue; none exists in
this repo. Reworded as an explicit deployment prerequisite (flag must stay off until
one is deployed, else buffered webhooks are enqueued and silently lost).

Idempotency - the consumer now passes the payload's stable task_id to task.apply().
Without it Celery mints a fresh uuid per delivery, so every guard keyed on request.id
(fan-out claims, generation_task_id, task-complete markers) deduped nothing across a
redelivery.

Security - the poison drop logged the full payload, leaking subscriber Authorization /
API-key headers and customer webhook bodies to stdout and Sentry. Added
_redact_payload (masks auth-ish keys, summarises the body); routing metadata kept.

Also: bounded the transient attempt-refund so NOTIFICATION_MAX_DISPATCH_ATTEMPTS is
reachable (an unconditional refund made termination impossible); DispatchResult now
carries the transport so the ramp metric can compare PG vs Celery; to_directory
rejects pluggable types; corrected the empty-registry rationale (app.tasks
auto-finalizes) and the stale link_error premises; fixed the conftest defect that
forced a leaf test to mutate global Celery state; parametrized NaN/inf coverage over
args/kwargs/fairness and the whole WorkerType->directory mapping; asserted the refund
and buffer_row_ids that comments claimed but tests never checked.

No regression: workers suite 208 failed / 1239 passed before, 208 failed / 1243 passed
after (the 208 are pre-existing env failures - no DB, no prometheus_client).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…survives a set env var and duplicate module copies

StateStore guards every set/get/clear with `cls.mode == ConcurrencyMode.THREAD` and
raises RuntimeError("Unknown concurrency mode") otherwise. As a bare Enum that guard
had two runtime failure modes, both invisible until production:

1. mode is read as os.environ.get("CONCURRENCY_MODE", ConcurrencyMode.THREAD), so
   SETTING the variable yields a plain str — and "thread" == ConcurrencyMode.THREAD is
   False for a bare Enum. Even the CORRECT value took the backend down.
2. The module exists in three copies (backend/utils, workers/shared/utils,
   workers/shared/infrastructure) and can be imported under more than one path in a
   merged OSS+cloud tree, producing two distinct ConcurrencyMode CLASSES. Members of
   different Enum classes never compare equal, so the guard raised on every call.

(2) is what failed ~70 cloud integration tests across unrelated suites
(test_pg_finalization_fixes, dashboard_metrics, manual_review_v2, statistics_service):
the same OSS tests pass in OSS CI and cloud main is green — it only appeared once the
cloud plugins were merged into the OSS tree.

StrEnum members ARE strings, so both cases now compare by value and the defining
class's identity stops mattering. Applied to all three copies, with a regression test
that loads two copies under different names, asserts they are genuinely distinct
classes, and asserts their members still compare equal — plus the env-var round trip.

No behaviour change otherwise: the comparison is strictly more permissive, and the
workers suite is unchanged at 208 failed / 1243 passed (pre-existing env failures:
no DB, no prometheus_client).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e guard survives a set env var and duplicate module copies"

This reverts 2a39e26. Two reasons, both raised by review:

OUT OF SCOPE. The bug is a pre-existing one in main with no connection to PG queue,
the flag, or the aux workers. It was picked up while diagnosing why cloud CI was red
and should never have been fixed inside this epic's branch: it muddies a PR whose
whole promise is "PG-only, flag-gated", and it touches StateStore — multi-tenant org
scoping on the request path, used by the flag-off Celery flow in staging and
production. "Probably safe" is not the bar for that path.

THE STATED CAUSE DOES NOT HOLD. The commit justified itself with "duplicate module
identity produces two ConcurrencyMode classes". On checking: both repos import it
under exactly one path (`from utils.local_context import`, 25 sites), cloud ships no
overlay copy, and CONCURRENCY_MODE is set nowhere (unfiltered grep, both repos). So
within a single module cls.mode and ConcurrencyMode.THREAD are the same class and
should compare equal — the raise is unexplained. Shipping the change would have
masked the symptom without anyone understanding the cause.

Verified separately that this epic did NOT introduce the bug: our branch never
touched these files, the three copies predate it (2024-03 / 2025-10), and cloud CI
resolves refs/heads/main at run time so it never checks out our OSS branch at all.

Tracked in UN-3893 for the owner of the merge/CI setup; cloud #1688 CI stays red on
it as a blocked-by rather than something this PR introduced or can fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
e2e-api-deployment e2e 3 0 0 0 16.7
e2e-coowners e2e 1 0 0 0 1.4
e2e-etl e2e 1 0 0 0 8.2
e2e-login e2e 2 0 0 0 1.2
e2e-prompt-studio e2e 1 0 0 0 4.3
e2e-smoke e2e 2 0 0 0 1.0
e2e-workflow e2e 1 0 0 0 17.9
integration-backend integration 205 0 0 26 42.9
integration-connectors integration 1 0 0 7 8.4
integration-workers integration 140 0 0 1 52.0
unit-backend unit 299 0 0 1 36.4
unit-connectors unit 63 0 0 0 9.7
unit-core unit 33 0 0 0 1.3
unit-platform-service unit 15 0 0 0 2.6
unit-rig unit 109 0 0 0 5.3
unit-sdk1 unit 480 0 0 0 23.1
unit-workers unit 1335 0 0 1 94.5
TOTAL 2691 0 0 36 327.0

Critical paths

⚠️ Critical paths not yet covered

  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend
  • workflow-author — covered by integration-backend
  • co-owner-manage — covered by integration-backend, e2e-coowners
  • workflow-create-execute — covered by e2e-workflow
  • api-deployment-provision — covered by integration-backend
  • api-deployment-auth — covered by integration-backend
  • api-deployment-run — covered by e2e-api-deployment
  • prompt-studio-author — covered by integration-backend
  • prompt-studio-fetch-response — covered by e2e-prompt-studio
  • connector-register-test — covered by integration-backend
  • pipeline-etl-execute — covered by e2e-etl
  • usage-aggregate-read — covered by integration-backend
  • usage-token-tracking — covered by e2e-api-deployment
  • callback-result-delivery — covered by e2e-api-deployment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants