Skip to content

feat(nzb-postproc): publish typed terminal failure codes - #151

Merged
thedancingdeveloper merged 4 commits into
mainfrom
feat/typed-failure-codes
Sep 27, 2026
Merged

thedancingdeveloper merged 4 commits into
mainfrom
feat/typed-failure-codes

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Collaborator

Ports typed terminal failure codes from MrVampy/rustnzb (e0870d8): persist an engine-owned JobFailureCode with 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-admission was deleted on merge of #149. Same branch/commits, now correctly targeting main with the merge conflicts resolved: server.rs/models.rs/db.rs taken as the #149 superset, and the failure_code threading re-applied onto #148's multi-recovery-set pipeline.rs.)

What it adds

  • JobFailureCode enum (stable snake_case wire values, Display+FromStr).
  • Post-proc assigns codes from causal stage state; typed ArchivePasswordRequired replaces the anyhow::bail!("archive is password-protected") prose.
  • HistoryEntry.failure_code persisted (DB migration v12, backfills failed rows to download_failed); history_insert enforces Failed⇔code-present.
  • queue_admission_observe consults the authoritative history row first and reports the code via QueueAdmissionState::History; exposed on the history API.

Tests

JobFailureCode roundtrip; typed password error; run_extract_stage/run_pipeline code 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

thedancingdeveloper and others added 4 commits September 27, 2026 09:53
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
@thedancingdeveloper
thedancingdeveloper merged commit fbb2239 into main Sep 27, 2026
11 checks passed
@thedancingdeveloper
thedancingdeveloper deleted the feat/typed-failure-codes branch September 27, 2026 23:24
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