feat(nzb-postproc): publish typed terminal failure codes - #151
Merged
Merged
Conversation
Add a durable, at-most-once admission path for queue adds so a caller that
retries an ambiguous `POST /api/queue/add` cannot create a duplicate job.
- New `queue_admissions` table (DB migration v11) keyed by idempotency key,
binding it to a payload digest and job id. It intentionally has no foreign
key to queue/history, so it stays authoritative after a job moves or bounded
history is pruned.
- `Idempotency-Key` request header (parsed/validated, path-safe, <=128 bytes)
and SHA-256 payload digest (`apps/rustnzb/src/admissions.rs`), plus
`GET /api/queue/admissions/{key}` to resolve one admission and its current
engine location (queue / history / unobserved).
- `Database::queue_admit` (transactional bind + queue insert) and
`queue_admission_observe`; `QueueManager::add_job_idempotent` /
`queue_admission_observe`, with `add_job` refactored so the shared activation
path is `activate_admitted_job`.
- New `NzbError::AdmissionConflict` -> HTTP 409 `admission_conflict` when a key
is reused with a different payload. A keyed add must carry exactly one NZB.
Ported from MrVampy/rustnzb (fe7c35d), adapted to main: migration renumbered
v9 -> v11 (main's v9/v10 are retry_data / damage_ledger); the fork's
flake.nix / workspace version bumps are excluded; digests use `hex::encode`
because main is on sha2 0.11 (whose output no longer implements LowerHex);
and the integration test now completes first-run auth setup and sends a bearer
token, since main gates `/api` behind auth middleware.
Tests: db `queue_admission_*` unit tests (replay/conflict, reopen/outlive);
`admissions` key + digest unit tests; and an end-to-end
`idempotent_admission` suite (exact replay returns one job, conflicting
payload 409s, keyed multi-payload upload 400s). `cargo test` across nzb-core,
nzb-web and rustnzb all pass; clippy/fmt clean.
Co-Authored-By: MrVampy <4302946+MrVampy@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persist an engine-owned failure code with each terminal history row instead
of requiring consumers to parse human-readable diagnostic prose, and make the
durable terminal row authoritative over a job's lingering queue view.
- New `JobFailureCode` enum (`nzb-core`) with stable snake_case wire values
(Display + FromStr): articles_unavailable, repair_failed,
archive_password_required, archive_invalid, storage_unavailable,
download_failed.
- Post-processing assigns codes from causal stage state: `run_pipeline`
threads a `failure_code` (articles-unavailable, repair-failed) and
`run_extract_stage` returns a typed code, including a typed
`ArchivePasswordRequired` error (replacing the `anyhow::bail!("archive is
password-protected")` prose that consumers had to string-match).
- Persist `HistoryEntry.failure_code` (DB migration v12 adds the column and
backfills existing failed rows to `download_failed`); `history_insert`
enforces the invariant that a Failed row carries exactly one code and a
non-Failed row carries none.
- `queue_manager` tracks the code in memory across pause/resume/clear and
writes it on terminal transition; `queue_admission_observe` now consults the
authoritative history row before the queue view, and reports the code via
`QueueAdmissionState::History`. Exposed on the history API response.
Ported from MrVampy/rustnzb (e0870d8), adapted to main: DB migration
renumbered v10 -> v12 (main's v10/v11 are damage_ledger / queue_admissions);
history SQL column indices adjusted for main's `retry_data` column; the
pipeline changes reapplied onto main's `run_pipeline_with_cleanup` structure;
and `QueueAdmissionState::History` extends the variant added in the idempotent
queue-admission change this branch is stacked on. Fork flake/workspace bumps
excluded.
Tests: JobFailureCode wire-value roundtrip; typed ArchivePasswordRequired
survives as a downcastable error; run_extract_stage / run_pipeline code
assignment; DB migration backfill, history invariant, and terminal-history
authority over the queue view; queue_manager terminal-code persistence
(ArchiveInvalid / DownloadFailed). Full nzb-core / nzb-postproc / nzb-web /
rustnzb suites pass; clippy/fmt clean.
Co-Authored-By: MrVampy <4302946+MrVampy@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The desktop app depends on `rustnzb`/`nzb-web` by path; the new dependencies added by this change must be reflected in desktop/src-tauri/Cargo.lock so the `desktop` CI job (built with --locked) accepts it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # apps/rustnzb/src/server.rs # crates/nzb-core/src/db.rs # crates/nzb-core/src/models.rs # crates/nzb-postproc/src/pipeline.rs
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.
Ports typed terminal failure codes from
MrVampy/rustnzb(e0870d8): persist an engine-ownedJobFailureCodewith each terminal history row instead of parsing diagnostic prose, and make the durable terminal row authoritative over the queue view.(Supersedes #150, which GitHub auto-closed when its stacked base branch
feat/idempotent-queue-admissionwas deleted on merge of #149. Same branch/commits, now correctly targetingmainwith the merge conflicts resolved:server.rs/models.rs/db.rstaken as the #149 superset, and thefailure_codethreading re-applied onto #148's multi-recovery-setpipeline.rs.)What it adds
JobFailureCodeenum (stable snake_case wire values, Display+FromStr).ArchivePasswordRequiredreplaces theanyhow::bail!("archive is password-protected")prose.HistoryEntry.failure_codepersisted (DB migration v12, backfills failed rows todownload_failed);history_insertenforces Failed⇔code-present.queue_admission_observeconsults the authoritative history row first and reports the code viaQueueAdmissionState::History; exposed on the history API.Tests
JobFailureCoderoundtrip; typed password error;run_extract_stage/run_pipelinecode assignment on the multi-set pipeline; DB migration backfill + history invariant + terminal-history authority; queue_manager terminal-code persistence. Full nzb-core/nzb-postproc/nzb-web/rustnzb suites pass; clippy/fmt clean.Co-Authored-By: MrVampy 4302946+MrVampy@users.noreply.github.com
🤖 Generated with Claude Code