Fix quote lifecycle recency
All checks were successful
deploy / deploy (push) Successful in 33s

Proof: quote lifecycle rows now anchor row time and sorting to quote/submission activity instead of later outcome recomputation; regression covers an old 2% not-filled outcome recomputed after a current 0.49% quote.

Assumptions: outcome-only historical rows still belong in lifecycle evidence, but their dashboard recency must come from original command/submission time when quote evidence is not loaded.

Still fake: venue-native terminal fill events and fee-complete realized PnL remain unavailable.
This commit is contained in:
philipp 2026-04-14 00:39:50 +02:00
parent a4a60fd521
commit 558a162cd2
3 changed files with 67 additions and 3 deletions

View file

@ -961,13 +961,15 @@ export function deriveQuoteLifecycleRows({
gross_edge_pct: outcome?.gross_edge_pct || null,
eure_notional: outcome?.eure_notional || null,
outcome,
command_at: outcome?.command_at || null,
execution_result_at: outcome?.submitted_at || null,
outcome_observed_at: outcome?.outcome_observed_at || outcome?.submitted_at || null,
});
}
const finalized = [...rowsByKey.values()]
.map((row) => finalizeLifecycleRow(row))
.sort((left, right) => sortTimestamps(right.latest_stage_at, left.latest_stage_at));
.sort((left, right) => sortTimestamps(right.quote_activity_at, left.quote_activity_at));
return limit == null ? finalized : finalized.slice(0, limit);
}
@ -1101,6 +1103,13 @@ function finalizeLifecycleRow(row) {
|| row.decision_at
|| row.quote_observed_at
|| null,
quote_activity_at:
row.quote_observed_at
|| row.decision_at
|| row.command_at
|| row.execution_result_at
|| row.outcome_observed_at
|| null,
stage_details: {
quote_observed_at: row.quote_observed_at,
decision_at: row.decision_at,

View file

@ -139,7 +139,7 @@ function QuoteLifecycleTable({ items }) {
<table className="quote-lifecycle-table">
<thead>
<tr>
<th>Time</th>
<th>Quote time</th>
<th>Quote id</th>
<th>Request</th>
<th>Responded?</th>
@ -156,7 +156,12 @@ function QuoteLifecycleTable({ items }) {
return (
<Fragment key={rowKey}>
<tr key={`${rowKey}:row`}>
<td>{formatTimestamp(item.latest_stage_at)}</td>
<td>
<div>{formatTimestamp(item.quote_activity_at || item.latest_stage_at)}</div>
{item.latest_stage_at && item.latest_stage_at !== item.quote_activity_at ? (
<div className="status-subtle">Updated {formatTimestamp(item.latest_stage_at)}</div>
) : null}
</td>
<td><IdentifierRow label="Quote" value={item.quote_id} /></td>
<td>
<div>{formatTerms(item.request_terms || item.submitted_terms)}</div>

View file

@ -670,6 +670,56 @@ test('submitted lifecycle evidence never becomes completed by itself', () => {
assert.doesNotMatch(`${submitted[0].lifecycle_label} ${submitted[0].reason_text}`, /completed|successful trade|asset delta/i);
});
test('quote lifecycle recency is anchored to quote or submission time, not later outcome recompute', () => {
const rows = deriveQuoteLifecycleRows({
recentTradeDecisions: [{
observed_at: '2026-04-13T22:33:18.000Z',
payload: {
decision_id: 'decision-current',
quote_id: 'quote-current',
pair: 'btc->eure',
decision: 'actionable',
decision_reason: 'actionable',
gross_edge_pct: '0.490000',
},
}],
recentExecutionResults: [{
command_id: 'cmd-current',
decision_id: 'decision-current',
quote_id: 'quote-current',
pair: 'btc->eure',
result_at: '2026-04-13T22:33:19.000Z',
status: 'submitted',
result_code: 'quote_response_ok',
}],
recentQuoteOutcomes: [{
command_id: 'cmd-old',
decision_id: 'decision-old',
quote_id: 'quote-old-two-percent',
pair: 'btc->eure',
gross_edge_pct: '2.000000',
eure_notional: '75.58',
submitted_at: '2026-04-09T23:36:35.566Z',
command_at: '2026-04-09T23:36:35.352Z',
outcome_status: 'not_filled',
outcome_reason: 'deadline_elapsed_without_settlement',
outcome_observed_at: '2026-04-13T22:35:33.295Z',
outcome_source: 'submission_deadline_and_inventory_snapshots',
attribution_status: 'unattributed',
attributed_inventory_delta: null,
}],
limit: null,
});
assert.equal(rows[0].quote_id, 'quote-current');
assert.equal(rows[0].gross_edge_pct, '0.490000');
const oldRow = rows.find((row) => row.quote_id === 'quote-old-two-percent');
assert.equal(oldRow.quote_activity_at, '2026-04-09T23:36:35.352Z');
assert.equal(oldRow.latest_stage_at, '2026-04-13T22:35:33.295Z');
assert.equal(oldRow.lifecycle_state, 'not_filled');
});
test('successful trade rows require completed outcome with linked settled inventory evidence', () => {
const config = buildConfig();
const bootstrap = buildDashboardBootstrap({