fix(admin): env filter and honest count on DLQ replay#80
Conversation
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
|
| Filename | Overview |
|---|---|
| server/src/dlq.rs | Return type of replay changed from bool to u64 (rows affected); logic unchanged. Clean and correct. |
| server/src/api/admin.rs | Adds AdminReplayFilter query param and slug resolution; replayed count is now honest. Minor: u64 as i64 cast is lossy in theory; 404 example only covers one of two messages. |
| server/src/main.rs | CLI call site updated to > 0 comparison following the bool → u64 return-type change. |
| server/tests/redteam_dlq_env_scope.rs | Three new integration tests covering filtered replay, cross-env count accuracy, and unknown-slug 404 — solid coverage of the new surface. |
| docs/openapi/chimely.yaml | Generated spec regenerated correctly with new environment query parameter and updated 404 description. |
| packages/client/src/generated/api.d.ts | Generated TypeScript types updated in sync with the spec — environment query parameter added to adminReplayDeadLetter. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant C as Caller
participant H as replay_dead_letter
participant D as dlq::environment_by_slug
participant R as dlq::replay
C->>H: "POST /admin/api/dlq/{job_id}/replay?environment=slug"
H->>H: parse_typeid(job_id)
alt invalid job_id
H-->>C: 404 no such parked job
end
alt environment param present
H->>D: environment_by_slug(pool, slug)
D-->>H: Some(uuid) or None
alt slug not found
H-->>C: 404 no such environment
end
end
H->>R: replay(pool, id, env_uuid)
R-->>H: rows_affected u64
alt "rows == 0"
H-->>C: 404 no such parked job
else rows greater than 0
H-->>C: 200 replayed N
end
%%{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 C as Caller
participant H as replay_dead_letter
participant D as dlq::environment_by_slug
participant R as dlq::replay
C->>H: "POST /admin/api/dlq/{job_id}/replay?environment=slug"
H->>H: parse_typeid(job_id)
alt invalid job_id
H-->>C: 404 no such parked job
end
alt environment param present
H->>D: environment_by_slug(pool, slug)
D-->>H: Some(uuid) or None
alt slug not found
H-->>C: 404 no such environment
end
end
H->>R: replay(pool, id, env_uuid)
R-->>H: rows_affected u64
alt "rows == 0"
H-->>C: 404 no such parked job
else rows greater than 0
H-->>C: 200 replayed N
end
Reviews (1): Last reviewed commit: "feat(admin): env filter on DLQ replay en..." | Re-trigger Greptile
| .await | ||
| .map_err(ApiError::from)?; | ||
| if !replayed { | ||
| .map_err(ApiError::from)? as i64; |
There was a problem hiding this comment.
rows_affected() returns u64, and the bare as i64 cast silently wraps to a negative value if the count ever exceeds i64::MAX. In DLQ replay that number is bounded by the number of environments a single job can be parked in, so it is practically zero risk — but a prefer-safe path would be u64::try_into().unwrap_or(i64::MAX) or simply changing AdminReplayResult.replayed to u64 (the OpenAPI schema renders it as an integer either way).
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!
| ), | ||
| ( | ||
| status = 404, | ||
| description = "No such parked job.", | ||
| description = "No such parked job, or no such environment.", | ||
| body = crate::api::contract::Error, | ||
| example = json!({"error": {"code": "not_found", "message": "no such parked job"}}), | ||
| ), |
There was a problem hiding this comment.
404 example only covers one of two distinct cases
The utoipa example on the 404 response hard-codes "no such parked job", but this status is also returned when the environment slug is unknown (with "no such environment"). An API consumer looking at the generated docs has no way to distinguish the two. Adding a second named example (utoipa supports examples with keys) would let callers tell apart a bad slug from a missing job.
This PR adds an optional environment filter to the admin DLQ replay endpoint and makes the replayed count honest.
POST /admin/api/dlq/{job_id}/replaycalleddlq::replay(pool, id, None), matching the bare job id across environments. Because(environment_id, id)is the key, the same job id parked in two environments would replay both while the response reportedreplayed: 1. Not attacker-reachable (job ids are server-minted UUIDv7), but the count lied and there was no way to pin the replay.Changes:
dlq::replayreturns the moved-row count instead of a bool, so both the CLI and the endpoint report the real number.?environment=<slug>, mirroring the CLI's--env, resolved viadlq::environment_by_slug. An unknown slug is a 404.redteam_dlq_env_scopecovers the new surface: a filtered replay moves only the pinned environment's row, an unfiltered replay reportsreplayed: 2for a job id parked in two environments, and an unknown slug 404s.Fixes #56