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.
299 lines
10 KiB
JavaScript
299 lines
10 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { extendMakerTiming } from '../src/core/maker-timing.mjs';
|
|
import {
|
|
DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY,
|
|
QUOTE_LIFECYCLE_DATA_CLASSES,
|
|
buildQuoteLifecycleRollups,
|
|
classifyQuoteLifecycleDataClass,
|
|
decideQuoteLifecycleRetentionMode,
|
|
normalizeQuoteLifecycleRetentionPolicy,
|
|
} from '../src/core/quote-lifecycle-retention.mjs';
|
|
import {
|
|
countQuoteLifecyclePruneCandidates,
|
|
runQuoteLifecycleRetentionMaintenance,
|
|
} from '../src/lib/postgres.mjs';
|
|
|
|
test('quote lifecycle classifier separates success, in-flight, non-success, raw firehose, and rollups', () => {
|
|
assert.equal(
|
|
classifyQuoteLifecycleDataClass({
|
|
table: 'quote_outcome_attributions',
|
|
row: { outcome_status: 'completed', attribution_status: 'heuristic_match' },
|
|
}),
|
|
QUOTE_LIFECYCLE_DATA_CLASSES.successfulTradeEvidence,
|
|
);
|
|
assert.equal(
|
|
classifyQuoteLifecycleDataClass({
|
|
table: 'quote_outcome_attributions',
|
|
row: { outcome_status: 'submitted', attribution_status: 'linked_settlement' },
|
|
}),
|
|
QUOTE_LIFECYCLE_DATA_CLASSES.successfulTradeEvidence,
|
|
);
|
|
assert.equal(
|
|
classifyQuoteLifecycleDataClass({
|
|
table: 'raw_near_intents_quotes',
|
|
row: { ingested_at: '2026-06-06T10:00:00.000Z', quote_id: null },
|
|
now: '2026-06-06T11:00:00.000Z',
|
|
}),
|
|
QUOTE_LIFECYCLE_DATA_CLASSES.rawDebugFirehose,
|
|
);
|
|
assert.equal(
|
|
classifyQuoteLifecycleDataClass({
|
|
table: 'trade_decisions',
|
|
row: { ingested_at: '2026-06-06T10:55:00.000Z', quote_id: 'quote-recent' },
|
|
now: '2026-06-06T11:00:00.000Z',
|
|
detailRetentionMs: 10 * 60 * 1000,
|
|
}),
|
|
QUOTE_LIFECYCLE_DATA_CLASSES.inFlightDetail,
|
|
);
|
|
assert.equal(
|
|
classifyQuoteLifecycleDataClass({
|
|
table: 'trade_execution_results',
|
|
row: { ingested_at: '2026-06-06T09:00:00.000Z', quote_id: 'quote-old' },
|
|
now: '2026-06-06T11:00:00.000Z',
|
|
detailRetentionMs: 10 * 60 * 1000,
|
|
}),
|
|
QUOTE_LIFECYCLE_DATA_CLASSES.nonSuccessDetail,
|
|
);
|
|
assert.equal(
|
|
classifyQuoteLifecycleDataClass({
|
|
table: 'quote_lifecycle_rollups',
|
|
row: { data_class: 'aggregate_rollup' },
|
|
}),
|
|
QUOTE_LIFECYCLE_DATA_CLASSES.aggregateRollup,
|
|
);
|
|
});
|
|
|
|
test('retention policy validation fails closed on missing or unsafe policy fields', () => {
|
|
assert.equal(normalizeQuoteLifecycleRetentionPolicy(null).ok, false);
|
|
assert.match(normalizeQuoteLifecycleRetentionPolicy(null).block_reason, /missing/);
|
|
|
|
const unsafe = normalizeQuoteLifecycleRetentionPolicy({
|
|
...DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY,
|
|
preserve_successful_evidence: false,
|
|
});
|
|
assert.equal(unsafe.ok, false);
|
|
assert.match(unsafe.block_reason, /preserve_successful_evidence/);
|
|
});
|
|
|
|
test('retention mode enters pressure only from DB-backed policy thresholds', () => {
|
|
const normal = decideQuoteLifecycleRetentionMode({
|
|
policy: DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY,
|
|
storageMetrics: {
|
|
database: { total_bytes: 10 },
|
|
tables: [{ table: 'trade_decisions', total_bytes: 100 }],
|
|
},
|
|
});
|
|
assert.equal(normal.mode, 'normal');
|
|
assert.equal(normal.detail_retention_ms, DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY.normal_detail_retention_ms);
|
|
|
|
const pressure = decideQuoteLifecycleRetentionMode({
|
|
policy: DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY,
|
|
storageMetrics: {
|
|
database: { total_bytes: 10 },
|
|
tables: [{
|
|
table: 'trade_decisions',
|
|
total_bytes: DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY.pressure_table_bytes,
|
|
}],
|
|
},
|
|
});
|
|
assert.equal(pressure.mode, 'pressure');
|
|
assert.equal(pressure.detail_retention_ms, DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY.pressure_detail_retention_ms);
|
|
});
|
|
|
|
test('quote lifecycle rollups group by pair, direction, request, edge, result, failure, age, notional, and outcome', () => {
|
|
const nbtc = 'nep141:nbtc.bridge.near';
|
|
const eure = 'nep141:eure.omft.near';
|
|
const usdc = 'nep141:usdc.omft.near';
|
|
const rows = [
|
|
{
|
|
quote_id: 'quote-completed',
|
|
activity_at: '2026-06-06T10:00:00.010Z',
|
|
quote: {
|
|
quote_id: 'quote-completed',
|
|
pair: `${nbtc}->${eure}`,
|
|
request_kind: 'exact_in',
|
|
maker_timing: { quote_received_at: '2026-06-06T10:00:00.000Z' },
|
|
},
|
|
decision: {
|
|
decision_id: 'decision-completed',
|
|
quote_id: 'quote-completed',
|
|
pair: `${nbtc}->${eure}`,
|
|
direction: 'base_to_quote',
|
|
request_kind: 'exact_in',
|
|
edge_bps: '49',
|
|
notional: '5',
|
|
notional_symbol: 'EURe',
|
|
},
|
|
command: {
|
|
command_id: 'command-completed',
|
|
quote_id: 'quote-completed',
|
|
},
|
|
execution: {
|
|
quote_id: 'quote-completed',
|
|
status: 'submitted',
|
|
result_code: 'quote_response_ok',
|
|
maker_timing: extendMakerTiming({ quote_received_at: '2026-06-06T10:00:00.000Z' }, {
|
|
relay_result_at: '2026-06-06T10:00:00.090Z',
|
|
}),
|
|
},
|
|
outcome: {
|
|
quote_id: 'quote-completed',
|
|
outcome_status: 'completed',
|
|
attribution_status: 'heuristic_match',
|
|
},
|
|
},
|
|
{
|
|
quote_id: 'quote-failed',
|
|
activity_at: '2026-06-06T10:00:00.020Z',
|
|
decision: {
|
|
decision_id: 'decision-failed',
|
|
quote_id: 'quote-failed',
|
|
pair: `${nbtc}->${usdc}`,
|
|
direction: 'base_to_quote',
|
|
request_kind: 'exact_in',
|
|
edge_bps: '20',
|
|
notional: '8',
|
|
notional_symbol: 'USDC',
|
|
},
|
|
execution: {
|
|
quote_id: 'quote-failed',
|
|
status: 'failed',
|
|
result_code: 'submission_failed',
|
|
failure_category: 'quote_not_found_or_finished',
|
|
maker_timing: extendMakerTiming({ quote_received_at: '2026-06-06T10:00:00.000Z' }, {
|
|
relay_result_at: '2026-06-06T10:00:00.400Z',
|
|
}),
|
|
},
|
|
},
|
|
];
|
|
|
|
const first = buildQuoteLifecycleRollups({
|
|
rows,
|
|
now: '2026-06-06T11:00:00.000Z',
|
|
computedAt: '2026-06-06T11:00:00.000Z',
|
|
});
|
|
const second = buildQuoteLifecycleRollups({
|
|
rows,
|
|
now: '2026-06-06T11:00:00.000Z',
|
|
computedAt: '2026-06-06T11:00:00.000Z',
|
|
});
|
|
|
|
assert.deepEqual(first, second);
|
|
assert.ok(first.some((rollup) => (
|
|
rollup.pair === `${nbtc}->${eure}`
|
|
&& rollup.request_kind === 'exact_in'
|
|
&& rollup.edge_bps === '49'
|
|
&& rollup.notional_bucket === '5-25 EURe'
|
|
&& rollup.outcome_status === 'completed'
|
|
&& rollup.completed_count === 1
|
|
&& rollup.relay_accepted_count === 1
|
|
)));
|
|
assert.ok(first.some((rollup) => (
|
|
rollup.pair === `${nbtc}->${usdc}`
|
|
&& rollup.result_code === 'submission_failed'
|
|
&& rollup.failure_category === 'quote_not_found_or_finished'
|
|
&& rollup.quote_age_bucket === '250-500ms'
|
|
&& rollup.notional_bucket === '5-25 USDC'
|
|
&& rollup.relay_failed_count === 1
|
|
)));
|
|
});
|
|
|
|
test('policy-driven prune candidate counting fails closed when policy is invalid', async () => {
|
|
const pool = {
|
|
async query() {
|
|
throw new Error('query should not run for invalid policy');
|
|
},
|
|
};
|
|
const result = await countQuoteLifecyclePruneCandidates(pool, {
|
|
policy: {
|
|
...DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY,
|
|
enabled: false,
|
|
},
|
|
});
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.total, 0);
|
|
assert.match(result.block_reason, /enabled/);
|
|
});
|
|
|
|
test('retention maintenance writes rollups before deleting detail and preserves successful predicates', async () => {
|
|
const queries = [];
|
|
let watermarkReads = 0;
|
|
const policyRow = {
|
|
...DEFAULT_QUOTE_LIFECYCLE_RETENTION_POLICY,
|
|
updated_at: '2026-06-06T09:00:00.000Z',
|
|
};
|
|
const pool = {
|
|
async query(sql, params = []) {
|
|
queries.push({ sql, params });
|
|
if (sql.includes('FROM retention_policies')) return { rows: [policyRow], rowCount: 1 };
|
|
if (sql.includes('pg_database_size')) return { rows: [{ total_bytes: 10 }], rowCount: 1 };
|
|
if (sql.includes('pg_stat_user_tables')) return { rows: [], rowCount: 0 };
|
|
if (sql.includes('COUNT(*)::INT AS count') && sql.includes('quote_outcome_attributions')) {
|
|
return { rows: [{ count: 1 }], rowCount: 1 };
|
|
}
|
|
if (sql.includes('FROM quote_lifecycle_rollup_watermarks')) {
|
|
watermarkReads += 1;
|
|
return watermarkReads === 1
|
|
? { rows: [], rowCount: 0 }
|
|
: {
|
|
rows: [{
|
|
rollup_key: 'quote_lifecycle',
|
|
covered_until: '2026-06-06T10:30:00.000Z',
|
|
computed_at: '2026-06-06T11:00:00.000Z',
|
|
status: 'success',
|
|
row_count: 1,
|
|
rollup_count: 1,
|
|
}],
|
|
rowCount: 1,
|
|
};
|
|
}
|
|
if (sql.includes('MIN(activity_at)')) {
|
|
return { rows: [{ earliest_at: '2026-06-06T10:00:00.000Z' }], rowCount: 1 };
|
|
}
|
|
if (sql.includes('WITH subject_events')) {
|
|
return { rows: [], rowCount: 0 };
|
|
}
|
|
if (sql.includes('quote_id IS NULL')) {
|
|
return {
|
|
rows: [{
|
|
event_id: 'raw-old',
|
|
quote_id: null,
|
|
activity_at: '2026-06-06T10:00:00.000Z',
|
|
pair: 'a->b',
|
|
quote_payload: {
|
|
pair: 'a->b',
|
|
request_kind: 'exact_in',
|
|
maker_timing: { quote_received_at: '2026-06-06T10:00:00.000Z' },
|
|
},
|
|
quote_observed_at: '2026-06-06T10:00:00.000Z',
|
|
quote_ingested_at: '2026-06-06T10:00:00.001Z',
|
|
}],
|
|
rowCount: 1,
|
|
};
|
|
}
|
|
if (sql.includes('INSERT INTO quote_lifecycle_rollups')) return { rows: [], rowCount: 1 };
|
|
if (sql.includes('INSERT INTO quote_lifecycle_rollup_watermarks')) return { rows: [], rowCount: 1 };
|
|
if (sql.includes('DELETE FROM')) return { rows: [], rowCount: 2 };
|
|
if (sql.includes('INSERT INTO quote_lifecycle_retention_runs')) return { rows: [], rowCount: 1 };
|
|
return { rows: [], rowCount: 0 };
|
|
},
|
|
};
|
|
|
|
const result = await runQuoteLifecycleRetentionMaintenance(pool, {
|
|
now: '2026-06-06T11:00:00.000Z',
|
|
});
|
|
const rollupIndex = queries.findIndex((query) => query.sql.includes('INSERT INTO quote_lifecycle_rollups'));
|
|
const firstDeleteIndex = queries.findIndex((query) => query.sql.includes('DELETE FROM'));
|
|
|
|
assert.equal(result.status, 'success');
|
|
assert.ok(rollupIndex >= 0);
|
|
assert.ok(firstDeleteIndex > rollupIndex);
|
|
assert.ok(queries.some((query) => (
|
|
query.sql.includes('DELETE FROM quote_outcome_attributions')
|
|
&& query.sql.includes("target.outcome_status = 'completed'")
|
|
&& query.sql.includes('linked_settlement')
|
|
)));
|
|
});
|