Proof: Archive the completed runtime-health turn and open a new implementation turn that replaces ambiguous quote verdicts with explicit lifecycle truth and durable execution explanation. Assumptions: Recent decision and execution records already contain enough repo-owned evidence to derive a truthful first-pass quote lifecycle for the active pair. Still fake: The new turn is planning state only; the dashboard still contains the existing lifecycle ambiguity until this proof is implemented.
251 lines
8.6 KiB
Markdown
251 lines
8.6 KiB
Markdown
# Implementation Turn: quote lifecycle truth and execution explanation
|
|
|
|
Status: open
|
|
Opened: 2026-04-09
|
|
|
|
## Goal
|
|
Replace ambiguous quote and decision wording with a truthful per-quote lifecycle that tells the operator exactly why a quote was filtered, rejected, blocked, submitted, failed, not filled, or completed.
|
|
|
|
## Selected backlog items
|
|
- [I021] Quote lifecycle truth and execution explanation: replace ambiguous dashboard verdicts with a per-quote state machine that shows exactly why a quote was filtered, rejected, blocked, submitted, not filled, or executed, with durable reason codes and operator-facing traceability.
|
|
|
|
## Design rules
|
|
- Treat quote lifecycle as product truth, not UI decoration.
|
|
- Strategy verdict is not the final operator answer.
|
|
- Prefer one explicit lifecycle derivation path shared by backend and dashboard over ad hoc page-specific wording.
|
|
- Do not invent downstream certainty where durable evidence is absent.
|
|
- Remove `Actionable` completely from operator-facing copy.
|
|
|
|
## Problem statement for this turn
|
|
The current dashboard still forces operators to infer too much:
|
|
- `Actionable` does not say whether a command was emitted or submitted
|
|
- the Strategy page mixes strategy and execution truth
|
|
- executor-rejected rows are not clearly distinguishable from strategy-rejected rows
|
|
- quote ids are truncated and awkward to use during debugging
|
|
|
|
The repo already stores enough of the real lifecycle to do better:
|
|
- quote id
|
|
- decision id
|
|
- emitted command id
|
|
- execution result status and result code
|
|
|
|
The turn therefore needs to improve:
|
|
- lifecycle derivation
|
|
- durable reason mapping
|
|
- recent-row rendering
|
|
- trace affordances
|
|
|
|
## Lifecycle model for this turn
|
|
Implement one repo-owned lifecycle derivation for recent rows, using durable evidence in this order:
|
|
|
|
1. Quote observed
|
|
2. Strategy evaluated
|
|
3. Command emitted or not emitted
|
|
4. Executor result observed or absent
|
|
5. Venue downstream outcome when available
|
|
|
|
The first mandatory states are:
|
|
- `Filtered`
|
|
- `Rejected`
|
|
- `Blocked`
|
|
- `Submitted`
|
|
- `Failed`
|
|
- `Awaiting outcome`
|
|
- `Completed`
|
|
|
|
Suggested meanings:
|
|
- `Filtered`
|
|
quote never entered the active trade path or was excluded before strategy decision
|
|
- `Rejected`
|
|
strategy evaluated the quote and decided not to trade
|
|
- `Blocked`
|
|
strategy approved or emitted a command, but execution did not proceed due to control state or another repo-owned gate
|
|
- `Submitted`
|
|
executor accepted the command and successfully submitted a quote response
|
|
- `Failed`
|
|
execution submission failed technically
|
|
- `Awaiting outcome`
|
|
submitted to venue, but no later durable terminal venue outcome exists yet
|
|
- `Completed`
|
|
durable evidence shows the trade completed successfully
|
|
|
|
Do not show states we cannot support yet for a given row.
|
|
|
|
## Reason-code model
|
|
For each lifecycle state, map durable payload fields to a small operator reason taxonomy.
|
|
|
|
Examples:
|
|
- strategy reason codes:
|
|
- unsupported_pair
|
|
- below_edge_threshold
|
|
- inventory_unavailable
|
|
- stale_reference_price
|
|
- executor reason codes:
|
|
- executor_disarmed
|
|
- executor_paused
|
|
- submission_failed
|
|
- quote_response_ok
|
|
- downstream outcome reasons if available:
|
|
- expired
|
|
- not_filled
|
|
- completed
|
|
|
|
If the exact reason is missing:
|
|
- expose `reason_unknown`
|
|
- keep the row truthful instead of synthesizing an explanation
|
|
|
|
## Backend changes
|
|
|
|
### 1. Add a lifecycle derivation helper
|
|
Create or extend a backend module that derives quote lifecycle from:
|
|
- recent trade decisions
|
|
- recent execution results
|
|
- successful trade records
|
|
- any available quote-status or venue result surfaces
|
|
|
|
It should emit a normalized row object with:
|
|
- `quote_id`
|
|
- `decision_id`
|
|
- `command_id`
|
|
- `pair`
|
|
- `direction`
|
|
- `lifecycle_state`
|
|
- `lifecycle_label`
|
|
- `reason_code`
|
|
- `reason_text`
|
|
- timestamps for the latest known stage
|
|
- stage details for tooltips or drilldown
|
|
|
|
### 2. Join decision and execution truth explicitly
|
|
The backend should no longer leave the frontend to infer execution from isolated tables.
|
|
|
|
For each recent quote/decision row:
|
|
- attach the matching execution result by `command_id`, `decision_id`, or `quote_id`
|
|
- attach successful-trade or later terminal evidence where available
|
|
- expose whether the row is strategy-only, strategy-plus-command, or strategy-plus-execution
|
|
|
|
### 3. Preserve operator drilldown identifiers
|
|
Ensure the bootstrap payload exposes:
|
|
- full quote id
|
|
- full decision id
|
|
- full command id
|
|
|
|
Avoid requiring the frontend to reconstruct or guess identifiers from formatted strings.
|
|
|
|
## Dashboard changes
|
|
|
|
### 4. Remove forbidden language
|
|
Remove `Actionable` from:
|
|
- Strategy page tables
|
|
- any lifecycle badge or verdict cell
|
|
- any supporting labels or legends
|
|
|
|
Replace it with explicit state labels driven by lifecycle derivation.
|
|
|
|
### 5. Make recent rows self-explanatory
|
|
For each row, render:
|
|
- primary lifecycle state
|
|
- secondary reason text
|
|
- quote id with copy action
|
|
- command id if emitted
|
|
- timestamps
|
|
|
|
The operator should be able to scan rows and answer:
|
|
- why no trade happened
|
|
- whether the system tried to trade
|
|
- whether failure was strategic, operational, or downstream
|
|
|
|
### 6. Add trace affordances
|
|
At minimum:
|
|
- copy button for quote id
|
|
- avoid over-truncating ids without recovery path
|
|
- show linked ids in a dedicated trace column or expanded detail panel
|
|
|
|
If the row layout gets crowded, prefer an expandable detail tray over hiding identifiers.
|
|
|
|
## Page-level application
|
|
|
|
### Strategy page
|
|
This page should become the primary recent quote-decision-execution lifecycle surface.
|
|
|
|
It should show:
|
|
- the latest recent rows for the active pair
|
|
- lifecycle state rather than strategy-only verdict
|
|
- explicit explanation text
|
|
|
|
If a strategy-only summary remains, it must be visually separate from per-quote lifecycle truth.
|
|
|
|
### Related quote surfaces
|
|
Inspect quote and system surfaces for similar ambiguity and align the wording if they expose the same concepts.
|
|
|
|
Do not let one page say `Submitted` while another page still says `Actionable` for the same row.
|
|
|
|
## Data and state edge cases
|
|
- Strategy decision exists, no command emitted:
|
|
render as `Rejected` with strategy reason
|
|
- Command emitted, no execution result yet:
|
|
render as `Blocked` or `Awaiting executor` only if that distinction is durably supportable; otherwise use a truthful pending label
|
|
- Execution result `executor_disarmed`:
|
|
render as `Blocked` with reason `executor disarmed`
|
|
- Execution result `submission_failed`:
|
|
render as `Failed`
|
|
- Execution result `submitted`:
|
|
render as `Submitted` or `Awaiting outcome`
|
|
- Successful trade summary exists but no explicit per-quote completion event:
|
|
only promote to `Completed` where the durable linkage is real
|
|
|
|
## Concrete implementation order
|
|
|
|
### Phase 1. Define lifecycle derivation
|
|
- inspect current durable decision and execution payloads
|
|
- write the normalized lifecycle state mapping
|
|
- define forbidden and allowed operator labels
|
|
|
|
### Phase 2. Implement backend aggregation
|
|
- derive unified recent lifecycle rows
|
|
- expose full identifiers and reason codes
|
|
- keep old consumers working until the frontend is switched
|
|
|
|
### Phase 3. Update Strategy page rendering
|
|
- replace verdict column with lifecycle state
|
|
- add reason text
|
|
- add quote-id copy affordance
|
|
- surface command id and execution state where relevant
|
|
|
|
### Phase 4. Tighten wording and consistency
|
|
- remove `Actionable`
|
|
- align supporting labels
|
|
- ensure blocked vs rejected vs submitted are clearly distinct
|
|
|
|
### Phase 5. Validate with live recent rows
|
|
- verify a row rejected due to executor disarmed renders as blocked with reason
|
|
- verify a submitted row renders as submitted
|
|
- verify quote ids can be copied and used for tracing
|
|
|
|
## Test plan
|
|
- unit tests for lifecycle derivation from:
|
|
- strategy-rejected rows
|
|
- executor-disarmed rows
|
|
- submission-failed rows
|
|
- submitted rows
|
|
- dashboard bootstrap tests for:
|
|
- forbidden `Actionable` removal
|
|
- explicit lifecycle labels
|
|
- reason text rendering
|
|
- identifier exposure
|
|
- frontend component tests if needed for copy affordance or row rendering logic
|
|
|
|
No lifecycle ambiguity fix is complete without a regression test proving the old ambiguous wording cannot return.
|
|
|
|
## Validation checklist against the proof
|
|
- `Actionable` no longer appears
|
|
- strategy approval is visibly distinct from execution submission
|
|
- recent blocked rows explain why they did not trade
|
|
- recent submitted rows show that they were submitted
|
|
- quote ids are directly usable from the dashboard
|
|
|
|
## Failure modes to plan for
|
|
- the backend joins rows incorrectly and attributes the wrong execution result
|
|
- the UI uses softer wording than the backend lifecycle state
|
|
- older rows lack enough evidence and the UI pretends certainty
|
|
- ids are still truncated without a copy or expand path
|