Тема
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.mdonto 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 commite53b73b.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
| Cat | Label | Definition | Typical scope |
|---|---|---|---|
| A | Fix-ready | LOC ≤ 100, no design ambiguity, one binary touched, behaviour fully specified by an existing ADR / FSD / wire contract | One sprint, one PR |
| B | Design-required | Needs an ADR or operator sign-off before coding starts; multiple invariants in play | Multi-sprint, ADR first |
| C | Operator-driven | Won't ship without an explicit operator ask (UX preference, integration choice, deployment policy) | Wait for trigger |
| D | Quality-gate | Tooling debt / coverage gap, low business value but non-zero correctness lift | Background 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 status | Verification | Audit verdict |
|---|---|---|---|
| TD-1 | open (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-5 | open (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-17 | open (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-18 | open (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-6 | open (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-7 | open (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
| # | Cat | Rationale |
|---|---|---|
| TD-1 | (mark OBSOLETE in next backlog edit) | Crate removed from workspace; backlog row is stale. |
| TD-2 | B — Design-required | Pricing-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-3 | A — Fix-ready | Drift-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-4 | C — Operator-driven | Pure 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-5 | D — Quality-gate | Coverage 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-6 | C — Operator-driven | Inert 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-7 | B — Design-required | Two-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-8 | C — Operator-driven | Worktree 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-9 | B — Design-required | Log 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-10 | C — Operator-driven | Side-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-11 | A — Fix-ready | Auto-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-13 | B — Design-required | Order 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-14 | A — Fix-ready | Risk 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-18 | A — Fix-ready | Topmost candidate. See § 4 deep-dive. |
| FE-1 | B — Design-required | Multi-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-2 | B — Design-required | SQLite-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-3 | B — Design-required | Multi-wallet support per process needs design for per-wallet OrderManager, SignerPool, position bookkeeping. Couples to FE-1. |
| FE-4 | B — Design-required | Declarative Condition enum rule engine. Substantial design work (25+ leaf conditions), pure forward-looking. No current operator demand. |
| FE-5 | C — Operator-driven | PositionManager actor for cross-strategy budget arbitration is moot until FE-3 is decided. No code path currently has the contention. |
4. Recommended next item — TD-18 (metrics_loop wire-up)
Why TD-18 first
- Lowest unlock cost in the entire backlog.
metrics_loopis already implemented (202 LOC atcrates/pm-bot/src/io/local_api/metrics_loop.rs) with a clear API surface (metrics_loop+metrics_loop_with_intervaltest variant). The only missing piece is atokio::spawnnext to the existinglocal_api::servespawn atcrates/pm-bot/src/main.rs:972-983. - Highest observable lift. Phase 7c (commit
9a85362) shipped the fullMetricsTickwire format — 7 counters +is_emergencybanner- 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.
- 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
- Bit-identical to opt-out path. When
BOT_LOCAL_API_ENABLED=false(default), the spawn is gated off the sameif config.local_api_enabledblock aslocal_api::serve. Trading hot path is unaffected.
Sprint scope
- Production code: ~10-15 LOC in
main.rs— onetokio::spawninside the existingSome(journal_handle) => { … }branch (same block that buildsLocalApiState), reusing thestateclone for the spawn. Keep the samestd::future::pending::<()>()shutdown future aslocal_api::servefor lifetime parity. Capture theJoinHandlein a siblinglet _metrics_loop_keepalive = …similar to_local_api_keepaliveat line 995. - Tests: the existing unit tests on
metrics_loop_with_interval(using aoneshot::Receivershutdown signal) cover the function. New integration test optional — end-to-end coverage via/seed+ SSE is already exercised inpm-bot-eventsintegration tests. - Docs: one-line entry in
local-dashboard-runbook.md§ "SSE event taxonomy" noting the new 1Hzmetrics_tickcadence; 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-runthencurl -N http://127.0.0.1:31339/api/local/streamand confirmevent: metrics_tickenvelopes 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; avbump 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-4 | Operator complaint that immediate-fill orders are visually indistinguishable from regular limit-GTD in the TUI. |
| TD-6 | Operator request for DCA semantics on bucket-arb (would override INV-10). |
| TD-8 | Operator hygiene task — recommend moving to runbook, not picking up as code work. |
| TD-10 | Operator says side-by-side comparison (Phase 6 db535a4) is insufficient for momentum analysis. |
| TD-13 | Operator asks for cancel / modify buttons in the orders table — must specify cancel-only vs full intervention scope. |
| FE-5 | Depends on FE-3 making the contention real. |
6. Forward-look after TD-18
A reasonable next-three-sprint ordering (purely advisory; operator priorities override):
- TD-18 (this triage's recommendation) — flip live SSE from speculative to production.
- TD-3 (Cat A, ~30-50 LOC) — close the immediate-fill drift-cancel math gap. Operator workaround documented but the fix is small.
- 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
tech-debt-backlog.md— full TD/FE register. Header refreshed in the same patch as this triage.production-readiness-checklist.md— "Known out-of-scope items" overlaps with TD-1..TD-5.local-dashboard-runbook.md— operator reference for the Phase 0..7c shipped surface that TD-18 completes.../adr/pm-bot-dashboard-integration.md— ADR-005 (canonicalMetricsTickwire format in Appendix B).
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 cat | User decision | Sprint phase (if queued) |
|---|---|---|---|
| TD-1 | OBSOLETE (audit) | unchanged | — |
| TD-2 | B (Design-required) | IMPLEMENT — user-confirmed scope | X3.1 |
| TD-3 | A (Fix-ready) | IMPLEMENT — compound with detector wiring | X3.2 |
| TD-4 | C (Operator-driven) | IMPLEMENT — user reframed as queued | X3.3 |
| TD-5 | D (Quality-gate) | IMPLEMENT — user-confirmed coverage close-out | X4 |
| TD-6 | C (Operator-driven) | unchanged — RESOLVED earlier | — |
| TD-7 | B (Design-required) | IMPLEMENT — write ADR-011 first | X5 |
| TD-8 | C (Operator-driven) | unchanged — RESOLVED earlier | — |
| TD-9 | B (Design-required) | MERGED into TD-27 — reframed: trading-terminal-style analytical dashboard, NOT raw log streaming | X1.2 |
| TD-10 | C (Operator-driven) | MERGED into TD-27 — using existing PrimeVue ui-components library | X1.3 |
| TD-11 | A (Fix-ready) | unchanged — RESOLVED | — |
| TD-12 | (RESOLVED) | unchanged | — |
| TD-13 | B (Design-required) | MERGED into TD-27 — scoped to cancel-only (NOT modify) | X1.4 |
| TD-14 | A (Fix-ready) | MERGED into TD-27 — analytical widget under dashboard rebuild | X1.5 |
| TD-17 | (REFRAMED) | unchanged | — |
| TD-18 | A (Fix-ready) | unchanged — RESOLVED | — |
| TD-27 | (added post-cleanup) | IMPLEMENT — production blocker; one more scope round (X1.0) before coding | X1 |
| FE-1 | B (Design-required) | IMPLEMENT — bundled with FE-2 + CLI management + migration tooling | X2.1–X2.2 |
| FE-2 | B (Design-required) | IMPLEMENT — replaces env-var fail-fast, ushers in WebUI configurator | X2.1, X2.3–X2.5 |
| FE-3 | B (Design-required) | DEFERRED — gated on FE-1 + FE-2 + WebUI configurator | future roadmap |
| FE-4 | B (Design-required) | DEFERRED — picks up after FE-2 lands; pre-existing impl in archived pm-orchestrator | future roadmap |
| FE-5 | C (Operator-driven) | IMPLEMENT (refactored) — passive StrategyManager budget predicate (NOT actor); reinvestment toggle deferred | implementation queue |
What the original triage got right
- TD-18 as topmost candidate — the recommendation to land the
metrics_looptokio::spawnfirst ranked correctly; it was RESOLVED in the same sweep (commit468619f). - 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-componentslibrary (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
StrategyManagerbudget-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:
tech-debt-backlog.md§§ "Confirmed implementation queue", "Production blocker — pending operator scope refinement", "Deferred to roadmap" — current row state.next-iteration-plan.md(local plan, not committed) §§ "NEW Phase X1..X6" — sprint sequencing + per-phase scope.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.