Skip to content

fix(admin): env filter and honest count on DLQ replay (alt to #76)#85

Closed
ravidsrk wants to merge 2 commits into
dodopayments:mainfrom
ravidsrk:ravidsrk/cs-56-dlq-env-filter
Closed

fix(admin): env filter and honest count on DLQ replay (alt to #76)#85
ravidsrk wants to merge 2 commits into
dodopayments:mainfrom
ravidsrk:ravidsrk/cs-56-dlq-env-filter

Conversation

@ravidsrk

Copy link
Copy Markdown

This PR is an alternative implementation of #56, opened alongside #76 (thanks @soufian3hm).

POST /admin/api/dlq/{job_id}/replay gains an optional ?environment=<slug> filter (404 on an unknown slug before any row moves, mirroring the CLI --env), and the response reports the real moved count instead of a hardcoded 1dlq::replay returns the count and the CLI is adapted. Three failing-first HTTP tests over testcontainers cover it (red at baseline, green at head, negative control confirmed).

One note from reviewing #76: its hand-edited OpenAPI YAML adds nullable: true, which utoipa does not emit for Option IntoParams query fields — regenerating dirties the artifacts the PR asks to keep byte-identical. This PR regenerates them via pnpm generate.

ravidsrk added 2 commits July 16, 2026 00:13
dlq::replay collapsed the moved-row count to a bool, hiding how many
rows an unscoped id match actually moved. Callers now see the count.
POST /admin/api/dlq/{job_id}/replay accepts an optional
?environment=<slug> filter mirroring the CLI's --env. The same job id
can be parked in more than one environment under the (environment_id,
id) key, and the unscoped handler replayed every copy while reporting
a hardcoded replayed: 1. Unfiltered replay keeps the documented
cross-environment reach but now reports the real moved count. Unknown
slugs 404 before any row moves.

Closes dodopayments#56
@ravidsrk

Copy link
Copy Markdown
Author

@claude review this PR. @codex review this PR.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two bugs in POST /admin/api/dlq/{job_id}/replay: it was always returning replayed: 1 regardless of how many DLQ rows were moved, and it had no way to scope a replay to a single environment. Both are corrected cleanly.

  • dlq::replay now returns u64 (the real moved-row count) instead of bool; the CLI adapts with a > 0 comparison and the handler casts to i64 for the response, consistent with the pre-existing replay_all pattern.
  • An optional ?environment=<slug> query parameter is added; the slug is resolved to a UUID before the DELETE/INSERT CTE executes, so an unknown slug returns 404 without touching any row.
  • Three failing-first HTTP integration tests over real Postgres cover the scoped replay, the unscoped multi-row count, and the unknown-slug 404-before-move guarantee.

Confidence Score: 4/5

Safe to merge — the environment-filter and honest-count changes are correct and thoroughly tested against real Postgres.

The core logic is solid: slug resolution happens before the atomic DELETE/INSERT CTE, the replayed count is accurate, and the CLI adapter is a trivial one-character fix. The only gap is a doc-level inconsistency in the utoipa 404 annotation.

The utoipa 404 annotation example field in server/src/api/admin.rs could be updated to cover the new no such environment case.

Important Files Changed

Filename Overview
server/src/api/admin.rs Adds optional ?environment query param to replay_dead_letter; resolves slug to UUID before any DB mutation (correct fail-before-move order); returns real row count instead of hardcoded 1.
server/src/dlq.rs Signature of replay changed from anyhow::Result to anyhow::Result, eliminating the moved > 0 collapse and exposing the real count. SQL CTE logic is unchanged.
server/src/main.rs CLI updated to compare dlq::replay result against > 0 instead of treating it as a bool; clean one-line adaptation.
server/tests/redteam_dlq_env_scope.rs Three new HTTP integration tests added over real Postgres covering scoped replay, unscoped multi-row count, and unknown-slug 404-before-move.
docs/openapi/chimely.yaml Generated via pnpm generate; adds the optional environment query param and updates the 404 description.
packages/client/src/generated/api.d.ts Generated TypeScript types updated to reflect the optional environment query parameter and revised 404 description.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Handler as replay_dead_letter
    participant DB as Postgres

    Client->>Handler: "POST /admin/api/dlq/{job_id}/replay?environment=slug"
    Handler->>Handler: parse_typeid(job_id) - 404 on bad format
    alt environment param present
        Handler->>DB: "SELECT id FROM environments WHERE slug = $1"
        DB-->>Handler: Some(env_uuid) or None
        alt slug not found
            Handler-->>Client: 404 no such environment
        end
    end
    Handler->>DB: WITH revived AS DELETE FROM dead_letters INSERT INTO jobs
    DB-->>Handler: rows_affected u64
    alt "rows_affected == 0"
        Handler-->>Client: 404 no such parked job
    else
        Handler-->>Client: 200 replayed count
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant Handler as replay_dead_letter
    participant DB as Postgres

    Client->>Handler: "POST /admin/api/dlq/{job_id}/replay?environment=slug"
    Handler->>Handler: parse_typeid(job_id) - 404 on bad format
    alt environment param present
        Handler->>DB: "SELECT id FROM environments WHERE slug = $1"
        DB-->>Handler: Some(env_uuid) or None
        alt slug not found
            Handler-->>Client: 404 no such environment
        end
    end
    Handler->>DB: WITH revived AS DELETE FROM dead_letters INSERT INTO jobs
    DB-->>Handler: rows_affected u64
    alt "rows_affected == 0"
        Handler-->>Client: 404 no such parked job
    else
        Handler-->>Client: 200 replayed count
    end
Loading

Comments Outside Diff (1)

  1. server/src/api/admin.rs, line 1298-1303 (link)

    P2 404 example covers only one of the two cases

    The description now says "No such parked job, or no such environment," but the example field still only shows the "no such parked job" message. A caller consulting the generated docs or the TypeScript types has no example of what the "no such environment" 404 looks like, even though the two cases produce distinct messages. Adding a second example (via examples) or updating the single one to something generic would align the annotation with the extended description.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(admin): env filter on DLQ replay en..." | Re-trigger Greptile

@ravidsrk

Copy link
Copy Markdown
Author

Closing in deference to #76, per the one-PR-per-issue preference expressed in #84. The findings from this implementation are already commented on #76, and the branch stays available if any of the tests or fixes are worth cherry-picking.

@ravidsrk ravidsrk closed this Jul 17, 2026
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.

1 participant