Skip to content

Backlog Triage — 2026-05-03 (post-N7) ​

⚠ Historical (2026-05-19 convergence pass): документ создан до пивота к ADR-024; TD-записи про bucket-arb / maker / maker-rebate / merge-arb / TUI отражают пред-пивотную модель и архивны. Сохранён для аудит-трейла.

Scope: orchestration artefact. No code changes. Maps every open TD/FE entry in tech-debt-backlog.md onto a work-readiness category and surfaces the recommended next sprint item. Produced as the final phase (N7) of the next-iteration polish series (N1..N7). Baseline at write-time: 899 pm-bot lib tests green + 167 integration; latest commit e53b73b.

Audience: future tech-lead picking up the next sprint. The table is the single source of truth — read top-to-bottom, then jump to the "Recommended next item" deep-dive.

1. Categorisation framework ​

CatLabelDefinitionTypical scope
AFix-readyLOC ≤ 100, no design ambiguity, one binary touched, behaviour fully specified by an existing ADR / FSD / wire contractOne sprint, one PR
BDesign-requiredNeeds an ADR or operator sign-off before coding starts; multiple invariants in playMulti-sprint, ADR first
COperator-drivenWon't ship without an explicit operator ask (UX preference, integration choice, deployment policy)Wait for trigger
DQuality-gateTooling debt / coverage gap, low business value but non-zero correctness liftBackground work

Items already marked RESOLVED or OBSOLETE in tech-debt-backlog.md are not re-categorised here — the triage focuses on items still labelled open plus a verification audit of stale entries.

2. Stale-entry verification (audit) ​

Before categorising, every TD/FE row was verified against the current tree. Findings:

#Backlog statusVerificationAudit verdict
TD-1open (LOW)crates/ contains only 6 crates: pm-bot, pm-bot-events, pm-cli, pm-common, pm-dashboard, pm-sdk. No pm-orchestrator directory.OBSOLETE — pm-orchestrator was removed from the workspace; no failing test target exists. Recommend marking OBSOLETE in the backlog header refresh.
TD-5open (MED)find … -name "bucket_arb_stress*" → 0 hits. Confirms the file was deleted in TD3-A as documented.Still open as a coverage gap, but the wording can be tightened: there is no file to "re-port from", only edge-case scenarios to re-author against the new hook API.
TD-17open (LOW)io/webhook/server.rs:395 and io/local_api/mod.rs:332 already share the same impl Future<Output = ()> shutdown signature with axum::Server::with_graceful_shutdown — the pattern is uniform today.OBSOLETE for the "backport pattern" framing. The remaining gap (a real shutdown signal vs std::future::pending::<()>()) is part of the broader session_shutdown_tx work flagged inline in main.rs:929, not a webhook backport. Recommend reframing as "thread shutdown signal through local_api::serve + metrics_loop".
TD-18open (post-Phase-7c)io/local_api/metrics_loop.rs exists (202 LOC), exports metrics_loop + metrics_loop_with_interval, re-exported from io/local_api/mod.rs:74. Grep on main.rs shows local_api::serve spawn at line 977 but no metrics_loop spawn.Half-shipped, ready to land. The function is implemented and unit-testable; only the tokio::spawn(metrics_loop(state.clone(), pending::<()>())) wire-up next to the existing local_api::serve spawn is missing. Estimated remaining LOC: 10-15 in main.rs.
TD-6open (LOW)crates/pm-bot/src/config/loader.rs:356-357 parses both BUCKET_ARB_DCA_ENABLED + BUCKET_ARB_DCA_DROP_PCT; crates/pm-bot/src/config/strategy/bucket_arb.rs:42-49 documents the no-op contract.Open — by design per INV-10. Documented as inert.
TD-7open (MED)domain/strategies/maker_rebate.rs:78-99 shows RebatePair struct + pairs: Arc<DashMap<String, RebatePair>> field; evaluate_periodic_impl at line 174 emits one leg per cid (single-side parity comment at line 162-164).Open — accurately reflects code.

Other open items (TD-2, TD-3, TD-4, TD-8, TD-9..TD-14) — backlog text matches code state; no audit findings.

Cleanup note (post-N7): TD-15 (CSV / Parquet export), TD-16 (multi-wallet aggregation), TD-25 (Telegram backend), and TD-26 (SMTP backend) have since been removed from the canonical tech-debt-backlog.md — they were never user-scoped tasks. Any rows below that reference those numbers are historical and should be ignored.

3. Triage table — every open item ​

#CatRationale
TD-1(mark OBSOLETE in next backlog edit)Crate removed from workspace; backlog row is stale.
TD-2B — Design-requiredPricing-mode persistence on retry is a small INV-20 invariant change but cuts across BucketArbStrategy::on_partial_reject + the FSM Retry branch + the placement helper. Needs a one-page ADR-amendment to bucket-arbitrage.md clarifying whether retry inherits the original POST's pricing mode or always degrades. Operator workaround documented; no business pressure.
TD-3A — Fix-readyDrift-cancel ceiling math is a single helper in bucket_arb/scanner_hooks.rs (~30-50 LOC). Behaviour fully specified — when immediate_fill_enabled=true the cancel threshold should derive from best_ask rather than maker_safe_price. No design ambiguity, single binary. Couple to TD-2 only if the same sprint owner picks both up.
TD-4C — Operator-drivenPure UX cosmetic (TUI badge differentiation). Won't ship without explicit operator request — ratatui badge palette already covers existing semantics. Defer until an operator complains during a live run.
TD-5D — Quality-gateCoverage debt. Re-author 6-14 ST-0* edge-case scenarios (mixed-tick rounding, touched books, phantom Gamma, augmented neg-risk, winner gate, leader logic) as integration tests against BucketArbStrategy + MockRepository + on_ws_book. Estimated 400-600 LOC of test code, no production change. Background work.
TD-6C — Operator-drivenInert by design (INV-10: bucket-arb holds to resolution). Will only move if an operator explicitly requests DCA semantics for bucket-arb — at which point the work is "implement DcaExtension for BucketArb" (Cat B). For now, leave inert.
TD-7B — Design-requiredTwo-sided maker-rebate placement is the single largest open product question in the backlog. The RebatePair struct + pairs map are wired but evaluating on a per-condition basis requires (a) deciding the leg-pair admission predicate (matched-edge vs split-edge), (b) deciding cancel-on-one-fill semantics (auto-cancel the unfilled leg vs let it ride), (c) sizing arithmetic when the two legs face different best_bids. Needs an ADR amendment to maker-rebate.md first.
TD-8C — Operator-drivenWorktree cleanup is an operator hygiene task, not a code item. Belongs in an operator runbook, not the backlog. Recommend moving to local-dashboard-runbook.md § "Maintenance" on the next runbook edit.
TD-9B — Design-requiredLog streaming requires schema decisions (per-session vs global logs table, retention, indexability) plus a tracing-subscriber appender choice. ADR-005 deferred this explicitly to post-MVP. ~400 LOC scope. No operator pressure surfaced post Phase 7c shipping.
TD-10C — Operator-drivenSide-by-side comparison shipped in Phase 6 commit db535a4 covers the operator's stated need. Overlay is "nice to have" — defer until an operator says side-by-side is insufficient for momentum analysis.
TD-11A — Fix-readyAuto-prune retention is a single tokio interval task in pm-bot + a pm-cli journal --gc subcommand. Behaviour specified in the runbook; ~150 + ~150 LOC. Opt-in env var (BOT_JOURNAL_AUTO_PRUNE_ENABLED=false default). No design ambiguity. Strong candidate for a follow-on sprint after TD-18.
TD-12(RESOLVED post-cleanup — engine + LogSink shipped 2026-05-03)Closed scope: in-process broadcast + four stock rules + LogSink baseline. Custom external sinks via the public AlertSink trait — no external-network sinks bundled. See alerts-2026-05-03.md.
TD-13B — Design-requiredOrder management from UI is the highest-risk open item: ad-hoc operator actions can violate strategy invariants (cancel-on-fill races, mid-flight modify breaking FSM state). Requires (a) ADR scoping cancel-only vs cancel+modify, (b) audit-log schema, (c) authn/z review. Defer until an operator ask.
TD-14A — Fix-readyRisk metrics (Sharpe, Sortino, max drawdown) are a SQL VIEW on existing pnl_snapshots + a JS widget. Computation rules are textbook; no design ambiguity. ~200 LOC. Good "second-up" candidate after TD-18.
TD-17(mark REFRAMED in next backlog edit)Webhook + local_api already share the same axum::Server::with_graceful_shutdown(impl Future<()>) pattern. The actual remaining gap — wiring a real shutdown signal instead of std::future::pending::<()>() — is a different scope (touches metrics_loop, local_api::serve, webhook::serve, and the receipt watcher). Recommend reframing as "thread session_shutdown_tx through long-lived spawn sites". Cat A under the reframed scope (~80-120 LOC).
TD-18A — Fix-readyTopmost candidate. See § 4 deep-dive.
FE-1B — Design-requiredMulti-wallet provisioning layer on top of pm-sdk encrypted-keys. Needs an ADR for the SQLite wallets schema, master-password unwrap flow, key-rotation policy. Upstream of FE-3.
FE-2B — Design-requiredSQLite-based strategy config storage is a substantial architectural change — moving from env-var fail-fast (current discipline) to a database source-of-truth requires an ADR covering (a) live re-config without restart, (b) validation re-run on update, (c) operator audit log.
FE-3B — Design-requiredMulti-wallet support per process needs design for per-wallet OrderManager, SignerPool, position bookkeeping. Couples to FE-1.
FE-4B — Design-requiredDeclarative Condition enum rule engine. Substantial design work (25+ leaf conditions), pure forward-looking. No current operator demand.
FE-5C — Operator-drivenPositionManager actor for cross-strategy budget arbitration is moot until FE-3 is decided. No code path currently has the contention.

Why TD-18 first ​

  • Lowest unlock cost in the entire backlog. metrics_loop is already implemented (202 LOC at crates/pm-bot/src/io/local_api/metrics_loop.rs) with a clear API surface (metrics_loop + metrics_loop_with_interval test variant). The only missing piece is a tokio::spawn next to the existing local_api::serve spawn at crates/pm-bot/src/main.rs:972-983.
  • Highest observable lift. Phase 7c (commit 9a85362) shipped the full MetricsTick wire format — 7 counters + is_emergency banner
    • RTDS observability fields (TD-21 N3 follow-on). The dashboard already ships reducers + widgets for every field. Without TD-18 the widget code is "wire-correct but speculative" (per TD-18 backlog text). Landing the spawn flips the entire latency / counter / RTDS observability surface from /seed-poll cadence (~5s) to live 1Hz SSE — a step change for operators reviewing a live session.
  • Bit-identical to opt-out path. When BOT_LOCAL_API_ENABLED=false (default), the spawn is gated off the same if config.local_api_enabled block as local_api::serve. Trading hot path is unaffected.

Sprint scope ​

  • Production code: ~10-15 LOC in main.rs — one tokio::spawn inside the existing Some(journal_handle) => { … } branch (same block that builds LocalApiState), reusing the state clone for the spawn. Keep the same std::future::pending::<()>() shutdown future as local_api::serve for lifetime parity. Capture the JoinHandle in a sibling let _metrics_loop_keepalive = … similar to _local_api_keepalive at line 995.
  • Tests: the existing unit tests on metrics_loop_with_interval (using a oneshot::Receiver shutdown signal) cover the function. New integration test optional — end-to-end coverage via /seed + SSE is already exercised in pm-bot-events integration tests.
  • Docs: one-line entry in local-dashboard-runbook.md § "SSE event taxonomy" noting the new 1Hz metrics_tick cadence; bump TD-18 to RESOLVED in the backlog.

Quality gate ​

  • cargo check --workspace — must remain clean.
  • cargo test -p pm-bot --lib — 899 baseline preserved.
  • Manual smoke (operator): BOT_LOCAL_API_ENABLED=true cargo run -p pm-bot -- --dry-run then curl -N http://127.0.0.1:31339/api/local/stream and confirm event: metrics_tick envelopes appear at 1Hz cadence with the expected counter / RTDS / emergency-flag fields populated.

Out of scope ​

  • Real shutdown signal threading (overlaps with TD-17 reframed).
  • Cadence configuration via env (defer until an operator needs it — 1Hz is the ADR Appendix B canonical value).
  • Schema changes to MetricsTick (Phase 7c is the canonical wire format; a v bump is the only correct way to extend).

5. Defer until operator direction ​

These items must NOT be picked up speculatively — they require an explicit operator trigger:

#Trigger to wait for
TD-4Operator complaint that immediate-fill orders are visually indistinguishable from regular limit-GTD in the TUI.
TD-6Operator request for DCA semantics on bucket-arb (would override INV-10).
TD-8Operator hygiene task — recommend moving to runbook, not picking up as code work.
TD-10Operator says side-by-side comparison (Phase 6 db535a4) is insufficient for momentum analysis.
TD-13Operator asks for cancel / modify buttons in the orders table — must specify cancel-only vs full intervention scope.
FE-5Depends on FE-3 making the contention real.

6. Forward-look after TD-18 ​

A reasonable next-three-sprint ordering (purely advisory; operator priorities override):

  1. TD-18 (this triage's recommendation) — flip live SSE from speculative to production.
  2. TD-3 (Cat A, ~30-50 LOC) — close the immediate-fill drift-cancel math gap. Operator workaround documented but the fix is small.
  3. TD-11 (Cat A, ~300 LOC) — auto-prune retention. SQLite at ~1 GB/day means manual GC will eventually bite. Opt-in env var keeps the change conservative.

Cat-D items (TD-5 coverage backfill) belong on a long-running quality-gate track, not interleaved with feature sprints.

7. Verification after this triage ​

  • cargo check --workspace — clean (verified at write-time).
  • cargo test -p pm-bot --lib — 899 lib tests pass, 1 ignored (verified at write-time).
  • Pure documentation change — no runtime behaviour affected.

8. Cross-references ​


POST-USER-DECISION addendum (2026-05-03) ​

Added after this triage shipped. The Cat A / B / C / D framework in §§ 1–7 above was produced before the post-completion user-decision sweep; this addendum records the actual user decisions for every open and deferred row, so future tech-leads can use a single page as source of truth.

Re-categorisation summary ​

#Original catUser decisionSprint phase (if queued)
TD-1OBSOLETE (audit)unchanged—
TD-2B (Design-required)IMPLEMENT — user-confirmed scopeX3.1
TD-3A (Fix-ready)IMPLEMENT — compound with detector wiringX3.2
TD-4C (Operator-driven)IMPLEMENT — user reframed as queuedX3.3
TD-5D (Quality-gate)IMPLEMENT — user-confirmed coverage close-outX4
TD-6C (Operator-driven)unchanged — RESOLVED earlier—
TD-7B (Design-required)IMPLEMENT — write ADR-011 firstX5
TD-8C (Operator-driven)unchanged — RESOLVED earlier—
TD-9B (Design-required)MERGED into TD-27 — reframed: trading-terminal-style analytical dashboard, NOT raw log streamingX1.2
TD-10C (Operator-driven)MERGED into TD-27 — using existing PrimeVue ui-components libraryX1.3
TD-11A (Fix-ready)unchanged — RESOLVED—
TD-12(RESOLVED)unchanged—
TD-13B (Design-required)MERGED into TD-27 — scoped to cancel-only (NOT modify)X1.4
TD-14A (Fix-ready)MERGED into TD-27 — analytical widget under dashboard rebuildX1.5
TD-17(REFRAMED)unchanged—
TD-18A (Fix-ready)unchanged — RESOLVED—
TD-27(added post-cleanup)IMPLEMENT — production blocker; one more scope round (X1.0) before codingX1
FE-1B (Design-required)IMPLEMENT — bundled with FE-2 + CLI management + migration toolingX2.1–X2.2
FE-2B (Design-required)IMPLEMENT — replaces env-var fail-fast, ushers in WebUI configuratorX2.1, X2.3–X2.5
FE-3B (Design-required)DEFERRED — gated on FE-1 + FE-2 + WebUI configuratorfuture roadmap
FE-4B (Design-required)DEFERRED — picks up after FE-2 lands; pre-existing impl in archived pm-orchestratorfuture roadmap
FE-5C (Operator-driven)IMPLEMENT (refactored) — passive StrategyManager budget predicate (NOT actor); reinvestment toggle deferredimplementation queue

What the original triage got right ​

  • TD-18 as topmost candidate — the recommendation to land the metrics_loop tokio::spawn first ranked correctly; it was RESOLVED in the same sweep (commit 468619f).
  • Cat A vs B vs C vs D framework — the categorisation was sound for a generic priority-ordering exercise. Operator's actual decisions, however, were driven less by LOC / design-readiness and more by product-strategic groupings (TD-27 dashboard rebuild bundling, FE-1 + FE-2 infrastructure bundling).

What changed in user direction ​

  • TD-9 is no longer raw log streaming — it is a "trading terminal-style analytical dashboard with reactive data updating" (i.e. distilled analytical info from the log / event stream, not the raw lines). Implementation under TD-27 X1.2.
  • TD-13 is scoped cancel-only (NOT modify) — half the prior "B Design-required" risk surface (cancel-on-fill races, mid-flight modify breaking FSM state) is eliminated by removing modify from scope.
  • TD-10 uses existing polymarket-copy-trading/packages/ui-components library (extend, don't reinvent). Rules out the "vanilla SVG vs Chart.js vs PrimeVue-bundled" decision in favour of a known stack.
  • FE-1 + FE-2 ship together — the design-required + design-required pair is bundled into a single sprint (X2) because they share encrypted-SQLite + master-password infrastructure.
  • FE-5 is NOT actor — user explicitly declined the mpsc + oneshot serialised accounting model. Replaced with a passive StrategyManager budget-limit predicate (~200 LOC, no actor ceremony).
  • FE-3 + FE-4 deferred with documented architecture — both have pre-existing implementations in archived pm-orchestrator; salvage paths documented. Picking them up out-of-order would be premature.

Source of truth going forward ​

For sprint planning after this date, read in this order:

  1. tech-debt-backlog.md §§ "Confirmed implementation queue", "Production blocker — pending operator scope refinement", "Deferred to roadmap" — current row state.
  2. next-iteration-plan.md (local plan, not committed) §§ "NEW Phase X1..X6" — sprint sequencing + per-phase scope.
  3. production-readiness-2026-05-03.md § 6 — current open known limitations + workarounds.

The Cat A / B / C / D table above (§§ 2–6 of this triage) is superseded by the user-decision categorisation. It remains in this document for blame / reference history but should not drive sprint selection.