From 1a7bb89f36abf64efaa937602e7184421e3cfbb0 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 12 Apr 2026 18:48:51 +0200 Subject: [PATCH] Fix intent request terminal reason text Proof: Live request validation showed a not_filled own-request row still displayed the earlier relay-accepted text; the normalizer now prefers terminal outcome reason text and has a regression test for deadline_elapsed_without_settlement. Assumptions: Terminal request outcome rows are more decisive than submission result text for operator-facing reason copy. Still fake: Venue-native fill ids and fee-complete realized PnL remain unavailable; completed still depends on durable inventory delta attribution. --- src/lib/postgres.mjs | 25 +++++++++- test/postgres-intent-requests.test.mjs | 68 ++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 test/postgres-intent-requests.test.mjs diff --git a/src/lib/postgres.mjs b/src/lib/postgres.mjs index 6093135..7744ada 100644 --- a/src/lib/postgres.mjs +++ b/src/lib/postgres.mjs @@ -1211,7 +1211,7 @@ function normalizeEventPayloadRow(row) { }; } -function normalizeIntentRequestRow(row) { +export function normalizeIntentRequestRow(row) { const preflight = row.preflight_payload || {}; const submission = row.submission_payload || null; const outcome = row.outcome_payload || null; @@ -1223,7 +1223,9 @@ function normalizeIntentRequestRow(row) { || submission?.result_code || preflight.reason_code || 'reason_unknown'; - const reasonText = submission?.result_text + const reasonText = outcome?.reason_text + || (outcome?.outcome_reason ? humanizeIntentRequestReason(outcome.outcome_reason) : null) + || submission?.result_text || preflight.reason_text || reasonCode.replaceAll('_', ' '); @@ -1287,6 +1289,25 @@ function normalizeIntentRequestRow(row) { }; } +function humanizeIntentRequestReason(reason) { + const normalized = String(reason || '').trim(); + const labels = { + accepted_by_relay_without_settlement: + 'Relay accepted the signed request; waiting for durable EURe decrease and BTC increase evidence.', + relay_settled_but_inventory_delta_missing: + 'Relay reported settlement, but no matching durable inventory movement is linked yet.', + deadline_elapsed_without_settlement: + 'Deadline and grace window elapsed without matching EURe decrease and BTC increase evidence.', + matched_inventory_delta: + 'Matched durable EURe decrease and BTC increase evidence.', + ambiguous_inventory_delta_match: + 'More than one inventory movement could match this request; no completion is assigned.', + relay_not_found_or_not_valid: + 'Relay reported the intent as not found or not valid.', + }; + return labels[normalized] || normalized.replaceAll('_', ' '); +} + function mapSubmissionStatusToRequestState(status) { if (status === 'accepted_by_relay') return 'awaiting_settlement'; if (status === 'submit_requested') return 'submitted'; diff --git a/test/postgres-intent-requests.test.mjs b/test/postgres-intent-requests.test.mjs new file mode 100644 index 0000000..3e060ba --- /dev/null +++ b/test/postgres-intent-requests.test.mjs @@ -0,0 +1,68 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { normalizeIntentRequestRow } from '../src/lib/postgres.mjs'; + +test('intent request normalization prefers terminal outcome reason text over relay acceptance text', () => { + const row = normalizeIntentRequestRow({ + preflight_observed_at: '2026-04-12T16:45:30.000Z', + preflight_ingested_at: '2026-04-12T16:45:30.000Z', + preflight_payload: { + request_id: 'request-1', + idempotency_key: 'intent-request:request-1', + state: 'draft', + reason_code: 'quote_available', + reason_text: 'Solver quote meets the explicit slippage/minimum receive policy.', + 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', + selected_quote: { quote_hash: 'quote-hash-1', amount_out: '8214' }, + created_at: '2026-04-12T16:45:30.000Z', + deadline_at: '2026-04-12T16:46:28.790Z', + }, + submission_observed_at: '2026-04-12T16:45:43.133Z', + submission_ingested_at: '2026-04-12T16:45:43.133Z', + submission_payload: { + request_id: 'request-1', + idempotency_key: 'intent-request:request-1', + submission_id: 'submission-1', + status: 'accepted_by_relay', + result_code: 'publish_intent_accepted', + result_text: 'Relay accepted the signed request. This is not settlement.', + submitted_at: '2026-04-12T16:45:43.133Z', + intent_hash: 'intent-hash-1', + quote_hash: 'quote-hash-1', + relay_status: 'PENDING', + destination_amount_units: '8214', + }, + outcome_observed_at: '2026-04-12T16:47:32.958Z', + outcome_payload: { + request_id: 'request-1', + idempotency_key: 'intent-request:request-1', + submission_id: 'submission-1', + intent_hash: 'intent-hash-1', + submission_status: 'accepted_by_relay', + relay_status: 'PENDING', + submitted_at: '2026-04-12T16:45:43.133Z', + outcome_status: 'not_filled', + outcome_observed_at: '2026-04-12T16:47:32.958Z', + outcome_source: 'request_deadline_and_inventory_snapshots', + outcome_reason: 'deadline_elapsed_without_settlement', + attribution_status: 'unattributed', + attribution_method: null, + attributed_inventory_delta: null, + evidence: {}, + }, + }); + + assert.equal(row.state, 'not_filled'); + assert.equal(row.reason_code, 'deadline_elapsed_without_settlement'); + assert.match(row.reason_text, /Deadline and grace window elapsed/i); + assert.doesNotMatch(row.reason_text, /Relay accepted the signed request/i); + assert.equal(row.has_settlement_evidence, false); +});