# Implementation Turn: Quote lifecycle investigator and missed quote follow-up Status: open Opened: 2026-06-12 ## Goal Improve the dashboard Quote lifecycle view so operators can pin, search, and follow missed or pending quotes from durable evidence even after live rows move on. ## Selected Backlog Items - [I012] Quotes and execution analytics workbench: live quote tape, quote count and volume windows, size-bucket distributions, oracle-deviation views, per-quote decision and execution trace, and matched-only filters. This turn intentionally implements only the per-quote lifecycle investigation and missed-quote follow-up part of I012. Remaining analytics-workbench work has been re-added to backlog as follow-on scope. ## Constraints - Preserve trading behavior: no strategy, pricing, threshold, notional, inventory, arming, signer, pair enablement, quote skip, or relay policy changes. - Do not add active pair, edge, notional, latency, response-policy, or retention env vars. - Do not introduce a new storage system or retention policy. - Preserve existing lifecycle and executor result rows as readable. - Use repo workflow for deployment only; no manual kubectl rollout or image patching. - Add regression tests for every bug fix or behavior contract introduced here. ## Current Shape To Inspect - `src/operator-dashboard/static/pages/StrategyPage.jsx` - `QuoteLifecycleSection` - `QuoteLifecycleTable` - `LifecycleDetails` - pause/filter/expanded-row behavior - `src/operator-dashboard/static/state/dashboardReducer.js` - `quote_lifecycle.updated` handling - `src/core/operator-dashboard.mjs` - `deriveQuoteLifecycleRows` - `buildLiveQuoteLifecycleRows` - `buildStrategySummary` - lifecycle row normalization/enrichment - `src/lib/postgres.mjs` - recent quote, decision, command, execution result, and outcome loaders - `src/apps/operator-dashboard.mjs` - bootstrap data loading - REST API routes - WebSocket update path - Existing dashboard/static tests under `test/`. ## Implementation Plan ### 1. Add a durable lifecycle lookup contract - Add a backend loader that can find lifecycle evidence by one identifier: - `quote_id` - `decision_id` - `command_id` - Reuse existing durable tables and normalizers: - `swap_demand_events` - `trade_decisions` - `execute_trade_commands` - `trade_execution_results` - quote outcomes table - Return the same normalized lifecycle row shape produced by `deriveQuoteLifecycleRows`, enriched for UI in the same way as recent rows. - Include lookup metadata: - requested identifier - matched identifier type - found/not found - lookup timestamp - latest stage timestamp - If no row is found, return a structured not-found response instead of a dashboard error. - Make missing old fields render as `null`/`Unavailable`; do not require new timing fields to exist. ### 2. Expose a dashboard API endpoint - Add an authenticated operator-dashboard route for lifecycle lookup, for example: - `GET /api/strategy/quote-lifecycle/:identifier` - The endpoint should: - validate a non-empty identifier - call the durable lookup loader - return `{ ok, lifecycle_row, lookup }` - return a 404-style payload for not found without crashing the dashboard - Do not expose secrets or raw relay payloads beyond what existing lifecycle rows already show. ### 3. Improve frontend state and interaction model - Replace the current "pause solves investigation" behavior with a selected quote investigator: - row action: inspect or pin quote - selected row highlight - stable investigator panel above the table or directly below controls - selected quote remains visible even when filters change or live rows move - Keep pause as table-only: - paused table keeps row order and row contents stable - selected investigator can still refresh from durable lookup - resume table applies latest rolling rows without clearing the selected quote - Add identifier lookup: - search input for quote id, decision id, or command id - loading, found, not-found, and error states - copyable identifiers remain available - Add problem-state filters without changing lifecycle data: - all - awaiting executor - approved/no command - blocked before submit - submission failed - quote not found or finished - strategy rejected - submitted/awaiting outcome - Ensure filters do not hide or clear the selected investigator. ### 4. Make `Awaiting executor` explain the next step - In the investigator panel, show a concise stage status: - quote observed - strategy decision - command recorded or missing - executor result recorded or missing - relay/outcome status when present - For `command_emitted` / `Awaiting executor` rows: - show command age using the current clock - show the command id and command timestamp - show "waiting for executor result" only when no result is found - if a later lookup finds an executor result, show the terminal status instead of stale awaiting text - For relay failures, make `quote_not_found_or_finished` visible as a first-class reason. ### 5. Keep live updates and lookup refresh coherent - When `quote_lifecycle.updated` arrives: - update the rolling table as it does today - if the selected quote appears in the live payload, refresh the selected panel from that row immediately - if the selected quote is absent from the live payload, schedule or expose a durable lookup refresh for the selected identifier - Avoid unbounded request loops: - debounce lookup refreshes client-side - do not add new env vars for refresh timing - keep manual refresh available - Show whether selected evidence came from: - current live rows - durable lookup - not found - lookup error ### 6. Preserve layout quality - Keep the lifecycle table full-width and scan-friendly. - Avoid large marketing-style or decorative redesign. - Keep row heights stable with existing fixed row slots. - Ensure long quote ids and reason text truncate or wrap intentionally. - Do not nest cards inside cards; use a panel/detail layout consistent with the existing dashboard. - Keep old rows with missing fields readable on desktop and mobile widths. ### 7. Tests - Add or update core tests for lifecycle lookup and row derivation: - command-only row classifies as `Awaiting executor` - later executor result for the same quote updates the lifecycle state - old rows missing timing fields normalize safely - Add backend API tests or static route tests: - lookup by quote id - lookup by decision id - lookup by command id - not-found response - Add frontend/static tests proving: - Quote lifecycle has a selected/pinned investigator - pause language is table-specific - identifier lookup controls exist - problem-state filters include awaiting executor and quote-not-found/finished - selected investigation is not represented only by expanded table rows - Add a regression test for the described bug: - start with a selected `Awaiting executor` quote - update the displayed recent list so that quote is absent - prove the selected quote detail remains available or can be refreshed from durable lookup ### 8. Validation Commands - Run targeted tests first, likely: - `node --test test/operator-dashboard*.test.mjs test/*lifecycle*.test.mjs` - add narrower filenames as tests are introduced - Run full: - `npm test` - Build dashboard bundle: - `npm run operator-dashboard:build` ### 9. Commit, deploy, and evidence - Commit implementation with required body: - `Proof: ...` - `Assumptions: ...` - `Still fake: ...` - Push only if tests and dashboard build pass. - Let repo workflow deploy; do not manually patch deployments. - Collect live evidence after deploy: - deployments on the new image - dashboard still receives live lifecycle updates - durable lookup returns at least one recent quote lifecycle row - an unresolved or missed quote can be inspected by identifier without relying on it being visible in the rolling table - selected quote UI does not clear on live row movement ## Assumptions - Existing durable quote, decision, command, execution result, and outcome rows are sufficient to reconstruct a single quote lifecycle. - The dashboard can add a small authenticated read endpoint without changing service topology. - Client-side debounce plus manual refresh is enough to keep pinned quote investigation fresh without adding a new response-policy or retention setting. ## Still Fake - Venue-native terminal fill truth remains incomplete where outcome rows only infer not-filled or settlement through existing attribution. - Fee-aware realized PnL and broader quote analytics workbench views remain out of scope. - Historical detail that has already been pruned cannot be reconstructed beyond existing rollups.