diff --git a/docs/backend-roadmap.md b/docs/backend-roadmap.md index 13646733..318835b4 100644 --- a/docs/backend-roadmap.md +++ b/docs/backend-roadmap.md @@ -286,7 +286,7 @@ queue, cache, and repository ports defined in section 1. ### 5.2. Queue adapter (Apalis) -- [ ] 5.2.1. Implement `RouteQueue` using Apalis with PostgreSQL backend, +- [x] 5.2.1. Implement `RouteQueue` using Apalis with PostgreSQL backend, replacing the current stub adapter. - [ ] 5.2.2. Define job structs for `GenerateRouteJob` and `EnrichmentJob`. - [ ] 5.2.3. Implement retry policies with exponential backoff and dead-letter diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 8d9f4dad..69ff3d1f 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -637,7 +637,7 @@ The hexagonal boundary is enforced via visibility: |--------------------------------------|---------------------------|--------------------------------------------| | `ApalisRouteQueue
` | `pub` | Public adapter for domain use | | `ApalisPostgresProvider` | `pub` | Production `QueueProvider` implementation | -| `GenericApalisRouteQueue
` | Internal; not re-exported | Generic adapter implementation | +| `GenericApalisRouteQueue
` | `pub` | Generic adapter and BDD harness seam | | `QueueProvider` | `pub(crate)` | Test seam for provider abstraction | | `test_helpers::FakeQueueProvider` | `pub(crate)` (test-only) | In-memory test double | | `test_helpers::FailingQueueProvider` | `pub(crate)` (test-only) | Always-failing test double | @@ -659,10 +659,10 @@ Public production API: Implementation details within `outbound::queue`: -- `GenericApalisRouteQueue
` – Declared `pub` inside the private - `apalis_route_queue` module, but not re-exported at crate root. It +- `GenericApalisRouteQueue
` – Re-exported beside the production alias + because the BDD harness constructs the adapter with a test provider. It parameterises the adapter over the queue provider type `Q` so tests can - substitute doubles. + substitute doubles, while production code should prefer `ApalisRouteQueue
`. - `QueueProvider` – Declared `pub(crate)` inside the private `apalis_route_queue` module. Defines `async fn push_job(&self, payload: serde_json::Value) -> Result<(), JobDispatchError>` as the test diff --git a/docs/execplans/backend-5-2-1-apalis-route-queue.md b/docs/execplans/backend-5-2-1-apalis-route-queue.md index 871b6753..830f7346 100644 --- a/docs/execplans/backend-5-2-1-apalis-route-queue.md +++ b/docs/execplans/backend-5-2-1-apalis-route-queue.md @@ -1,888 +1,590 @@ -# Implement the Apalis-backed `RouteQueue` adapter (roadmap 5.2.1) +# Validate and close the Apalis-backed `RouteQueue` adapter (backend 5.2.1) This ExecPlan (execution plan) is a living document. The sections `Constraints`, `Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds. -Status: IN PROGRESS +Status: COMPLETE This plan covers roadmap item 5.2.1 only: `Implement RouteQueue using Apalis with PostgreSQL backend, replacing the current stub adapter.` +No implementation work may begin from this plan until the plan is explicitly +approved. Approval authorizes the implementation and closure milestones below; +it does not authorize later queue tasks such as job-struct modelling, retry +policy, trace propagation, worker deployment, or route-submission dispatch. + ## Purpose / big picture -Today `backend/src/domain/ports/route_queue.rs` defines the `RouteQueue` port -with a single async method `enqueue`, but -`backend/src/outbound/queue/mod.rs` only provides `StubRouteQueue`, which -discards every job with a one-time warning. That keeps the crate compiling, but -means no enqueued work is persisted or available for downstream processing. - -After this change, the backend will have a real Apalis-backed driven adapter -for the `RouteQueue` port, built on `apalis-postgres` with PostgreSQL storage. -The adapter will accept typed plan payloads, serialize them into the Apalis job -table, and surface domain-owned `JobDispatchError` variants when the queue -infrastructure is unavailable or rejects a job. The repository will gain -focused unit and behavioural test coverage proving enqueue success, queue -unavailability errors, and rejected-job error mapping against a real embedded -PostgreSQL instance via `pg-embedded-setup-unpriv`. - -Because no runtime service currently dispatches to `RouteQueue` (the dispatch -points in `backend/src/domain/route_submission/mod.rs` are gated behind -`TODO(#276)`), this work intentionally stops at the adapter boundary. -Production request-path queue dispatch, worker consumption, job struct -definitions (roadmap 5.2.2), retry policies (5.2.3), and trace propagation -(5.2.4) remain later roadmap items. - -Observable success criteria: - -- `backend::outbound::queue` exports a real Apalis-backed `RouteQueue` - implementation instead of only a stub. -- The adapter uses `apalis-postgres` with `PostgresStorage` for job - persistence and maps connection or dispatch failures to - `JobDispatchError::Unavailable`. -- Job serialization failures (if the plan type cannot be encoded) map to - `JobDispatchError::Rejected`. -- `rstest` coverage proves happy, unhappy, and edge cases for the adapter. -- Behavioural (BDD) coverage via `rstest-bdd` proves adapter behaviour against - a real embedded PostgreSQL instance, not a handwritten mock. -- `docs/wildside-backend-architecture.md` records the scope decision that - 5.2.1 delivers the driven adapter but does not yet enable request-path queue - dispatch or worker consumption. -- `docs/backend-roadmap.md` marks 5.2.1 done only after all required gates - pass. -- `make check-fmt`, `make lint`, and `make test` pass with logs retained. +Wildside's route generation work needs a real queue adapter so route jobs can +be persisted for background processing instead of disappearing through a stub. +The domain already owns the queue contract through the `RouteQueue` port, and +the adapter boundary must keep Apalis, SQLx, and PostgreSQL details out of the +domain. + +The repository currently contains an Apalis/PostgreSQL queue adapter and tests +from prior work, but `docs/backend-roadmap.md` still lists 5.2.1 as open and +the existing plan history is stale. This plan therefore treats 5.2.1 as a +validation-and-closure activity: confirm the adapter still satisfies the +roadmap item on the current base, repair any defects found within the +tolerances below, run the required gates, record CodeRabbit review outcomes, +and mark the roadmap item done only after evidence is clean. + +Observable success means: + +- `backend::outbound::queue::ApalisRouteQueue` persists queue payloads through + Apalis PostgreSQL storage. +- `backend::domain::ports::RouteQueue` remains the only domain-facing queue + contract. +- Unit tests using `rstest` cover happy and unhappy adapter paths. +- Behavioural tests using `rstest-bdd` cover PostgreSQL-backed queue + persistence and failure behaviour. +- Documentation accurately explains the adapter scope, the SQLx pool used by + Apalis, and the fact that worker consumption remains future work. +- `make check-fmt`, `make lint`, and `make test` pass. +- CodeRabbit review has been run after each major milestone, and all concerns + have either been fixed or explicitly documented as not applicable. +- `docs/backend-roadmap.md` marks 5.2.1 done only after the evidence above is + available. ## Constraints -- Scope is roadmap item 5.2.1 only. Do not mark 5.2.2, 5.2.3, 5.2.4, or - 5.3.x done as part of this change unless the implementation is explicitly - widened and re-approved. -- Preserve hexagonal boundaries: - - `backend/src/domain/ports/route_queue.rs` remains the domain-owned - contract. The `RouteQueue` trait and `JobDispatchError` enum must not be - widened to accommodate Apalis-specific concerns. - - Apalis client, storage, and serialization details live under - `backend/src/outbound/queue/*`. - - Inbound adapters and domain services must not import Apalis types. -- Do not add queue dispatch calls to the `RouteSubmissionServiceImpl` or any - HTTP handler in this task. The `TODO(#276)` markers at - `backend/src/domain/route_submission/mod.rs` lines 241 and 295 are - intentionally left for a later integration step. -- Do not implement worker consumption (`Monitor`, `WorkerBuilder`) in this - task. The adapter covers the "push" side only; the "pull" side belongs to - roadmap items 5.2.2 through 5.3.1. -- Replace the production stub adapter with a real driven adapter, but preserve - lightweight in-memory or fixture doubles for tests that do not need - PostgreSQL. -- Use `apalis-postgres` (from the `apalis` 1.x release candidate family) for - PostgreSQL-backed job storage as specified by the roadmap. -- Use `rstest` for focused unit coverage and `rstest-bdd` for behavioural - coverage. -- Ensure the existing Postgres-backed suites still run through - `pg-embedded-setup-unpriv` as part of the full `make test` gate. -- Keep files under 400 lines by splitting queue code into coherent modules if - needed. -- New public Rust APIs must carry Rustdoc comments and examples that follow - `docs/rust-doctest-dry-guide.md`. -- Update documentation in en-GB-oxendict style. -- Follow the adapter pattern established by `RedisRouteCache` in - `backend/src/outbound/cache/redis_route_cache.rs`: use a - `ConnectionProvider`-style trait to abstract the storage backend, keep serde - bounds on the adapter (not the port), and provide a `FakeProvider` for unit - tests. +- Do not start implementation until this DRAFT plan has explicit approval. +- Keep scope to backend roadmap item 5.2.1. Do not implement or mark done + 5.2.2, 5.2.3, 5.2.4, 5.3.1, or any route-submission dispatch item. +- Preserve hexagonal architecture. Domain and inbound modules must not import + Apalis, SQLx, or concrete outbound queue types. +- Keep `RouteQueue` and `JobDispatchError` domain-owned. Do not widen the port + just to expose Apalis details. +- Keep worker consumption out of scope. No `WorkerBuilder`, `Monitor`, retry + policy, dead-letter queue, or worker deployment work belongs in this plan. +- Keep request-path dispatch out of scope unless the plan is revised and + re-approved. The current `TODO(#276)` queue-dispatch markers in + `backend/src/domain/route_submission/mod.rs` remain later work. +- Retain `StubRouteQueue` for tests and non-PostgreSQL development paths if it + is still used. +- Use `apalis-postgres` with PostgreSQL storage as required by the roadmap. +- Use `rstest` for focused unit tests and `rstest-bdd` for behavioural tests. +- Use the existing `pg-embedded-setup-unpriv` test infrastructure for live + PostgreSQL tests. +- Keep source files below 400 lines, or split them before completing the + milestone. +- Keep documentation in en-GB-oxendict style and follow + `docs/documentation-style-guide.md`. +- Prefer Makefile gates over raw tool invocations for final validation. +- Run tests, formatting, and linting sequentially, not in parallel. +- Capture long command output with `tee` under `/tmp`. +- Commit each approved implementation milestone only after its gate passes. +- Do not mark the roadmap item done until all required gates and CodeRabbit + review concerns are clear. ## Tolerances (exception triggers) -- Scope tolerance: if the work requires changing the `RouteQueue` port trait - signature or `JobDispatchError` enum beyond what is already defined, the work - should be stopped and escalated. -- Runtime-wiring tolerance: if a real Apalis adapter cannot be introduced - without also wiring new server/application configuration or modifying - `state_builders.rs`, the work should be stopped and an explicit decision - made about whether that extra wiring belongs in 5.2.1. -- Dependency tolerance: adding `apalis`, `apalis-core`, `apalis-sql`, and - `apalis-postgres` (plus `sqlx` if not already present) is expected. If more - than two additional production dependencies beyond these are required, the - work should be stopped and the trade-off reviewed. -- Error-contract tolerance: if Apalis or SQLx failures cannot be mapped - cleanly into the existing `JobDispatchError::{Unavailable, Rejected}` shape, - the work should be stopped and the domain error contract revisited before - proceeding. -- Test-harness tolerance: the implementation uses the existing - `pg-embedded-setup-unpriv` infrastructure for live adapter tests (the same - harness used by Diesel repository tests). If the embedded PostgreSQL cluster - cannot be started, the work should be stopped and the blocker documented - before continuing. -- Migration tolerance: if Apalis requires database migrations that conflict - with or duplicate existing Diesel migrations, the work should be stopped and - escalated. `PostgresStorage::setup()` creates its own tables; this must be - verified to coexist safely with the existing Diesel-managed schema. -- Gate tolerance: if `make check-fmt`, `make lint`, or `make test` fail after - three repair loops, the work should be stopped and the failing logs captured - instead of pushing past the quality gates. -- Environment tolerance: if embedded PostgreSQL cannot start locally, the work - should be stopped and the exact blocker plus the command output documented. -- File-size tolerance: if any single source file exceeds 400 lines, it should - be split into coherent sub-modules before proceeding. +- Scope: stop and escalate if satisfying 5.2.1 requires changing more than 12 + production source files or more than 800 net lines outside tests and + documentation. +- Port shape: stop and escalate before changing the `RouteQueue` trait + signature or adding Apalis-specific variants to `JobDispatchError`. +- Runtime wiring: stop and escalate before wiring `RouteQueue` into + `RouteSubmissionServiceImpl`, HTTP handlers, or server startup. +- Dependencies: `apalis-core`, `apalis-postgres`, and `sqlx` are expected. Stop + and escalate if more than two additional production dependencies are needed. +- Persistence schema: stop and escalate if Apalis table setup conflicts with + Diesel migrations or requires Diesel-managed Apalis migrations. +- Test harness: stop and document the blocker if embedded PostgreSQL cannot be + started with the existing `pg-embedded-setup-unpriv` flow. +- Verification: stop and document logs if `make check-fmt`, `make lint`, or + `make test` still fail after three focused repair loops. +- CodeRabbit: stop and document the concern if `coderabbit review --agent` + reports a finding that would require widening the approved scope. ## Risks -- Risk: Apalis 1.x is in release-candidate status, not a stable 1.0 release. - The API surface may change between release candidates. - Severity: medium. - Likelihood: medium. - Mitigation: pin the `apalis-postgres` version tightly in `Cargo.toml` (for - example, `"1.0.0-rc.6"`) and document the pinned version in the decision - log. If a breaking change is encountered, evaluate whether an older RC or - the 0.7.x stable line is more appropriate. - -- Risk: `PostgresStorage::setup()` creates its own tables in the connected - database. These tables may conflict with existing Diesel-managed migrations - or with `pg-embedded-setup-unpriv` template databases. - Severity: high. - Likelihood: medium. - Mitigation: call `PostgresStorage::setup()` after Diesel migrations in test - setup, and verify that the Apalis tables do not collide with existing table - names. If collisions occur, use a separate PostgreSQL schema or database for - the queue. - -- Risk: the `RouteQueue` port is generic over `Plan` via an associated type, - so the Apalis adapter must choose serialization bounds without leaking them - into the domain. - Severity: high. - Likelihood: medium. - Mitigation: keep `Serialize` bounds on the adapter implementation only - (not on the port trait), following the pattern established by - `RedisRouteCache`. Use `serde_json::Value` as the intermediate payload - format between the adapter and provider. Test with a representative fixture - plan type. - -- Risk: Apalis uses `sqlx` for PostgreSQL access whereas the existing - repository layer uses `diesel-async`. Two connection pool implementations - coexisting may introduce complexity. - Severity: medium. - Likelihood: high. - Mitigation: the Apalis adapter owns its own `sqlx::PgPool`, which is - separate from the Diesel `bb8` pool. Both connect to the same PostgreSQL - instance but through independent pool implementations. Document this - dual-pool arrangement in the architecture doc. - -- Risk: the roadmap text says "Apalis with PostgreSQL backend", but the - architecture document's code samples reference Redis as the job broker. The - implementation must follow the roadmap, not the older architecture prose. - Severity: low. - Likelihood: low. - Mitigation: use `apalis-postgres` as specified by the roadmap. Record the - divergence from older architecture prose in the decision log and update the - architecture document to reflect the chosen backend. - -- Risk: full-gate failures may come from the existing embedded-Postgres setup, - not from the Apalis adapter. - Severity: medium. - Likelihood: medium. - Mitigation: retain logs with `tee`, rely on `make test` so - `PG_EMBEDDED_WORKER` is wired automatically, and record any environment - failures explicitly before judging the feature incomplete. - -## Agent team and ownership - -This implementation should use an explicit agent team. One person may play more -than one role, but the ownership boundaries should remain visible. - -- Coordinator agent: - owns sequencing, keeps this ExecPlan current, enforces tolerances, collects - gate evidence, and decides when roadmap item 5.2.1 is ready to close. - -- Queue adapter agent: - owns `backend/Cargo.toml` dependency additions and - `backend/src/outbound/queue/*`, including the Apalis storage wrapper, adapter - struct, error translation, and connection provider trait. - -- Test harness agent: - owns `backend/tests/support/*` additions needed to provision an - Apalis-compatible PostgreSQL database within the existing - `pg-embedded-setup-unpriv` infrastructure, and any shared fixtures for queue - integration tests. - -- Quality Assurance (QA) agent: - owns adapter `rstest` coverage plus `rstest-bdd` scenarios and feature files - proving happy, unhappy, and edge behaviour. - -- Documentation agent: - owns `docs/wildside-backend-architecture.md` and `docs/backend-roadmap.md`, - and updates the latter only after the coordinator confirms all gates passed. - -Hand-off order: - -1. Queue adapter agent adds the dependencies and module layout plus failing - unit tests. -2. Test harness agent lands the Postgres integration fixture additions and - failing behavioural scenarios. -3. Queue adapter agent makes the Apalis adapter pass both focused suites. -4. QA agent broadens error-path and edge-case coverage. -5. Documentation agent records the design decision and closes the roadmap item. -6. Coordinator agent runs final gates and updates this ExecPlan. - -## Progress +- Risk: the current base already contains an Apalis adapter, so the remaining + work may be partly reconciliation rather than new implementation. + Mitigation: start by auditing the current symbols, tests, dependencies, and + docs. If the adapter is already correct, make only closure changes such as + roadmap updates and plan evidence. + +- Risk: Apalis release-candidate APIs may have shifted. Firecrawl research on + 2026-05-21 found `apalis-postgres` latest documentation at 1.0.0-rc.8, while + this repository currently pins older 1.0.0 release-candidate crates. + Mitigation: do not upgrade during 5.2.1 unless a gate failure requires it. + Record any version decision in this plan before changing dependencies. + +- Risk: Apalis uses SQLx while repository adapters use Diesel and `bb8`. + Mitigation: keep the Apalis SQLx `PgPool` contained in + `backend/src/outbound/queue/*` and document the dual-pool boundary. + +- Risk: `PostgresStorage::setup()` creates Apalis-owned tables that are not + represented in Diesel migrations. + Mitigation: verify setup is idempotent in tests and document that Apalis owns + its internal queue schema. + +- Risk: route-submission user flows may appear to require end-to-end queue + dispatch to satisfy "replacing the current stub adapter". + Mitigation: keep the acceptance boundary explicit. 5.2.1 closes when the + driven adapter exists and is validated; dispatch and worker processing are + later roadmap items. + +- Risk: `docs/users-guide.md` was requested, but this workspace does not + contain that file. + Mitigation: update `docs/developers-guide.md` and + `docs/wildside-backend-architecture.md` for internal behaviour. If no + end-user server behaviour changes, record that `docs/users-guide.md` is + absent and no user-facing guide update was possible. + +## Skills and reference documents + +Use the following skills while executing this plan: + +- `leta`: navigate symbols and references before editing code. +- `rust-router`: route any Rust language issue to the smallest useful Rust + skill. +- `hexagonal-architecture`: enforce port and adapter boundaries. +- `rust-async-and-concurrency`: review async ownership, storage handles, and + worker-adjacent lifecycle implications. +- `rust-errors`: review `JobDispatchError` mapping. +- `domain-cli-and-daemons`: keep future worker process concerns out of this + adapter milestone. +- `firecrawl-mcp`: verify external Apalis and PostgreSQL queue-tooling facts. +- `commit-message`: commit with a file-based commit message. +- `pr-creation` and `en-gb-oxendict-style`: prepare the draft pull request. + +Read these repository documents before implementation: -- [x] Review roadmap item 5.2.1, the current `RouteQueue` port, the stub - adapter, the architecture guidance, and the testing guides. -- [x] Confirm that no current runtime service or server builder dispatches to - `RouteQueue`; verify the change is adapter-first. -- [x] Draft this ExecPlan at - `docs/execplans/backend-5-2-1-apalis-route-queue.md`. -- [x] Await approval gate. -- [x] Run baseline queue tests (`/tmp/5-2-1-queue-baseline.out` - - 1 passing test confirmed) -- [x] Add `apalis-postgres` and related dependencies to `backend/Cargo.toml`. - - Updated wildside-data git rev from 894aa38 to 2db6cbf (rusqlite 0.32.1) - - Added apalis-postgres 1.0.0-rc.6 and sqlx 0.8 (postgres, runtime-tokio-rustls) - - `cargo check -p backend` succeeded - - Log: `/tmp/5-2-1-check-deps-updated-wildside.out` -- [x] Create `backend/src/outbound/queue/apalis_route_queue.rs` with the - adapter struct, connection provider trait, and error mapping. -- [x] Create `backend/src/outbound/queue/test_helpers.rs` with a fake provider +- `docs/backend-roadmap.md` +- `docs/wildside-backend-architecture.md` +- `docs/developers-guide.md` +- `docs/documentation-style-guide.md` +- `docs/rust-testing-with-rstest-fixtures.md` +- `docs/rstest-bdd-users-guide.md` +- `docs/rust-doctest-dry-guide.md` +- `docs/complexity-antipatterns-and-refactoring-strategies.md` +- `docs/pg-embed-setup-unpriv-users-guide.md` + +External references checked during planning: + +- `https://docs.rs/apalis-postgres/latest/apalis_postgres/` +- `https://apalis.dev/docs/introduction/quickstart` + +## Current repository orientation + +The current base contains these relevant files: + +- `backend/src/domain/ports/route_queue.rs` defines the domain-owned + `RouteQueue` trait and `JobDispatchError`. +- `backend/src/outbound/queue/mod.rs` exports queue adapters. +- `backend/src/outbound/queue/stub_route_queue.rs` retains the no-op stub. +- `backend/src/outbound/queue/apalis_route_queue.rs` contains the + Apalis-backed adapter and its focused unit tests. +- `backend/src/outbound/queue/test_helpers.rs` contains fake queue providers for unit tests. -- [x] Add focused `rstest` unit tests in - `backend/src/outbound/queue/apalis_route_queue.rs` (or a sibling `tests` - module) covering enqueue round-trip, unavailable backend, and rejected job - paths. -- [x] Extend `backend/tests/support/` with Apalis PostgreSQL setup helpers - that reuse the `pg-embedded-setup-unpriv` cluster. - - Added `setup_apalis_storage` helper to `backend/tests/support/embedded_postgres.rs` -- [x] Create `backend/tests/features/route_queue_apalis.feature` with BDD - scenarios. -- [x] Create `backend/tests/route_queue_apalis_bdd.rs` with step definitions - exercising the adapter against a real PostgreSQL instance. -- [x] Update `docs/wildside-backend-architecture.md` with the 5.2.1 scope - decision and dual-pool documentation. -- [x] Run `make check-fmt` and capture the log. - - Log: `/tmp/5-2-1-check-fmt-after.out` (passed after `make fmt`) -- [ ] Run `make lint` and capture the log. - - In progress: `/tmp/5-2-1-lint-attempt2.out` -- [ ] Run `make test` and capture the log. - - In progress: `/tmp/5-2-1-make-test-full.out` -- [ ] Mark `docs/backend-roadmap.md` item 5.2.1 done after all gates pass. - -## Surprises & discoveries +- `backend/tests/features/route_queue_apalis.feature` describes behavioural + queue scenarios. +- `backend/tests/route_queue_apalis_bdd.rs` implements the behavioural tests. +- `backend/tests/support/embedded_postgres.rs` contains embedded PostgreSQL + support, including Apalis storage setup helpers. +- `backend/src/domain/route_submission/mod.rs` still has queue-dispatch TODOs + and must remain out of scope for this plan. +- `backend/src/server/mod.rs` does not currently construct or inject a + route-queue dependency into route submission. +- `docs/wildside-backend-architecture.md` and `docs/developers-guide.md` + already mention Apalis queue adapter behaviour and must be checked against + the implementation before closure. + +Key terms: + +- Apalis is a Rust background task processing library. +- `apalis-postgres` is the Apalis backend that stores tasks in PostgreSQL. +- `PostgresStorage` is the Apalis storage type used for polling-backed + PostgreSQL queues. +- `PostgresStorage::setup()` provisions Apalis-owned queue tables. +- A driven adapter is outbound infrastructure code that implements a + domain-owned port. + +## Implementation plan + +Milestone 0: approval and baseline audit. + +After approval, confirm the branch and workspace: + +```bash +git branch --show-current +git status --short --branch +leta workspace add "$(pwd)" +``` -### Discovery 1: Dependency conflict blocked apalis-postgres integration (2026-04-03) - -**Issue**: `apalis-postgres` (either 1.0.0-rc.6 or apalis-sql 0.7.x) -could not be added due to a `libsqlite3-sys` native library version -conflict: - -- `wildside-data` (git dependency at rev 894aa38) depends on - `rusqlite` 0.31 → `libsqlite3-sys` 0.28 -- `pg-embed-setup-unpriv` 0.5.0 (used for test infrastructure) depends - on `postgresql_embedded` 0.20.1 → `sqlx` 0.8.6 → - `libsqlite3-sys` 0.30+ -- Cargo enforces that only one version of a native library (`sqlite3`) - can be linked per binary - -**Attempted resolutions (all failed)**: - -- Adjusting version constraints for `url`, `hex`, - `postgresql_embedded` -- Disabling sqlx default features -- Explicitly adding `rusqlite` 0.32 or `libsqlite3-sys` 0.30 to - backend/Cargo.toml -- Using `[patch.crates-io]` at workspace level (patches require - different source, can't patch crates.io with crates.io) -- Using `[workspace.dependencies]` (doesn't override git dependency - locks) -- Switching to apalis 0.7.x stable (still blocked by - pg-embed-setup-unpriv → sqlx 0.8) - -**Root cause**: The git dependency `wildside-data` is locked to a -specific revision that uses rusqlite 0.31, and Cargo cannot override -transitive dependencies of git sources. - -**Options forward**: - -1. **Update wildside-engine upstream**: Modify the wildside-engine - repository to use rusqlite 0.32+, then update the git revision in - this project - - Pros: Clean resolution, no workarounds - - Cons: Requires upstream coordination, blocks this task - -2. **Use apalis-core directly without apalis-postgres**: Implement a - custom PostgreSQL storage adapter using Diesel instead of sqlx - - Pros: Avoids sqlx entirely, keeps Diesel as single DB access layer - - Cons: Significantly more implementation work, diverges from roadmap plan - -3. **Use an alternate job queue library**: Evaluate alternatives like - `fang`, `tokio-cron-scheduler`, or custom implementation - - Pros: May avoid dependency conflicts - - Cons: Deviates from approved roadmap, requires re-evaluation of architecture - -4. **Defer 5.2.1 until wildside-data conflict is resolved**: Mark this - task as blocked and continue with other roadmap items - - Pros: Clean path forward once unblocked - - Cons: Delays queue functionality - -**Resolution (2026-04-03)**: Wildside-engine upstream was updated to -rusqlite 0.32.1 via commit 2db6cbf. Updated backend/Cargo.toml to use -this revision, which resolved the libsqlite3-sys conflict. -`cargo check -p backend` now succeeds with apalis-postgres 1.0.0-rc.6 -and sqlx 0.8 dependencies added successfully. - -## Decision log - -- Decision: 5.2.1 delivers the Apalis-driven adapter itself, not - request-path queue dispatch or worker consumption. - Rationale: no domain service currently dispatches to `RouteQueue` (gated - behind `TODO(#276)` in `route_submission`), and adding that behaviour would - spill into later roadmap items covering job structs, retry policies, and - worker deployment. - Date/Author: 2026-04-03 / planning team. - -- Decision: use `apalis-postgres` (1.0.0-rc family) rather than - `apalis-sql` with the `postgres` feature flag. - Rationale: `apalis-postgres` is the dedicated PostgreSQL crate with - purpose-built storage types (`PostgresStorage`, `PostgresStorageWithListener`) - and is the recommended path for PostgreSQL-backed queues in the Apalis - ecosystem. It provides `NOTIFY`/`SKIP LOCKED` support and heartbeat-based - orphan recovery. - Date/Author: 2026-04-03 / planning team. - -- Decision: use `sqlx::PgPool` for the Apalis adapter, coexisting with the - Diesel `bb8` pool used by repository adapters. - Rationale: Apalis is built on SQLx and expects a `sqlx::PgPool`. Attempting - to share the Diesel pool would require a brittle adapter layer. Both pools - connect to the same PostgreSQL instance with independent lifecycle - management. This dual-pool arrangement mirrors how the Redis cache adapter - uses its own `bb8-redis` pool independently of Diesel. - Date/Author: 2026-04-03 / planning team. - -- Decision: follow the `RedisRouteCache` adapter pattern — use a - `QueueProvider` trait to abstract the Apalis storage backend, keeping the - adapter generic over both the plan type and the provider. - Rationale: this pattern is proven in the codebase, enables unit testing with - a `FakeProvider`, and keeps the adapter decoupled from Apalis internals. - Date/Author: 2026-04-03 / planning team. - -- Decision: the Apalis adapter's PostgreSQL tables are created by - `PostgresStorage::setup()` at connection time, not by Diesel migrations. - Rationale: Apalis owns its table schema and the `setup()` call is - idempotent. Mixing Apalis schema into Diesel migrations would create a - coupling between the two object–relational mappers (ORMs). The adapter calls - `setup()` during construction, and tests call it during harness provisioning - after Diesel migrations complete. - Date/Author: 2026-04-03 / planning team. - -- Decision: use PostgreSQL rather than Advanced Message Queuing Protocol - (AMQP) brokers (RabbitMQ / LavinMQ) as the queue backend for 5.2.1, on the - explicit condition that the `QueueProvider` abstraction provides sufficient - isolation to migrate to an AMQP backend at a later date should volume - warrant it. - Rationale: PostgreSQL is already provisioned in production and in the test - harness (`pg-embedded-setup-unpriv`), so the adapter can be tested and - deployed with zero new infrastructure. AMQP would require a new broker - service in production, a new test harness (no embedded RabbitMQ equivalent - exists for Rust), and depends on the less mature `apalis-amqp` crate. The - hexagonal boundary — specifically the `QueueProvider` trait and the - domain-owned `RouteQueue` port — ensures that swapping to an - `ApalisAmqpProvider` is a contained adapter change: the domain port, all - `FakeQueueProvider` unit tests, and consuming services remain untouched. - If queue throughput or routing/fanout requirements outgrow PostgreSQL - `NOTIFY`/`SKIP LOCKED`, the migration path is to implement a new provider - behind the same trait and update the composition root. - Date/Author: 2026-04-03 / planning team. - -- Decision: the architecture document will be updated to reflect that the - queue backend is PostgreSQL (via `apalis-postgres`), not Redis, for job - storage. The older architecture prose references Redis as the job broker; - the roadmap explicitly specifies PostgreSQL. - Rationale: the roadmap is the authoritative source for delivery scope. The - architecture document must be updated to match. - Date/Author: 2026-04-03 / planning team. - -## Outcomes & retrospective - -(To be populated upon completion.) - -## Context and orientation - -The relevant current files are: - -- `backend/src/domain/ports/route_queue.rs` - defines the `RouteQueue` trait with a single async method `enqueue` and an - associated type `Plan: Send + Sync`. It also defines `JobDispatchError` with - two variants: `Unavailable { message: String }` (queue infrastructure is - down) and `Rejected { message: String }` (the job could not be acknowledged - or persisted). Both variants have auto-generated constructors via the - `define_port_error!` macro (for example, `JobDispatchError::unavailable("…")` - and `JobDispatchError::rejected("…")`). - -- `backend/src/outbound/queue/mod.rs` - now exposes `StubRouteQueue
`, which is generic over any `P: Send + Sync` - and implements `RouteQueue` with a no-op `enqueue` that logs a warning once - per process, alongside the Apalis adapter re-exports - `ApalisPostgresProvider`, `ApalisRouteQueue
`, and - `GenericApalisRouteQueue
`. The stub still has a small unit test - confirming enqueue succeeds. - -- `backend/src/domain/route_submission/mod.rs` - contains two `TODO(#276)` markers (lines 241 and 295) where queue dispatch - will eventually be wired. This integration is out of scope for 5.2.1. - -- `backend/src/server/state_builders.rs` - does not currently construct or inject a `RouteQueue`. The server composition - root is unchanged by this task. - -- `backend/src/outbound/cache/redis_route_cache.rs` - is the reference implementation for the adapter pattern: it defines a - `ConnectionProvider` trait, a `RedisPoolProvider` concrete implementation, - and a `GenericRedisRouteCache
` struct parameterised over the plan type - and provider. Error mapping consolidates all infrastructure failures into - domain error variants. A `FakeProvider` in - `backend/src/outbound/cache/test_helpers.rs` enables unit testing without - Redis. - -- `backend/tests/support/embedded_postgres.rs` - provides `provision_template_database()` for test database creation using - `pg-embedded-setup-unpriv`. This harness will be reused for the queue - adapter's integration tests. +The expected branch is `backend-5-2-1-apalis-route-queue`. The working tree +should be clean before implementation begins. If there are user changes, do +not overwrite them. -- `docs/wildside-backend-architecture.md` - describes the hexagonal architecture, the `RouteQueue` port, and the - outbound adapter pattern. It will be updated to document the Apalis adapter - scope and the dual-pool (SQLx + Diesel) arrangement. +Use Leta and plain text search for non-code documents to confirm the current +adapter state: -- `docs/backend-roadmap.md` - lists 5.2.1 as the first queue adapter task. The item will be marked done - only after all gates pass. - -Key terminology: - -- **Apalis**: a Rust background job processing library that uses tower - `Service` semantics. Jobs are defined as serializable structs; workers - consume them from a storage backend. -- **`apalis-postgres`**: the PostgreSQL storage backend for Apalis, using SQLx - and `NOTIFY`/`SKIP LOCKED` for reliable job delivery. -- **`PostgresStorage`**: the concrete Apalis type that implements job - enqueueing and consumption against a PostgreSQL database. -- **`pg-embedded-setup-unpriv`**: a crate used in this repository to provision - ephemeral PostgreSQL clusters for integration testing without requiring - system-level PostgreSQL installation. -- **Driven adapter**: an outbound adapter that implements a domain port, - translating domain calls into infrastructure operations (for example, - `enqueue` → Apalis `push_request`). -- **Connection provider**: a trait abstracting the storage backend so the - adapter can be tested with fakes in unit tests and with real PostgreSQL in - integration tests. - -## Plan of work - -Stage A: add dependencies and scaffold the adapter module. - -Add `apalis-core` and `apalis-postgres` to `backend/Cargo.toml` as production -dependencies, plus `sqlx` with the `postgres` and `runtime-tokio` features (if -not already present). The `apalis-postgres` crate transitively brings in -`apalis-core` and `sqlx`, but explicit entries ensure version pinning. Pin -`apalis-postgres` to a specific 1.0.0 release candidate version. - -Restructure `backend/src/outbound/queue/` from a single `mod.rs` into a -multi-file module: - -- keep `backend/src/outbound/queue/mod.rs` as a thin module header with - re-exports; -- add `backend/src/outbound/queue/stub_route_queue.rs` containing the existing - `StubRouteQueue` (moved from `mod.rs`); -- add `backend/src/outbound/queue/apalis_route_queue.rs` containing the real - adapter; -- add `backend/src/outbound/queue/test_helpers.rs` (gated behind `#[cfg(test)]`) - containing a `FakeQueueProvider` for unit tests. - -The adapter should follow this shape: - -```rust -// backend/src/outbound/queue/apalis_route_queue.rs - -use async_trait::async_trait; -use serde::Serialize; -use serde_json::Value; -use std::marker::PhantomData; - -use crate::domain::ports::{JobDispatchError, RouteQueue}; - -/// Abstracts the queue storage backend for testability. -#[async_trait] -pub(crate) trait QueueProvider: Send + Sync { - /// Push a JSON job payload into the queue. - async fn push_job(&self, payload: Value) -> Result<(), JobDispatchError>; -} - -/// Apalis-backed `RouteQueue` adapter using PostgreSQL storage. -#[derive(Debug, Clone)] -pub struct GenericApalisRouteQueue
{
- provider: Q,
- _plan: PhantomData =
- GenericApalisRouteQueue ;
-
-#[async_trait]
-impl RouteQueue for GenericApalisRouteQueue
-where
- P: Serialize + Send + Sync,
- Q: QueueProvider,
-{
- type Plan = P;
-
- async fn enqueue(
- &self,
- plan: &Self::Plan,
- ) -> Result<(), JobDispatchError> {
- let payload = serde_json::to_value(plan)
- .map_err(|e| JobDispatchError::rejected(
- format!("Failed to serialize plan: {e}")
- ))?;
- self.provider.push_job(payload).await
- }
-}
+```bash
+leta grep "RouteQueue|ApalisRouteQueue|GenericApalisRouteQueue" backend \
+ -k trait,struct,enum,function,method --head 200
+rg -n "5.2.1|Apalis|RouteQueue|PostgresStorage" \
+ docs/backend-roadmap.md docs/wildside-backend-architecture.md \
+ docs/developers-guide.md backend/Cargo.toml
```
-The `ApalisPostgresProvider` wraps `apalis_postgres::PostgresStorage` and maps
-its errors to `JobDispatchError` variants:
-
-```rust
-// backend/src/outbound/queue/apalis_route_queue.rs (continued)
-
-use apalis_postgres::PostgresStorage;
-use sqlx::PgPool;
-
-/// Real provider backed by Apalis PostgreSQL storage.
-#[derive(Debug, Clone)]
-pub struct ApalisPostgresProvider {
- storage: PostgresStorage ` implements `RouteQueue ` is re-exported for the BDD harness
+ seam even though production code should prefer `ApalisRouteQueue `.
+- `docs/pg-embed-setup-unpriv-users-guide.md` must be updated only if the
+ embedded PostgreSQL setup expectations changed.
+- `docs/users-guide.md` must be updated if it exists and user-facing server
+ behaviour changed. If the file is still absent and the server interface did
+ not change, record that no user-guide update was applicable.
+
+Run documentation checks after documentation edits:
+
+```bash
+set -o pipefail
+make markdownlint 2>&1 | tee /tmp/backend-5-2-1-markdownlint.out
+
+set -o pipefail
+make nixie 2>&1 | tee /tmp/backend-5-2-1-nixie.out
+```
+
+Run CodeRabbit again:
-All steps in this plan are designed to be re-runnable:
+```bash
+coderabbit review --agent
+```
-- `cargo check` and `cargo test` are idempotent.
-- `PostgresStorage::setup()` is idempotent (creates tables if they do not
- exist).
-- Diesel migrations are idempotent (tracked by migration version).
-- `pg-embedded-setup-unpriv` provisions fresh template databases on each test
- run.
-- If a step fails halfway, re-running it from the beginning is safe. No
- destructive or irreversible operations are performed.
+Fix all in-scope concerns before continuing. Commit the passing milestone if
+any files changed.
-If the embedded PostgreSQL cluster fails to start (a known environment issue in
-this container), `/dev/null` should be checked to confirm it is a character
-device (`ls -l /dev/null` should show `crw-rw-rw-`). If it is a regular file,
-it should be recreated with `mknod -m 666 /dev/null c 1 3`.
+Milestone 3: full quality gates.
-## Interfaces and dependencies
+Run the required repository gates sequentially:
-### Dependencies to add to `backend/Cargo.toml`
+```bash
+set -o pipefail
+make check-fmt 2>&1 | tee /tmp/backend-5-2-1-check-fmt.out
-```toml
-# Queue adapter (Apalis with PostgreSQL)
-apalis-core = "1.0.0-rc.7"
-apalis-postgres = "1.0.0-rc.6"
+set -o pipefail
+make lint 2>&1 | tee /tmp/backend-5-2-1-lint.out
-# SQLx for Apalis PostgreSQL pool (if not already present)
-sqlx = { version = "0.8", default-features = false, features = ["postgres", "runtime-tokio-rustls"] }
+set -o pipefail
+make test 2>&1 | tee /tmp/backend-5-2-1-test.out
```
-The exact version of `apalis-postgres` should be verified against the latest
-available release candidate at implementation time. If the version has advanced
-beyond `1.0.0-rc.6`, evaluate the changelog for breaking changes and pin
-accordingly.
+If a gate fails, repair only in-scope defects and rerun the failing gate. After
+the failing gate passes, rerun any later gates in sequence. Stop after three
+repair loops for the same gate.
-### Module layout after implementation
+Run CodeRabbit after full gates:
-```plaintext
-backend/src/outbound/queue/
-├── mod.rs (module header, re-exports)
-├── stub_route_queue.rs (moved from mod.rs, unchanged)
-├── apalis_route_queue.rs (new: adapter struct, provider trait, error mapping)
-└── test_helpers.rs (new, #[cfg(test)]: FakeQueueProvider)
+```bash
+coderabbit review --agent
```
-### Key types and traits
+Milestone 4: roadmap closure.
+
+Only after Milestones 1 through 3 pass and CodeRabbit concerns are clear,
+update `docs/backend-roadmap.md`:
+
+```markdown
+- [x] 5.2.1. Implement `RouteQueue` using Apalis with PostgreSQL backend,
+ replacing the current stub adapter.
+```
-In `backend/src/outbound/queue/apalis_route_queue.rs`, define:
+Add a short execution note under the item if useful, pointing to the queue
+tests and this ExecPlan. Do not mark 5.2.2 or later items done.
-```rust
-/// Abstracts the queue storage backend for testability.
-#[async_trait]
-pub(crate) trait QueueProvider: Send + Sync {
- async fn push_job(&self, payload: Value) -> Result<(), JobDispatchError>;
-}
+Run the final required gates again if the roadmap edit triggers formatting or
+lint changes:
-/// Apalis-backed provider using PostgreSQL storage.
-#[derive(Debug, Clone)]
-pub struct ApalisPostgresProvider { /* PostgresStorage { /* provider + PhantomData */ }
+set -o pipefail
+make lint 2>&1 | tee /tmp/backend-5-2-1-final-lint.out
-/// Production type alias.
-pub type ApalisRouteQueue =
- GenericApalisRouteQueue ;
+set -o pipefail
+make test 2>&1 | tee /tmp/backend-5-2-1-final-test.out
```
-In `backend/src/outbound/queue/test_helpers.rs`, define:
+Commit the roadmap closure separately.
-```rust
-/// Fake provider that records pushed payloads for assertion.
-pub(crate) struct FakeQueueProvider { /* internal state */ }
+Milestone 5: push and pull request.
-/// Fake provider that always returns an error.
-pub(crate) struct FailingQueueProvider { /* error message */ }
+Push the branch and set upstream tracking:
+
+```bash
+git push -u origin backend-5-2-1-apalis-route-queue
```
-### Test files
+Create or update a draft pull request. The title must include the roadmap item
+as `(backend-5.2.1)`. The body must mention this ExecPlan and include a
+`## References` section with the Lody session link from:
-```plaintext
-backend/tests/features/route_queue_apalis.feature (Gherkin scenarios)
-backend/tests/route_queue_apalis_bdd.rs (step definitions)
+```bash
+echo "${LODY_SESSION_ID}"
```
-## Approval / implementation gate
+The draft pull request for the plan itself must say that implementation awaits
+explicit approval. The implementation pull request, if later created from the
+approved work, must include gate logs and CodeRabbit outcomes.
+
+## Validation and acceptance
+
+5.2.1 can be marked complete only when all of the following are true:
+
+- `ApalisRouteQueue ` implements `RouteQueue ` is
+ re-exported for the BDD harness seam.
+- [x] 2026-05-26: Ran documentation checks after documentation
+ reconciliation: `make markdownlint` (`/tmp/backend-5-2-1-markdownlint.out`)
+ and `make nixie` (`/tmp/backend-5-2-1-nixie.out`); both passed.
+- [x] 2026-05-26: Confirmed `docs/users-guide.md` is absent and that 5.2.1
+ does not change user-facing server behaviour, so no user-guide update is
+ applicable.
+- [x] 2026-05-26: Ran `coderabbit review --agent` after documentation
+ reconciliation; review completed with 0 findings.
+- [x] 2026-05-26: Ran full gates: `make check-fmt`
+ (`/tmp/backend-5-2-1-check-fmt.out`), `make lint`
+ (`/tmp/backend-5-2-1-lint.out`), and `make test`
+ (`/tmp/backend-5-2-1-test.out`); all passed. The Rust nextest portion ran
+ 1220 tests with 1220 passed and 4 skipped, and the frontend/workspace tests
+ also passed.
+- [x] 2026-05-26: Ran `coderabbit review --agent` after full gates; review
+ completed with 0 findings.
+- [x] 2026-05-26: Marked only `docs/backend-roadmap.md` item 5.2.1 done.
+- [x] 2026-05-26: Ran final closure gates after the roadmap update:
+ `make check-fmt` (`/tmp/backend-5-2-1-final-check-fmt.out`), `make lint`
+ (`/tmp/backend-5-2-1-final-lint.out`), and `make test`
+ (`/tmp/backend-5-2-1-final-test.out`); all passed. The Rust nextest portion
+ ran 1220 tests with 1220 passed and 4 skipped, and the frontend/workspace
+ tests also passed.
+- [x] 2026-05-26: Ran `coderabbit review --agent` after the final closure
+ commit; review completed with 0 findings.
+- [x] 2026-05-26: Completed outcomes and retrospective.
+
+## Surprises & discoveries
+
+- 2026-05-21: The current base already contains
+ `backend/src/outbound/queue/apalis_route_queue.rs`,
+ `backend/tests/route_queue_apalis_bdd.rs`, and Apalis queue documentation.
+ The branch started with no diff from `origin/main`, while the roadmap item
+ still remained unchecked. This plan therefore focuses on approval-gated
+ validation, repair, and closure rather than assuming the adapter must be
+ written from scratch.
+
+- 2026-05-21: `docs/users-guide.md` is absent from this worktree. User-visible
+ behaviour does not appear to change in 5.2.1 because request-path dispatch
+ and workers remain future work; internal docs are the likely documentation
+ surface.
+
+- 2026-05-21: CodeRabbit review could not complete for the plan-drafting
+ milestone because the service returned a recoverable rate-limit error on two
+ attempts. No CodeRabbit concerns were produced. Approved implementation must
+ retry CodeRabbit before moving beyond the first implementation milestone.
+
+- 2026-05-26: The implementation baseline still matches the planning audit.
+ The adapter exists in the outbound queue module, the domain-facing
+ `RouteQueue` port remains the published contract, and request-path dispatch
+ remains out of scope.
+
+- 2026-05-26: `docs/developers-guide.md` incorrectly described
+ `GenericApalisRouteQueue ` as not re-exported. The code re-exports it
+ from `backend/src/outbound/queue/mod.rs`, and the Apalis BDD test harness
+ imports that re-export directly.
+
+- 2026-05-26: `make nixie` runs `bun install`, which rewrote `bun.lock` to
+ bump the `ip-address` override from `10.1.1` to `10.2.0`. That lockfile
+ change was unrelated to 5.2.1 and was removed from this milestone.
+
+## Decision Log
+
+- Decision: Treat this plan as an approval-gated validation and closure plan
+ for 5.2.1, not as permission to continue the prior in-progress execution.
+ Rationale: the user explicitly stated that the plan must be approved before
+ implementation, and the branch currently has no diff from `origin/main`.
+ Date/Author: 2026-05-21 / planning agent.
+
+- Decision: Keep route-submission dispatch and worker consumption out of
+ scope.
+ Rationale: roadmap items 5.2.2 through 5.3.1 cover job structs, retries,
+ trace propagation, and worker deployment. The current route-submission TODOs
+ show dispatch remains deliberately deferred.
+ Date/Author: 2026-05-21 / planning agent.
+
+- Decision: Use `apalis-postgres` with PostgreSQL storage for the queue
+ adapter.
+ Rationale: `docs/backend-roadmap.md` explicitly requires Apalis with a
+ PostgreSQL backend. Firecrawl research confirmed Apalis PostgreSQL
+ documentation describes `PostgresStorage`, storage setup, standard polling,
+ `NOTIFY`-based storage, heartbeat support, and orphaned job re-enqueueing.
+ Date/Author: 2026-05-21 / planning agent.
+
+- Decision: Do not upgrade Apalis dependencies as part of the plan draft.
+ Rationale: external documentation currently shows a newer release candidate
+ than the repository pins, but dependency upgrades are implementation work and
+ need evidence from gates before they are justified.
+ Date/Author: 2026-05-21 / planning agent.
+
+- Decision: Start approved execution as a validation-and-closure activity.
+ Rationale: the user's 2026-05-26 instruction explicitly approves proceeding
+ with the planned functionality, and the baseline audit confirms that the
+ adapter already exists and the remaining work is to validate, repair any
+ defects found, reconcile documentation, and close the roadmap item.
+ Date/Author: 2026-05-26 / implementation agent.
+
+- Decision: Keep the current `GenericApalisRouteQueue ` re-export and
+ document it as a BDD harness seam.
+ Rationale: changing visibility would be an API adjustment rather than a
+ required 5.2.1 behaviour fix. The re-export is already used by the
+ PostgreSQL-backed BDD test, while production code still has the clearer
+ `ApalisRouteQueue ` alias.
+ Date/Author: 2026-05-26 / implementation agent.
+
+## Outcomes & Retrospective
+
+5.2.1 is complete as a validation-and-closure milestone. The current backend
+already contained the Apalis/PostgreSQL `RouteQueue` adapter, so the approved
+implementation work verified that adapter against focused `rstest` unit tests,
+PostgreSQL-backed `rstest-bdd` scenarios, and full repository gates rather
+than rewriting working code.
+
+The only documentation defect found was in `docs/developers-guide.md`, which
+described `GenericApalisRouteQueue ` as internal even though the backend
+module re-exports it for the BDD harness seam. That guide now matches the
+actual public adapter surface and still directs production code to prefer the
+`ApalisRouteQueue ` alias.
+
+No user-facing server behaviour changed, and this worktree does not contain
+`docs/users-guide.md`, so no user-guide update was applicable. Route-submission
+dispatch, worker consumption, retry policy, trace propagation, and route-engine
+invocation remain explicitly deferred to later roadmap items.
+
+CodeRabbit returned 0 findings after targeted adapter verification,
+documentation reconciliation, full quality gates, and final roadmap closure.
+`docs/backend-roadmap.md` now marks only item 5.2.1 as done.