All checks were successful
deploy / deploy (push) Successful in 57s
Proof: DB-backed quote lifecycle retention policy, rollup-before-prune maintenance, dashboard storage visibility, and regression tests for preservation and fail-closed behavior. Assumptions: Completed quote outcome attribution remains the current durable successful-trade evidence; venue-native terminal fill ids and fee-complete PnL remain unavailable. Still fake: Historical non-success detail already pruned cannot be recovered; rollups are aggregate analytics, not per-quote settled trade proof.
213 lines
6.8 KiB
JavaScript
213 lines
6.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { buildInventorySnapshot } from '../src/core/inventory.mjs';
|
|
import { routeHistoryRecord } from '../src/core/history-records.mjs';
|
|
import {
|
|
insertHistoryEvents,
|
|
pruneNonSuccessfulQuoteLifecycleHistory,
|
|
pruneRawNearIntentsQuoteHistory,
|
|
} from '../src/lib/postgres.mjs';
|
|
|
|
test('inventory snapshot keeps pending funding out of spendable balances', () => {
|
|
const snapshot = buildInventorySnapshot({
|
|
accountId: 'solver.near',
|
|
balances: {
|
|
'nep141:btc.omft.near': '1000',
|
|
'nep141:eure.omft.near': '2000',
|
|
},
|
|
recentDeposits: [
|
|
{
|
|
asset_id: 'nep141:eure.omft.near',
|
|
amount: '300',
|
|
status: 'PENDING',
|
|
},
|
|
],
|
|
trackedWithdrawals: [
|
|
{
|
|
withdrawal_hash: 'wd-1',
|
|
asset_id: 'nep141:btc.omft.near',
|
|
amount: '25',
|
|
status: 'PENDING',
|
|
},
|
|
],
|
|
assetRegistry: new Map([
|
|
['nep141:btc.omft.near', { decimals: 8 }],
|
|
['nep141:eure.omft.near', { decimals: 18 }],
|
|
]),
|
|
observedAt: '2026-04-02T10:00:00.000Z',
|
|
});
|
|
|
|
assert.equal(snapshot.spendable['nep141:eure.omft.near'], '2000');
|
|
assert.equal(snapshot.pending_inbound['nep141:eure.omft.near'], '300');
|
|
assert.equal(snapshot.pending_outbound['nep141:btc.omft.near'], '25');
|
|
});
|
|
|
|
test('history writer routes decision events into the decision table family', () => {
|
|
const routed = routeHistoryRecord({
|
|
topic: 'decision.trade_decision',
|
|
event: {
|
|
event_id: 'evt-1',
|
|
event_type: 'trade_decision',
|
|
venue: 'near-intents',
|
|
schema_version: 1,
|
|
ingested_at: '2026-04-02T10:00:00.000Z',
|
|
payload: {
|
|
decision_id: 'decision-1',
|
|
quote_id: 'quote-1',
|
|
pair: 'a->b',
|
|
direction: 'btc_to_eure',
|
|
decision: 'rejected',
|
|
decision_reason: 'strategy_disarmed',
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.equal(routed.table, 'trade_decisions');
|
|
assert.equal(routed.record.decision_key, 'decision-1');
|
|
assert.equal(routed.record.quote_id, 'quote-1');
|
|
});
|
|
|
|
test('bulk history insert writes multiple routed events in one table statement', async () => {
|
|
const queries = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
queries.push({ sql, params });
|
|
return {
|
|
rows: [
|
|
{ event_id: params[0] },
|
|
{ event_id: params[12] },
|
|
],
|
|
rowCount: 2,
|
|
};
|
|
},
|
|
};
|
|
|
|
const result = await insertHistoryEvents(pool, [
|
|
{
|
|
table: 'trade_decisions',
|
|
topic: 'decision.trade_decision',
|
|
event: historyEvent('evt-1', { decision_id: 'decision-1', quote_id: 'quote-1' }),
|
|
record: { quote_id: 'quote-1', pair: 'a->b', decision_key: 'decision-1' },
|
|
},
|
|
{
|
|
table: 'trade_decisions',
|
|
topic: 'decision.trade_decision',
|
|
event: historyEvent('evt-2', { decision_id: 'decision-2', quote_id: 'quote-2' }),
|
|
record: { quote_id: 'quote-2', pair: 'a->b', decision_key: 'decision-2' },
|
|
},
|
|
]);
|
|
|
|
assert.equal(queries.length, 1);
|
|
assert.match(queries[0].sql, /INSERT INTO trade_decisions/);
|
|
assert.match(queries[0].sql, /RETURNING event_id/);
|
|
assert.equal(queries[0].params.length, 24);
|
|
assert.deepEqual([...result.insertedEventIds].sort(), ['evt-1', 'evt-2']);
|
|
});
|
|
|
|
test('raw quote retention preserves rows linked to maker lifecycle evidence', async () => {
|
|
const queries = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
queries.push({ sql, params });
|
|
return { rows: [], rowCount: 42 };
|
|
},
|
|
};
|
|
|
|
const result = await pruneRawNearIntentsQuoteHistory(pool, {
|
|
now: '2026-05-21T12:00:00.000Z',
|
|
retainRecentMs: 6 * 60 * 60 * 1000,
|
|
batchSize: 123,
|
|
});
|
|
|
|
assert.equal(result.deletedCount, 42);
|
|
assert.equal(result.cutoff, '2026-05-21T06:00:00.000Z');
|
|
assert.equal(result.batchSize, 123);
|
|
assert.equal(queries.length, 1);
|
|
assert.match(queries[0].sql, /DELETE FROM raw_near_intents_quotes/);
|
|
assert.match(queries[0].sql, /trade_decisions/);
|
|
assert.match(queries[0].sql, /execute_trade_commands/);
|
|
assert.match(queries[0].sql, /trade_execution_results/);
|
|
assert.match(queries[0].sql, /quote_outcome_attributions/);
|
|
assert.deepEqual(queries[0].params, ['2026-05-21T06:00:00.000Z', 123]);
|
|
});
|
|
|
|
test('raw quote retention drains multiple bounded batches when firehose backlog exceeds one batch', async () => {
|
|
const rowCounts = [500_000, 500_000, 12];
|
|
const queries = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
queries.push({ sql, params });
|
|
return { rows: [], rowCount: rowCounts.shift() ?? 0 };
|
|
},
|
|
};
|
|
|
|
const result = await pruneRawNearIntentsQuoteHistory(pool, {
|
|
now: '2026-05-21T12:00:00.000Z',
|
|
retainRecentMs: 30 * 60 * 1000,
|
|
batchSize: 500_000,
|
|
maxBatches: 20,
|
|
});
|
|
|
|
assert.equal(result.deletedCount, 1_000_012);
|
|
assert.equal(result.cutoff, '2026-05-21T11:30:00.000Z');
|
|
assert.equal(result.batchSize, 500_000);
|
|
assert.equal(result.maxBatches, 20);
|
|
assert.equal(result.batches, 3);
|
|
assert.equal(queries.length, 3);
|
|
for (const query of queries) {
|
|
assert.match(query.sql, /DELETE FROM raw_near_intents_quotes/);
|
|
assert.deepEqual(query.params, ['2026-05-21T11:30:00.000Z', 500_000]);
|
|
}
|
|
});
|
|
|
|
test('quote lifecycle retention preserves completed trades and drains old non-success rows', async () => {
|
|
const rowCounts = [100_000, 12, 7, 4, 3, 2, 1];
|
|
const queries = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
queries.push({ sql, params });
|
|
return { rows: [], rowCount: rowCounts.shift() ?? 0 };
|
|
},
|
|
};
|
|
|
|
const result = await pruneNonSuccessfulQuoteLifecycleHistory(pool, {
|
|
now: '2026-06-05T12:00:00.000Z',
|
|
retainRecentMs: 30 * 60 * 1000,
|
|
batchSize: 100_000,
|
|
maxBatches: 10,
|
|
});
|
|
|
|
assert.equal(result.deletedCount, 100_029);
|
|
assert.equal(result.cutoff, '2026-06-05T11:30:00.000Z');
|
|
assert.equal(result.batchSize, 100_000);
|
|
assert.equal(result.maxBatches, 10);
|
|
assert.deepEqual(result.tables.map((entry) => entry.table), [
|
|
'raw_near_intents_quotes',
|
|
'swap_demand_events',
|
|
'trade_decisions',
|
|
'execute_trade_commands',
|
|
'trade_execution_results',
|
|
'quote_outcome_attributions',
|
|
]);
|
|
assert.equal(queries.length, 7);
|
|
for (const query of queries) {
|
|
assert.match(query.sql, /DELETE FROM/);
|
|
assert.match(query.sql, /outcome_status = 'completed'/);
|
|
assert.match(query.sql, /attribution_status IN \('heuristic_match', 'linked_settlement', 'exact_match', 'attributed'\)/);
|
|
assert.deepEqual(query.params, ['2026-06-05T11:30:00.000Z', 100_000]);
|
|
}
|
|
assert.match(queries.at(-1).sql, /DELETE FROM quote_outcome_attributions/);
|
|
});
|
|
|
|
function historyEvent(eventId, payload) {
|
|
return {
|
|
event_id: eventId,
|
|
event_type: 'trade_decision',
|
|
venue: 'near-intents',
|
|
source: 'strategy-engine',
|
|
observed_at: '2026-04-02T10:00:00.000Z',
|
|
ingested_at: '2026-04-02T10:00:00.001Z',
|
|
payload,
|
|
};
|
|
}
|