Roadmap
Each task is one GitHub issue, sized for one agent session (≤ 30 min) and one PR that leaves main
green. The issue is the specification (ADR-0013): a YAML manifest,
the decisions already made, acceptance criteria mapped to named tests, and its dependencies (also
recorded as GitHub "blocked by" links). This page is the index.
Start a session with:
Implement issue #N. Follow AGENTS.md.
A task is ready when every issue it depends on is closed. Tasks without a path between them in the graph can run in parallel, for example M1-T1 ∥ M1-T2, M1-T6 ∥ M1-T4, and M3-T1 alongside M1-T4…T7.
M0, the baseline, is done: the monorepo skeleton, contracts, wallet stub, schema, docs, CI and
Pages, DecisionModeRouter, and a green ./gradlew presubmit. Every open question is resolved by a
working assumption and an ADR (PRD §7).
Dependency graph
flowchart LR
subgraph M1
M1T1["M1-T1"]
M1T2["M1-T2"]
M1T3["M1-T3"]
M1T4["M1-T4"]
M1T5["M1-T5"]
M1T6["M1-T6"]
M1T7["M1-T7"]
end
subgraph M2
M2T1["M2-T1"]
M2T2["M2-T2"]
M2T3["M2-T3"]
M2T4["M2-T4"]
M2T5["M2-T5"]
end
subgraph M3
M3T1["M3-T1"]
M3T2["M3-T2"]
M3T3["M3-T3"]
M3T4["M3-T4"]
M3T5["M3-T5"]
end
M1T2 --> M1T3
M1T1 --> M1T4
M1T3 --> M1T4
M1T4 --> M1T5
M1T1 --> M1T6
M1T5 --> M1T7
M1T6 --> M1T7
M1T4 --> M2T1
M2T1 --> M2T2
M2T2 --> M2T3
M1T7 --> M2T3
M1T7 --> M2T4
M2T2 --> M2T4
M2T3 --> M2T5
M2T4 --> M2T5
M1T2 --> M3T1
M1T3 --> M3T1
M3T1 --> M3T2
M1T7 --> M3T2
M3T2 --> M3T3
M3T3 --> M3T4
M3T4 --> M3T5
M2T1 --> M3T5
M2T4 --> M3T5
M1 · Phase 1: correct and resilient on the existing wallet API
Exit criteria: A till can pay, be declined and reverse. The wallet's slow, down and ambiguous modes produce fast, correct responses, and no money is lost or doubled.
| Task | Issue | Title | Depends on | Proves |
|---|---|---|---|---|
| M1-T1 | #1 | Local ledger repositories and inbound idempotency gate | — | FR-2, NFR-6, F6, F7, F8 |
| M1-T2 | #2 | Wallet v1 HTTP client with deadline-capped timeouts and outcome classification | — | NFR-1, NFR-4, FR-3, FR-4 |
| M1-T3 | #3 | Resilient wallet adapter: circuit breaker and bulkheads | M1-T2 | NFR-2, NFR-3, FR-6, F2 |
| M1-T4 | #4 | POST /v1/payments end to end with DirectDebitStrategy | M1-T1, M1-T3 | FR-1, FR-2, FR-3, FR-4, FR-5, FR-6, NFR-1, NFR-6, F1, F6 |
| M1-T5 | #5 | Payment read and reversal intake (tombstones, advisory lock) | M1-T4 | FR-7, FR-8, FR-9, FR-10, F10 |
| M1-T6 | #6 | Postgres work queue engine (SKIP LOCKED, lease, backoff, DEAD) | M1-T1 | NFR-7 |
| M1-T7 | #7 | Compensation workers (RESOLVE_UNKNOWN, REVERSE) and stale-request sweeper | M1-T5, M1-T6 | FR-5, FR-10, NFR-4, F4, F5 |
M2 · Operate it: switches, stand-in, observability, failure proof
Exit criteria: On-call can tell a wallet outage from customer behaviour, orphaned money is visible, safety switches work without a deploy, and every row of the fault matrix is an automated test.
| Task | Issue | Title | Depends on | Proves |
|---|---|---|---|---|
| M2-T1 | #8 | Runtime switches stored in Postgres (kill switch, cohort %, stand-in on/off) | M1-T4 | FR-12, FR-15 |
| M2-T2 | #9 | Degradation policy and stand-in approval with atomic exposure caps (off by default) | M2-T1 | FR-14, FR-15 |
| M2-T3 | #10 | Stand-in post-recovery debit worker and stand-in reversal | M2-T2, M1-T7 | FR-15, FR-10 |
| M2-T4 | #11 | Metrics, Prometheus alert rules and orphan reconciler | M1-T7, M2-T2 | NFR-8, G2 |
| M2-T5 | #12 | Failure-mode test suite (fault matrix) and k6 load smoke | M2-T3, M2-T4 | G1, G2, G3, G4, F1, F2, F3, F9, F13 |
M3 · Phase 2: hold/capture and cutover
Exit criteria: HOLD_CAPTURE works end to end against the wallet v2 stub and can be ramped by cohort and rolled back with the kill switch. Production enablement is gated on Wallet sandbox contract tests (A9).
| Task | Issue | Title | Depends on | Proves |
|---|---|---|---|---|
| M3-T1 | #13 | Wallet v2 (holds) HTTP client and resilient adapter | M1-T2, M1-T3 | FR-11 |
| M3-T2 | #14 | HoldCaptureStrategy: hold-based decision and hold compensation | M3-T1, M1-T7 | FR-11, FR-4, FR-5 |
| M3-T3 | #15 | Capture worker (HELD → CAPTURING → CAPTURED) | M3-T2 | FR-11, FR-13, F11 |
| M3-T4 | #16 | Hold reaper and TTL safety check at startup | M3-T3 | FR-13 |
| M3-T5 | #17 | Cutover wiring: mixed-mode proof, kill switch mid-flight, runbook | M3-T4, M2-T1, M2-T4 | FR-12, G5, F12 |
Definition of done (every task)
./gradlew presubmitis green, including the Testcontainers ITs, which must run and not be skipped.- Each acceptance criterion has a test whose display name starts with
AC-nand a requirement id. - Contract changes go into
api/**first and code second. The stub is updated in the same PR. - A behaviour change that diverges from an ADR or a PRD assumption needs an ADR or a PRD update in the same PR.
- The task's
TODO(Mx-Ty)markers are closed.
Deferred (not scheduled)
- Outbox relay to Kafka (Debezium or a polling publisher), when a consumer exists (A13, ADR-0011).
- Daily wallet-statement reconciliation (needs a statement API from the Wallet team).
- Risk/fraud dependency, using the same primitives with its own bulkhead and breaker.
- Adaptive concurrency limits instead of a fixed bulkhead size.
- Archival of
DONEwork items, expired idempotency keys and published outbox rows. - Dashboards, and authentication of actuator endpoints (A15).