unrip/IMPLEMENTATION.md
philipp 7ea1576ba7 Plan quote lifecycle truth turn
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.
2026-04-09 00:39:02 +02:00

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