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

8.6 KiB

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.

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