From 8c4181f409830f243e465e74219e7cb2e067f29f Mon Sep 17 00:00:00 2001 From: leynos Date: Thu, 21 May 2026 00:58:33 +0200 Subject: [PATCH 1/6] Draft Apalis route queue closure plan Replace the stale in-progress backend 5.2.1 ExecPlan with an approval-gated validation and closure plan. Capture the current repository state, Wyvern planning findings, Firecrawl Apalis research, validation gates, and the CodeRabbit rate-limit blocker before implementation proceeds. --- .../backend-5-2-1-apalis-route-queue.md | 1262 ++++++----------- 1 file changed, 437 insertions(+), 825 deletions(-) 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..c02a6b16 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,500 @@ -# 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: DRAFT 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 P>, -} - -/// Production type alias with the real Apalis PostgreSQL provider. -pub type ApalisRouteQueue

= - 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, -} - -#[async_trait] -impl QueueProvider for ApalisPostgresProvider { - async fn push_job( - &self, - payload: Value, - ) -> Result<(), JobDispatchError> { - let mut storage = self.storage.clone(); - storage - .push(payload) - .await - .map_err(|e| JobDispatchError::unavailable( - format!("Failed to enqueue job: {e}") - )) - } -} +Record the findings in `Surprises & Discoveries` before editing code. + +Milestone 1: targeted adapter verification. + +Run the focused queue unit and behavioural suites. These commands intentionally +use Cargo directly because they isolate the feature before the full Makefile +gates. + +```bash +set -o pipefail +cargo test -p backend outbound::queue --lib 2>&1 \ + | tee /tmp/backend-5-2-1-queue-unit.out + +set -o pipefail +cargo test -p backend --test route_queue_apalis_bdd 2>&1 \ + | tee /tmp/backend-5-2-1-queue-bdd.out ``` -Note: the exact Apalis API may differ from what is shown above. The -implementation agent must consult the `apalis-postgres` 1.0.0-rc documentation -at build time to determine the correct method names (`push_request`, -`push_job`, `enqueue`, etc.) and adjust accordingly. The key invariant is that -the provider maps all Apalis/SQLx errors to `JobDispatchError::Unavailable` -and all serialization errors to `JobDispatchError::Rejected`. - -Stage B: lock behaviour with focused unit tests first. - -Before chasing integration wiring, add `rstest` coverage around the adapter's -smallest meaningful behaviours, using the `FakeQueueProvider`: - -- `enqueue` with a valid plan succeeds and the fake provider receives the - serialized payload; -- `enqueue` with a plan that fails serialization returns - `JobDispatchError::Rejected`; -- `enqueue` when the provider returns an error returns - `JobDispatchError::Unavailable`; -- adapter constructors preserve stable defaults; -- the stub adapter continues to work unchanged (regression). - -Use a simple fixture plan type (for example, a `TestPlan` struct deriving -`Debug`, `Clone`, `PartialEq`, `Eq`, `Serialize`, and `Deserialize` with a -single `name: String` field) in these tests. The goal is to pin the error -contract and round-trip semantics -before adding broader behavioural scenarios. - -Stage C: add PostgreSQL integration harness and behavioural coverage. - -Extend the existing `backend/tests/support/` infrastructure to provision an -Apalis-compatible database. The approach is: - -1. Reuse `pg-embedded-setup-unpriv` to create an embedded PostgreSQL cluster - (the same cluster used by Diesel repository tests). -2. After Diesel migrations complete, call `PostgresStorage::setup()` on the - same database URL to create the Apalis job tables. -3. Create a `sqlx::PgPool` connected to the test database for the Apalis - adapter. - -Then add a behavioural suite at -`backend/tests/route_queue_apalis_bdd.rs` with a companion feature file at -`backend/tests/features/route_queue_apalis.feature`. Recommended scenarios: - -- Happy path: enqueueing a plan persists it in the PostgreSQL job table. -- Happy path: enqueueing multiple distinct plans does not overwrite each other. -- Unhappy path: enqueueing when the database connection is invalid returns an - unavailable error. -- Edge path: enqueueing the same plan twice results in two independent jobs - (no deduplication at the adapter level). - -Because this is a driven-adapter feature rather than an HTTP endpoint, the BDD -world can operate directly on the adapter and PostgreSQL harness instead of -booting the Actix server. - -The BDD World struct should follow the pattern established by existing suites: - -```rust -struct World { - mode: TestMode, - db_context: Option, - enqueue_result: Option>, -} +If both pass, update `Progress` with the log paths and move to Milestone 2. If +either fails, repair only adapter, test-harness, or documentation defects that +are inside this plan's tolerances. Do not wire request-path dispatch to make +these tests pass. + +Run CodeRabbit after this milestone: + +```bash +coderabbit review --agent ``` -Use `is_skipped(world)` gates for cluster setup failures, matching the pattern -in `backend/tests/user_state_startup_modes_bdd.rs`. - -Stage D: document the architectural scope explicitly. - -Update `docs/wildside-backend-architecture.md` to record: - -- Roadmap item 5.2.1 introduces the Apalis-backed `RouteQueue` adapter with - PostgreSQL storage, but does not yet enable route queue dispatch in runtime - request flows or worker consumption. -- The queue adapter uses `sqlx::PgPool`, coexisting with the Diesel `bb8` - pool used by repository adapters. Both pools connect to the same PostgreSQL - instance. -- The adapter pattern follows the same `ConnectionProvider`-style abstraction - used by the Redis cache adapter. -- The Apalis job tables are managed by `PostgresStorage::setup()`, not by - Diesel migrations. -- The architecture document's earlier references to Redis as the job broker are - superseded by the roadmap's specification of PostgreSQL. - -Only after the implementation, tests, and full gates pass should -`docs/backend-roadmap.md` mark 5.2.1 done. - -Stage E: replay the full repository gates. - -Once focused queue tests are green, run the required repository gates with log -capture. `make test` is required even though the queue work uses its own SQLx -pool, because the repo's standard backend suites still rely on -`pg-embed-setup-unpriv` and the roadmap item cannot close without a clean -global gate run. - -## Concrete steps - -All commands should be run from `/home/user/project`. `set -o pipefail` and -`tee` should be used for every meaningful command so the exit code survives -truncation and the log is retained. - -1. Capture the current stub-only baseline and confirm there is no Apalis - adapter yet. - - ```bash - set -o pipefail - cargo test -p backend outbound::queue --lib 2>&1 \ - | tee /tmp/5-2-1-queue-baseline.out - ``` - - Expected pre-change signal: - - ```plaintext - test ...stub_queue_enqueue_succeeds ... ok - test result: ok. 1 passed; 0 failed; - ``` - -2. Add Apalis dependencies to `backend/Cargo.toml` and verify the workspace - compiles. - - ```bash - set -o pipefail - cargo check -p backend 2>&1 | tee /tmp/5-2-1-check-deps.out - ``` - -3. Scaffold the adapter module, move the stub, and add focused `rstest` - coverage. Then run the targeted queue tests until they pass. - - ```bash - set -o pipefail - cargo test -p backend outbound::queue --lib 2>&1 \ - | tee /tmp/5-2-1-queue-unit.out - ``` - - Expected green-state examples: - - ```plaintext - test ...apalis_queue_enqueue_round_trips ... ok - test ...apalis_queue_maps_provider_error_to_unavailable ... ok - test ...apalis_queue_maps_serialization_failure_to_rejected ... ok - test ...stub_queue_enqueue_succeeds ... ok - test result: ok. - ``` - -4. Add the PostgreSQL integration harness and BDD coverage, then run the - focused scenarios. - - ```bash - set -o pipefail - cargo test -p backend --test route_queue_apalis_bdd \ - 2>&1 | tee /tmp/5-2-1-queue-bdd.out - ``` - - Expected green-state examples: - - ```plaintext - test route_queue_apalis_bdd::plan_is_persisted_in_queue ... ok - test route_queue_apalis_bdd::invalid_connection_returns_unavailable ... ok - test result: ok. - ``` - -5. Update documentation after the focused suites are stable. - - ```bash - set -o pipefail - make markdownlint 2>&1 | tee /tmp/5-2-1-markdownlint.out - set -o pipefail - make nixie 2>&1 | tee /tmp/5-2-1-nixie.out - ``` - -6. Run the required full gates before marking the roadmap item done. - - ```bash - set -o pipefail - make check-fmt 2>&1 | tee /tmp/5-2-1-check-fmt.out - set -o pipefail - make lint 2>&1 | tee /tmp/5-2-1-lint.out - set -o pipefail - make test 2>&1 | tee /tmp/5-2-1-test.out - ``` - -7. Mark `docs/backend-roadmap.md` item 5.2.1 done only after the gate logs - are clean, then append the log paths and outcome summary to this ExecPlan. +Fix all in-scope concerns before continuing. If CodeRabbit requests +out-of-scope work, document it and escalate. -## Validation and acceptance +Commit the passing milestone with a file-based commit message if any files +changed. -The implementation is done only when all of the following are true: - -- Adapter behaviour: - - `ApalisRouteQueue

` implements `RouteQueue` using - `apalis-postgres` `PostgresStorage`. - - `enqueue` serializes the plan and pushes it to the PostgreSQL-backed Apalis - job table. - - serialization failures return `Err(JobDispatchError::Rejected { .. })`. - - connection or storage failures return - `Err(JobDispatchError::Unavailable { .. })`. -- Architectural boundaries: - - domain code still knows only the `RouteQueue` trait and - `JobDispatchError`; - - no inbound adapter or domain service imports Apalis or SQLx types; - - runtime request-path queue dispatch is not enabled as an accidental side - effect; - - the `TODO(#276)` markers in `route_submission` remain unchanged. -- Tests: - - new `rstest` coverage passes for unit-level adapter behaviour; - - new `rstest-bdd` coverage passes against a real embedded PostgreSQL - instance; - - existing Postgres-backed suites still pass through `make test`; - - the existing `StubRouteQueue` tests still pass. -- Documentation: - - `docs/wildside-backend-architecture.md` records the 5.2.1 scope decision - and dual-pool architecture; - - `docs/backend-roadmap.md` marks 5.2.1 done only after the gates pass. -- Gates: - - `make check-fmt` passes; - - `make lint` passes; - - `make test` passes. +Milestone 2: documentation reconciliation. -## Idempotence and recovery +Review and update only documentation that is stale or inaccurate: + +- `docs/wildside-backend-architecture.md` must explain that 5.2.1 covers the + driven queue adapter, not worker consumption or request dispatch. +- `docs/developers-guide.md` must explain adapter boundaries, dependency + visibility, and how to run the queue tests. +- `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 */ } +```bash +set -o pipefail +make check-fmt 2>&1 | tee /tmp/backend-5-2-1-final-check-fmt.out -/// Generic queue adapter parameterised over plan type and provider. -#[derive(Debug, Clone)] -pub struct GenericApalisRouteQueue { /* 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` using + `apalis-postgres` PostgreSQL storage. +- Queue payload serialization failures map to `JobDispatchError::Rejected`. +- PostgreSQL, SQLx, or Apalis enqueue failures map to + `JobDispatchError::Unavailable`. +- The domain and inbound layers do not import Apalis or SQLx. +- Request-path dispatch and worker consumption remain out of scope. +- Focused `rstest` unit tests pass. +- `rstest-bdd` queue scenarios pass against embedded PostgreSQL. +- Documentation accurately states the adapter's scope and operational + boundaries. +- `make check-fmt`, `make lint`, and `make test` pass. +- `coderabbit review --agent` has no unresolved in-scope concerns. +- `docs/backend-roadmap.md` marks only item 5.2.1 done. + +`proptest`, `kani`, and `verus` are not required for this milestone unless the +implementation introduces new pure invariants beyond serialization and +infrastructure error mapping. The queue adapter is primarily an external +system integration, so focused unit tests and PostgreSQL-backed behavioural +tests provide the appropriate level of rigour for 5.2.1. + +## Idempotence and recovery + +The validation commands are safe to rerun. `PostgresStorage::setup()` is +expected to be idempotent. Embedded PostgreSQL tests provision isolated +databases through the existing test harness. + +If a command is interrupted, rerun the same command with the same log path or a +new path with an `-attempt-N` suffix. If embedded PostgreSQL setup fails for an +environmental reason, record the exact log path in `Surprises & Discoveries` +and stop rather than replacing the repository's test infrastructure. + +No destructive Git commands are required. Do not use `git reset --hard` or +`git checkout --` to discard work unless the user explicitly asks for that +operation. + +## Progress + +- [x] 2026-05-21: Loaded `leta`, `rust-router`, `hexagonal-architecture`, + `execplans`, `firecrawl-mcp`, `pr-creation`, and supporting Rust skills for + planning. +- [x] 2026-05-21: Created the Leta workspace for this worktree. +- [x] 2026-05-21: Renamed the local branch to + `backend-5-2-1-apalis-route-queue`. +- [x] 2026-05-21: Used a Wyvern planning team to inspect roadmap/docs, current + queue code, and test coverage. +- [x] 2026-05-21: Used Firecrawl to verify current Apalis/PostgreSQL + documentation and queue concepts. +- [x] 2026-05-21: Drafted this approval-gated ExecPlan. +- [x] 2026-05-21: Validated the draft with `make check-fmt` + (`/tmp/check-fmt-wildside-backend-5-2-1-apalis-route-queue.out`). +- [x] 2026-05-21: Validated the draft with `make lint` + (`/tmp/lint-wildside-backend-5-2-1-apalis-route-queue.out`). +- [x] 2026-05-21: Validated the draft with `make test` + (`/tmp/test-wildside-backend-5-2-1-apalis-route-queue.out`; 1220 tests + passed, 4 skipped). +- [x] 2026-05-21: Attempted `coderabbit review --agent` twice for the plan + milestone; both attempts were blocked by a recoverable service rate limit. +- [ ] Await explicit approval before implementation or roadmap closure. +- [ ] Run targeted queue unit and behavioural tests. +- [ ] Run CodeRabbit after targeted adapter verification. +- [ ] Reconcile architecture and developer documentation if needed. +- [ ] Run full `make check-fmt`, `make lint`, and `make test` gates. +- [ ] Run CodeRabbit after full gates. +- [ ] Mark roadmap item 5.2.1 done after gates pass. +- [ ] Complete 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. + +## 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. + +## Outcomes & Retrospective + +Not yet populated. This section must be completed after approved execution, +full validation, CodeRabbit review, roadmap closure, and pull request updates. From cbfd070fe7f1d757aa6af34520136a7ac917c6a9 Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 26 May 2026 12:48:12 +0200 Subject: [PATCH 2/6] Record Apalis queue validation progress Mark the approved execution as in progress and capture the targeted adapter validation evidence. Record the first implementation CodeRabbit review result so the ExecPlan can be resumed from the documented state. --- .../backend-5-2-1-apalis-route-queue.md | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) 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 c02a6b16..75c2cfec 100644 --- a/docs/execplans/backend-5-2-1-apalis-route-queue.md +++ b/docs/execplans/backend-5-2-1-apalis-route-queue.md @@ -5,7 +5,7 @@ This ExecPlan (execution plan) is a living document. The sections `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds. -Status: DRAFT +Status: IN PROGRESS This plan covers roadmap item 5.2.1 only: `Implement RouteQueue using Apalis with PostgreSQL backend, replacing the @@ -436,9 +436,26 @@ operation. passed, 4 skipped). - [x] 2026-05-21: Attempted `coderabbit review --agent` twice for the plan milestone; both attempts were blocked by a recoverable service rate limit. -- [ ] Await explicit approval before implementation or roadmap closure. -- [ ] Run targeted queue unit and behavioural tests. -- [ ] Run CodeRabbit after targeted adapter verification. +- [x] 2026-05-26: Received explicit approval to implement this ExecPlan. +- [x] 2026-05-26: Confirmed branch + `backend-5-2-1-apalis-route-queue`, clean worktree, and existing Leta + workspace. +- [x] 2026-05-26: Completed baseline audit. `ApalisRouteQueue` and + `GenericApalisRouteQueue` are present under `backend/src/outbound/queue`, + current documentation describes the Apalis/PostgreSQL boundary, and + `docs/backend-roadmap.md` still lists 5.2.1 as open. +- [x] 2026-05-26: Ran targeted queue unit tests with + `cargo test -p backend outbound::queue --lib` + (`/tmp/backend-5-2-1-queue-unit.out`); 5 passed. +- [x] 2026-05-26: Ran targeted Apalis queue BDD tests with + `cargo test -p backend --test route_queue_apalis_bdd` + (`/tmp/backend-5-2-1-queue-bdd.out`); 9 passed. +- [x] 2026-05-26: Ran applicable pre-review gates: + `make check-fmt` (`/tmp/backend-5-2-1-pre-coderabbit-check-fmt.out`) and + `make markdownlint` + (`/tmp/backend-5-2-1-pre-coderabbit-markdownlint.out`); both passed. +- [x] 2026-05-26: Ran `coderabbit review --agent` after targeted adapter + verification; review completed with 0 findings. - [ ] Reconcile architecture and developer documentation if needed. - [ ] Run full `make check-fmt`, `make lint`, and `make test` gates. - [ ] Run CodeRabbit after full gates. @@ -465,6 +482,11 @@ operation. 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. + ## Decision Log - Decision: Treat this plan as an approval-gated validation and closure plan @@ -494,6 +516,13 @@ operation. 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. + ## Outcomes & Retrospective Not yet populated. This section must be completed after approved execution, From f29c996ada8b702682482b2b00c194c920ff27e8 Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 26 May 2026 12:50:24 +0200 Subject: [PATCH 3/6] Reconcile Apalis queue documentation Correct the developer guide's visibility notes for `GenericApalisRouteQueue` so they match the exported BDD harness seam. Update the ExecPlan with the documentation check evidence and the user-guide applicability decision. --- docs/developers-guide.md | 8 ++--- .../backend-5-2-1-apalis-route-queue.md | 31 +++++++++++++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) 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 75c2cfec..4a18e0f8 100644 --- a/docs/execplans/backend-5-2-1-apalis-route-queue.md +++ b/docs/execplans/backend-5-2-1-apalis-route-queue.md @@ -274,7 +274,9 @@ Review and update only documentation that is stale or inaccurate: - `docs/wildside-backend-architecture.md` must explain that 5.2.1 covers the driven queue adapter, not worker consumption or request dispatch. - `docs/developers-guide.md` must explain adapter boundaries, dependency - visibility, and how to run the queue tests. + visibility, and how to run the queue tests. In the approved implementation + baseline, `GenericApalisRouteQueue` 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 @@ -456,7 +458,15 @@ operation. (`/tmp/backend-5-2-1-pre-coderabbit-markdownlint.out`); both passed. - [x] 2026-05-26: Ran `coderabbit review --agent` after targeted adapter verification; review completed with 0 findings. -- [ ] Reconcile architecture and developer documentation if needed. +- [x] 2026-05-26: Reconciled `docs/developers-guide.md` with the current + queue adapter API by documenting that `GenericApalisRouteQueue` 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. - [ ] Run full `make check-fmt`, `make lint`, and `make test` gates. - [ ] Run CodeRabbit after full gates. - [ ] Mark roadmap item 5.2.1 done after gates pass. @@ -487,6 +497,15 @@ operation. `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 @@ -523,6 +542,14 @@ operation. 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 Not yet populated. This section must be completed after approved execution, From 4f41ac46b011d1416ae77b6af6462ca5161429df Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 26 May 2026 13:04:03 +0200 Subject: [PATCH 4/6] Record full Apalis queue gate evidence Capture the successful full formatting, lint, and test gate results in the ExecPlan before requesting the post-gate review. --- docs/execplans/backend-5-2-1-apalis-route-queue.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 4a18e0f8..8a9d22db 100644 --- a/docs/execplans/backend-5-2-1-apalis-route-queue.md +++ b/docs/execplans/backend-5-2-1-apalis-route-queue.md @@ -467,7 +467,14 @@ operation. - [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. -- [ ] Run full `make check-fmt`, `make lint`, and `make test` gates. +- [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. - [ ] Run CodeRabbit after full gates. - [ ] Mark roadmap item 5.2.1 done after gates pass. - [ ] Complete outcomes and retrospective. From 77b2f0d43e7c2a6cbc0cf07a16dee36a1496537b Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 26 May 2026 13:14:38 +0200 Subject: [PATCH 5/6] Close Apalis route queue roadmap item Mark backend roadmap item 5.2.1 done after the approved validation gates passed, and complete the ExecPlan with final gate evidence plus the implementation retrospective. --- docs/backend-roadmap.md | 2 +- .../backend-5-2-1-apalis-route-queue.md | 37 ++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) 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/execplans/backend-5-2-1-apalis-route-queue.md b/docs/execplans/backend-5-2-1-apalis-route-queue.md index 8a9d22db..8ddcbcd3 100644 --- a/docs/execplans/backend-5-2-1-apalis-route-queue.md +++ b/docs/execplans/backend-5-2-1-apalis-route-queue.md @@ -5,7 +5,7 @@ This ExecPlan (execution plan) is a living document. The sections `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 @@ -475,9 +475,16 @@ operation. (`/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. -- [ ] Run CodeRabbit after full gates. -- [ ] Mark roadmap item 5.2.1 done after gates pass. -- [ ] Complete outcomes and retrospective. +- [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: Completed outcomes and retrospective. ## Surprises & discoveries @@ -559,5 +566,23 @@ operation. ## Outcomes & Retrospective -Not yet populated. This section must be completed after approved execution, -full validation, CodeRabbit review, roadmap closure, and pull request updates. +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, and full quality gates. `docs/backend-roadmap.md` +now marks only item 5.2.1 as done. From 01ad7867514dae4514b6210cd67f7bdf657b5111 Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 26 May 2026 13:21:31 +0200 Subject: [PATCH 6/6] Record final queue review evidence Add the final CodeRabbit closure result to the ExecPlan so the implementation record matches the completed review sequence. --- docs/execplans/backend-5-2-1-apalis-route-queue.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 8ddcbcd3..830f7346 100644 --- a/docs/execplans/backend-5-2-1-apalis-route-queue.md +++ b/docs/execplans/backend-5-2-1-apalis-route-queue.md @@ -484,6 +484,8 @@ operation. (`/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 @@ -584,5 +586,5 @@ 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, and full quality gates. `docs/backend-roadmap.md` -now marks only item 5.2.1 as done. +documentation reconciliation, full quality gates, and final roadmap closure. +`docs/backend-roadmap.md` now marks only item 5.2.1 as done.