Let request controls outlive quote waits
All checks were successful
deploy / deploy (push) Successful in 33s
All checks were successful
deploy / deploy (push) Successful in 33s
Proof: Live dashboard preflight waited through the generic 3s proxy timeout while trade-executor later recorded solver_quote_unanswered after the 10s relay quote wait. Request controls now use action-aware timeouts and unanswered requests render with plain reason text. Assumptions: Own-request preflight needs at least quote_timeout plus small overhead; submit needs publish plus relay-status wait. Generic service refresh controls should keep the shorter dashboard upstream timeout. Still fake: This does not create external solver liquidity; it only lets the dashboard observe whether the request was answered, submitted, or blocked without timing out first.
This commit is contained in:
parent
4d9347d55f
commit
a4a60fd521
5 changed files with 88 additions and 1 deletions
|
|
@ -15,6 +15,7 @@ import {
|
|||
createDashboardLiveState,
|
||||
listDashboardServices,
|
||||
resolveDashboardControl,
|
||||
resolveDashboardControlTimeoutMs,
|
||||
} from '../core/operator-dashboard.mjs';
|
||||
import {
|
||||
buildDashboardAuthChallengeHeader,
|
||||
|
|
@ -524,7 +525,7 @@ async function invokeControl(control, body) {
|
|||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body || {}),
|
||||
signal: AbortSignal.timeout(config.operatorDashboardUpstreamTimeoutMs),
|
||||
signal: AbortSignal.timeout(resolveDashboardControlTimeoutMs({ control, config })),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -260,6 +260,27 @@ export function buildDashboardControlErrorResponse(error, { control = null } = {
|
|||
};
|
||||
}
|
||||
|
||||
export function resolveDashboardControlTimeoutMs({ control, config } = {}) {
|
||||
const baseTimeoutMs = Number(config?.operatorDashboardUpstreamTimeoutMs || 3_000);
|
||||
if (control?.service !== 'trade-executor') return baseTimeoutMs;
|
||||
|
||||
if (control.action === 'intent-request-preflight') {
|
||||
return Math.max(baseTimeoutMs, Number(config?.intentRequestQuoteTimeoutMs || 10_000) + 2_000);
|
||||
}
|
||||
if (control.action === 'intent-request-submit') {
|
||||
return Math.max(
|
||||
baseTimeoutMs,
|
||||
Number(config?.intentRequestPublishTimeoutMs || 10_000)
|
||||
+ Number(config?.intentRequestStatusTimeoutMs || 10_000)
|
||||
+ 5_000,
|
||||
);
|
||||
}
|
||||
if (control.action === 'intent-request-refresh-outcomes') {
|
||||
return Math.max(baseTimeoutMs, Number(config?.intentRequestStatusTimeoutMs || 10_000) + 5_000);
|
||||
}
|
||||
return baseTimeoutMs;
|
||||
}
|
||||
|
||||
export function listDashboardServices(config) {
|
||||
return SERVICE_DEFINITIONS.map(([service, label, configKey]) => ({
|
||||
service,
|
||||
|
|
|
|||
|
|
@ -1363,6 +1363,8 @@ function humanizeIntentRequestReason(reason) {
|
|||
'Relay reported the intent as not found or not valid.',
|
||||
relay_settled_without_expected_inventory_delta:
|
||||
'Relay reports settlement, but durable inventory does not show the expected EURe decrease and BTC increase.',
|
||||
solver_quote_unanswered:
|
||||
'The relay returned no solver quotes for this request.',
|
||||
};
|
||||
return labels[normalized] || normalized.replaceAll('_', ' ');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
createDashboardLiveState,
|
||||
deriveQuoteLifecycleRows,
|
||||
resolveDashboardControl,
|
||||
resolveDashboardControlTimeoutMs,
|
||||
} from '../src/core/operator-dashboard.mjs';
|
||||
import {
|
||||
buildDashboardSessionToken,
|
||||
|
|
@ -105,6 +106,31 @@ test('profitability summary flags cash-flow-adjusted benchmarks after later fund
|
|||
assert.match(summary.caveats[0], /external cash flows/);
|
||||
});
|
||||
|
||||
test('request creation controls get enough upstream time for relay quote and status waits', () => {
|
||||
const preflight = resolveDashboardControl({
|
||||
service: 'trade-executor',
|
||||
action: 'intent-request-preflight',
|
||||
});
|
||||
const submit = resolveDashboardControl({
|
||||
service: 'trade-executor',
|
||||
action: 'intent-request-submit',
|
||||
});
|
||||
const refresh = resolveDashboardControl({
|
||||
service: 'inventory-sync',
|
||||
action: 'refresh',
|
||||
});
|
||||
const config = {
|
||||
operatorDashboardUpstreamTimeoutMs: 3000,
|
||||
intentRequestQuoteTimeoutMs: 10000,
|
||||
intentRequestPublishTimeoutMs: 10000,
|
||||
intentRequestStatusTimeoutMs: 10000,
|
||||
};
|
||||
|
||||
assert.equal(resolveDashboardControlTimeoutMs({ control: preflight, config }), 12000);
|
||||
assert.equal(resolveDashboardControlTimeoutMs({ control: submit, config }), 25000);
|
||||
assert.equal(resolveDashboardControlTimeoutMs({ control: refresh, config }), 3000);
|
||||
});
|
||||
|
||||
test('dashboard control errors become structured responses instead of uncaught failures', () => {
|
||||
const control = resolveDashboardControl({
|
||||
service: 'trade-executor',
|
||||
|
|
|
|||
|
|
@ -115,3 +115,40 @@ test('intent request status refresh loader normalizes accepted relay submissions
|
|||
assert.equal(row.submitted_at, '2026-04-12T16:45:43.133Z');
|
||||
assert.equal(row.status_checked_at, '2026-04-12T16:45:44.000Z');
|
||||
});
|
||||
|
||||
|
||||
test('intent request normalization explains unanswered solver quotes in plain terms', () => {
|
||||
const row = normalizeIntentRequestRow({
|
||||
preflight_observed_at: '2026-04-12T17:20:19.476Z',
|
||||
preflight_ingested_at: '2026-04-12T17:20:19.476Z',
|
||||
preflight_payload: {
|
||||
request_id: 'request-unanswered',
|
||||
idempotency_key: 'intent-request:request-unanswered',
|
||||
state: 'blocked',
|
||||
reason_code: 'solver_quote_unanswered',
|
||||
reason_text: 'The relay returned no solver quotes for this request.',
|
||||
source_asset_id: 'nep141:eure.omft.near',
|
||||
source_symbol: 'EURe',
|
||||
source_decimals: 18,
|
||||
destination_asset_id: 'nep141:btc.omft.near',
|
||||
destination_symbol: 'BTC',
|
||||
destination_decimals: 8,
|
||||
source_amount_units: '5000000000000000000',
|
||||
min_destination_amount_units: '8090',
|
||||
solver_quote_count: 0,
|
||||
created_at: '2026-04-12T17:20:19.476Z',
|
||||
},
|
||||
outcome_payload: {
|
||||
request_id: 'request-unanswered',
|
||||
outcome_status: 'blocked',
|
||||
outcome_reason: 'solver_quote_unanswered',
|
||||
attribution_status: 'unattributed',
|
||||
attributed_inventory_delta: null,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(row.state, 'blocked');
|
||||
assert.equal(row.reason_code, 'solver_quote_unanswered');
|
||||
assert.equal(row.reason_text, 'The relay returned no solver quotes for this request.');
|
||||
assert.equal(row.live_submit_capable, false);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue