Restore live trading under quote pressure
Some checks failed
deploy / deploy (push) Failing after 52s

Proof: npm test (209/209) covers stale command expiry, bounded executor state, bounded strategy quote cache, bounded quote outcome refresh, and resource guardrails.

Assumptions: current DB pair config and armed state remain the operator-approved live trading controls; stale quote commands are unsafe to submit after their min_deadline_ms.

Still fake: quote outcomes still infer fills from inventory deltas rather than a venue-native terminal fill event.
This commit is contained in:
philipp 2026-05-18 12:51:25 +02:00
parent 82017dd301
commit 92aa636dc0
16 changed files with 503 additions and 26 deletions

View file

@ -547,6 +547,9 @@ spec:
image: ghcr.io/example/unrip:bootstrap image: ghcr.io/example/unrip:bootstrap
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: ["node", "src/apps/trade-executor.mjs"] command: ["node", "src/apps/trade-executor.mjs"]
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=896"
ports: ports:
- name: control-api - name: control-api
containerPort: 8087 containerPort: 8087
@ -555,6 +558,11 @@ spec:
name: unrip-config name: unrip-config
- secretRef: - secretRef:
name: unrip-secrets name: unrip-secrets
resources:
requests:
memory: 256Mi
limits:
memory: 1280Mi
volumeMounts: volumeMounts:
- name: executor-state - name: executor-state
mountPath: /var/lib/unrip/executor-state mountPath: /var/lib/unrip/executor-state

View file

@ -115,7 +115,12 @@ const intentRequestOutcomeTopics = new Set([
]); ]);
for (const topic of topics) { for (const topic of topics) {
await consumer.subscribe({ topic, fromBeginning: true }); // Raw quote volume is a live firehose; replaying retained history can starve
// durable strategy/execution topics and exhaust the writer.
await consumer.subscribe({
topic,
fromBeginning: topic !== config.kafkaTopicRawNearIntentsQuote,
});
} }
const state = { const state = {

View file

@ -6,6 +6,7 @@ import { createArmedStateStore } from '../core/armed-state-store.mjs';
import { startControlApi } from '../core/control-api.mjs'; import { startControlApi } from '../core/control-api.mjs';
import { buildEventEnvelope, parseEventMessage } from '../core/event-envelope.mjs'; import { buildEventEnvelope, parseEventMessage } from '../core/event-envelope.mjs';
import { createLogger, serializeError } from '../core/log.mjs'; import { createLogger, serializeError } from '../core/log.mjs';
import { createRecentIdCache } from '../core/recent-id-cache.mjs';
import { assertInventorySnapshotEvent, assertMarketPriceEvent, assertNormalizedSwapDemand } from '../core/schemas.mjs'; import { assertInventorySnapshotEvent, assertMarketPriceEvent, assertNormalizedSwapDemand } from '../core/schemas.mjs';
import { evaluateTradeOpportunity } from '../core/strategy.mjs'; import { evaluateTradeOpportunity } from '../core/strategy.mjs';
import { loadConfig } from '../lib/config.mjs'; import { loadConfig } from '../lib/config.mjs';
@ -51,6 +52,7 @@ const armedStateStore = createArmedStateStore({
fileName: 'strategy-engine-control.json', fileName: 'strategy-engine-control.json',
initialArmed: config.strategyInitialArmed, initialArmed: config.strategyInitialArmed,
}); });
const seenQuotes = createRecentIdCache({ limit: 5000 });
await consumer.subscribe({ topic: config.kafkaTopicNormSwapDemand, fromBeginning: false }); await consumer.subscribe({ topic: config.kafkaTopicNormSwapDemand, fromBeginning: false });
await consumer.subscribe({ topic: config.kafkaTopicRefMarketPrice, fromBeginning: false }); await consumer.subscribe({ topic: config.kafkaTopicRefMarketPrice, fromBeginning: false });
@ -66,7 +68,6 @@ const state = {
latest_decision: null, latest_decision: null,
recent_decisions: [], recent_decisions: [],
skipped_counts: {}, skipped_counts: {},
seen_quotes: {},
}; };
await consumer.run({ await consumer.run({
@ -109,7 +110,7 @@ async function handleDemand(event) {
if (state.paused) return; if (state.paused) return;
const tradingConfig = await tradingConfigStore.getConfig(); const tradingConfig = await tradingConfigStore.getConfig();
if (state.seen_quotes[event.payload.quote_id]) { if (seenQuotes.has(event.payload.quote_id)) {
const pair = tradingConfig.pairByKey?.get(event.payload.pair || `${event.payload.asset_in}->${event.payload.asset_out}`); const pair = tradingConfig.pairByKey?.get(event.payload.pair || `${event.payload.asset_in}->${event.payload.asset_out}`);
const strategyConfig = pair?.strategyConfig || null; const strategyConfig = pair?.strategyConfig || null;
await publishDecision({ await publishDecision({
@ -131,7 +132,7 @@ async function handleDemand(event) {
return; return;
} }
state.seen_quotes[event.payload.quote_id] = true; seenQuotes.add(event.payload.quote_id);
const evaluation = evaluateTradeOpportunity({ const evaluation = evaluateTradeOpportunity({
demandEvent: event, demandEvent: event,
@ -197,6 +198,7 @@ const controlApi = startControlApi({
getState() { getState() {
return { return {
...state, ...state,
seen_quotes: seenQuotes.getState(),
trading_config: tradingConfigStore.getState(), trading_config: tradingConfigStore.getState(),
durable_control_state: armedStateStore.getState(), durable_control_state: armedStateStore.getState(),
}; };

View file

@ -4,6 +4,7 @@ import { createConsumer } from '../bus/kafka/consumer.mjs';
import { createProducer } from '../bus/kafka/producer.mjs'; import { createProducer } from '../bus/kafka/producer.mjs';
import { createArmedStateStore } from '../core/armed-state-store.mjs'; import { createArmedStateStore } from '../core/armed-state-store.mjs';
import { startControlApi } from '../core/control-api.mjs'; import { startControlApi } from '../core/control-api.mjs';
import { classifyExecuteCommandExpiry } from '../core/executor-command-expiry.mjs';
import { buildEventEnvelope, parseEventMessage } from '../core/event-envelope.mjs'; import { buildEventEnvelope, parseEventMessage } from '../core/event-envelope.mjs';
import { createExecutorStateStore } from '../core/executor-state-store.mjs'; import { createExecutorStateStore } from '../core/executor-state-store.mjs';
import { createIntentRequestController } from '../core/intent-request-controller.mjs'; import { createIntentRequestController } from '../core/intent-request-controller.mjs';
@ -211,6 +212,27 @@ async function handleCommand(event) {
return; return;
} }
const expiry = classifyExecuteCommandExpiry(event);
if (expiry.expired) {
stateStore.markFailed(payload.command_id, {
quote_id: payload.quote_id,
error: {
name: 'StaleExecuteTradeCommand',
message: expiry.reason,
},
});
await publishResult(payload, {
status: 'rejected',
result_code: 'stale_execute_command',
note: 'execute command deadline elapsed before relay submission',
command_age_ms: expiry.age_ms == null ? null : String(expiry.age_ms),
command_deadline_ms: expiry.deadline_ms == null ? null : String(expiry.deadline_ms),
command_deadline_at: expiry.deadline_at,
stale_reason: expiry.reason,
});
return;
}
stateStore.markProcessing(payload.command_id, { stateStore.markProcessing(payload.command_id, {
quote_id: payload.quote_id, quote_id: payload.quote_id,
idempotency_key: payload.idempotency_key, idempotency_key: payload.idempotency_key,
@ -308,7 +330,7 @@ const controlApi = startControlApi({
trading_config: tradingConfigStore.getState(), trading_config: tradingConfigStore.getState(),
...state, ...state,
durable_control_state: armedStateStore.getState(), durable_control_state: armedStateStore.getState(),
durable_state: stateStore.getState(), durable_state: stateStore.getSummary({ limit: 50 }),
}; };
}, },
}, },

View file

@ -0,0 +1,54 @@
const DEFAULT_COMMAND_DEADLINE_MS = 60_000;
export function classifyExecuteCommandExpiry(event, { now = Date.now() } = {}) {
const payload = event?.payload || {};
const observedAtMs = parseTimestamp(
event?.observed_at
|| payload.quote_observed_at
|| payload.decision_at
|| event?.ingested_at,
);
const deadlineMs = parseDeadlineMs(payload.min_deadline_ms);
if (!Number.isFinite(observedAtMs)) {
return {
expired: true,
reason: 'command_timestamp_missing',
age_ms: null,
deadline_ms: deadlineMs,
deadline_at: null,
};
}
if (!Number.isFinite(deadlineMs) || deadlineMs <= 0) {
return {
expired: true,
reason: 'command_deadline_invalid',
age_ms: Math.max(0, now - observedAtMs),
deadline_ms: null,
deadline_at: null,
};
}
const deadlineAtMs = observedAtMs + deadlineMs;
const ageMs = Math.max(0, now - observedAtMs);
return {
expired: now > deadlineAtMs,
reason: now > deadlineAtMs ? 'command_deadline_elapsed' : null,
age_ms: ageMs,
deadline_ms: deadlineMs,
deadline_at: new Date(deadlineAtMs).toISOString(),
};
}
function parseDeadlineMs(value) {
if (value == null || value === '') return DEFAULT_COMMAND_DEADLINE_MS;
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : Number.NaN;
}
function parseTimestamp(value) {
if (!value) return Number.NaN;
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : Number.NaN;
}

View file

@ -2,14 +2,20 @@ import { createJsonStateStore } from './json-state-store.mjs';
const INITIAL_STATE = { const INITIAL_STATE = {
commands: {}, commands: {},
evicted_count: 0,
}; };
export function createExecutorStateStore({ stateDir, fileName = 'trade-executor-commands.json' }) { export function createExecutorStateStore({
stateDir,
fileName = 'trade-executor-commands.json',
maxCommands = 1000,
}) {
const store = createJsonStateStore({ const store = createJsonStateStore({
stateDir, stateDir,
fileName, fileName,
initialState: INITIAL_STATE, initialState: INITIAL_STATE,
}); });
compactStore(store, maxCommands);
return { return {
get(commandId) { get(commandId) {
@ -21,21 +27,24 @@ export function createExecutorStateStore({ stateDir, fileName = 'trade-executor-
}; };
}, },
markProcessing(commandId, metadata) { markProcessing(commandId, metadata) {
return updateCommand(store, commandId, metadata, 'processing'); return updateCommand(store, commandId, metadata, 'processing', maxCommands);
}, },
markSubmitted(commandId, metadata) { markSubmitted(commandId, metadata) {
return updateCommand(store, commandId, metadata, 'submitted'); return updateCommand(store, commandId, metadata, 'submitted', maxCommands);
}, },
markFailed(commandId, metadata) { markFailed(commandId, metadata) {
return updateCommand(store, commandId, metadata, 'failed'); return updateCommand(store, commandId, metadata, 'failed', maxCommands);
}, },
getState() { getState() {
return normalizeState(store.getState()); return normalizeState(store.getState());
}, },
getSummary({ limit = 50 } = {}) {
return summarizeState(store.getState(), { limit });
},
}; };
} }
function updateCommand(store, commandId, metadata, status) { function updateCommand(store, commandId, metadata, status, maxCommands) {
const nextState = store.update((state) => { const nextState = store.update((state) => {
state.commands[commandId] = { state.commands[commandId] = {
...(state.commands[commandId] || {}), ...(state.commands[commandId] || {}),
@ -43,7 +52,7 @@ function updateCommand(store, commandId, metadata, status) {
status, status,
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
}; };
return state; return pruneState(state, maxCommands);
}); });
return nextState.commands[commandId]; return nextState.commands[commandId];
@ -61,5 +70,62 @@ function normalizeState(state) {
}, },
]), ]),
), ),
evicted_count: Number(state.evicted_count || 0),
}; };
} }
function compactStore(store, maxCommands) {
const before = store.getState();
const beforeCount = Object.keys(before.commands || {}).length;
const next = pruneState(before, maxCommands);
const afterCount = Object.keys(next.commands || {}).length;
if (beforeCount !== afterCount || Number(before.evicted_count || 0) !== Number(next.evicted_count || 0)) {
store.setState(next);
}
}
function pruneState(state, maxCommands) {
const maxEntries = Math.max(1, Number(maxCommands) || 1000);
const entries = Object.entries(state.commands || {}).map(([commandId, command]) => [
commandId,
normalizeCommand(command),
]);
entries.sort((left, right) => timestampValue(right[1].updated_at) - timestampValue(left[1].updated_at));
const kept = entries.slice(0, maxEntries);
const evicted = Math.max(0, entries.length - kept.length);
return {
...state,
evicted_count: Number(state.evicted_count || 0) + evicted,
commands: Object.fromEntries(kept),
};
}
function summarizeState(state, { limit = 50 } = {}) {
const normalized = normalizeState(state);
const entries = Object.entries(normalized.commands || {});
entries.sort((left, right) => timestampValue(right[1].updated_at) - timestampValue(left[1].updated_at));
const byStatus = {};
for (const [, command] of entries) {
const status = command.status || 'unknown';
byStatus[status] = (byStatus[status] || 0) + 1;
}
return {
total_commands: entries.length,
evicted_count: normalized.evicted_count,
by_status: byStatus,
latest_updated_at: entries[0]?.[1]?.updated_at || null,
commands: Object.fromEntries(entries.slice(0, Math.max(0, Number(limit) || 50))),
};
}
function normalizeCommand(command) {
return {
...command,
status: command.status === 'completed' ? 'submitted' : command.status,
};
}
function timestampValue(value) {
const parsed = Date.parse(value || '');
return Number.isFinite(parsed) ? parsed : 0;
}

View file

@ -0,0 +1,30 @@
export function createRecentIdCache({ limit = 5000 } = {}) {
const maxEntries = Math.max(1, Number(limit) || 5000);
const ids = new Set();
const order = [];
let evictedCount = 0;
return {
has(id) {
return ids.has(id);
},
add(id) {
if (!id) return this.getState();
if (ids.has(id)) return this.getState();
ids.add(id);
order.push(id);
while (order.length > maxEntries) {
const evicted = order.shift();
if (evicted && ids.delete(evicted)) evictedCount += 1;
}
return this.getState();
},
getState() {
return {
count: ids.size,
limit: maxEntries,
evicted_count: evictedCount,
};
},
};
}

View file

@ -2173,36 +2173,59 @@ export async function refreshQuoteOutcomes(pool, {
btcAsset = null, btcAsset = null,
eureAsset = null, eureAsset = null,
now = Date.now(), now = Date.now(),
submissionLimit = 1000,
inventoryLimit = 5000,
} = {}) { } = {}) {
if (!btcAsset?.assetId || !eureAsset?.assetId) return []; if (!btcAsset?.assetId || !eureAsset?.assetId) return [];
const safeSubmissionLimit = Math.max(1, Number(submissionLimit) || 1000);
const safeInventoryLimit = Math.max(1, Number(inventoryLimit) || 5000);
const submissionsResult = await pool.query(
`
SELECT event_id, observed_at, ingested_at, quote_id, payload
FROM (
SELECT event_id, observed_at, ingested_at, quote_id, payload
FROM trade_execution_results
WHERE payload->>'status' = 'submitted'
ORDER BY COALESCE(observed_at, ingested_at) DESC
LIMIT $1
) recent_submissions
ORDER BY COALESCE(observed_at, ingested_at) ASC
`,
[safeSubmissionLimit],
);
if (!submissionsResult.rows.length) return [];
const quoteIds = [...new Set(submissionsResult.rows.map((row) => row.quote_id).filter(Boolean))];
if (!quoteIds.length) return [];
const [ const [
submissionsResult,
commandsResult, commandsResult,
decisionsResult, decisionsResult,
inventoryResult, inventoryResult,
] = await Promise.all([ ] = await Promise.all([
pool.query(`
SELECT event_id, observed_at, ingested_at, quote_id, payload
FROM trade_execution_results
WHERE payload->>'status' = 'submitted'
ORDER BY COALESCE(observed_at, ingested_at) ASC
`),
pool.query(` pool.query(`
SELECT event_id, observed_at, ingested_at, quote_id, payload SELECT event_id, observed_at, ingested_at, quote_id, payload
FROM execute_trade_commands FROM execute_trade_commands
WHERE quote_id = ANY($1::text[])
ORDER BY COALESCE(observed_at, ingested_at) ASC ORDER BY COALESCE(observed_at, ingested_at) ASC
`), `, [quoteIds]),
pool.query(` pool.query(`
SELECT event_id, observed_at, ingested_at, quote_id, payload SELECT event_id, observed_at, ingested_at, quote_id, payload
FROM trade_decisions FROM trade_decisions
WHERE quote_id = ANY($1::text[])
ORDER BY COALESCE(observed_at, ingested_at) ASC ORDER BY COALESCE(observed_at, ingested_at) ASC
`), `, [quoteIds]),
pool.query(` pool.query(`
SELECT event_id, observed_at, ingested_at, quote_id, payload
FROM (
SELECT event_id, observed_at, ingested_at, quote_id, payload SELECT event_id, observed_at, ingested_at, quote_id, payload
FROM intent_inventory_snapshots FROM intent_inventory_snapshots
ORDER BY COALESCE(observed_at, ingested_at) DESC
LIMIT $1
) recent_inventory_snapshots
ORDER BY COALESCE(observed_at, ingested_at) ASC ORDER BY COALESCE(observed_at, ingested_at) ASC
`), `, [safeInventoryLimit]),
]); ]);
const records = deriveQuoteOutcomeRecords({ const records = deriveQuoteOutcomeRecords({

View file

@ -0,0 +1,45 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { classifyExecuteCommandExpiry } from '../src/core/executor-command-expiry.mjs';
test('execute command expiry rejects commands older than their quote deadline', () => {
const result = classifyExecuteCommandExpiry({
observed_at: '2026-05-13T10:00:00.000Z',
ingested_at: '2026-05-13T10:00:01.000Z',
payload: {
min_deadline_ms: '15000',
},
}, {
now: Date.parse('2026-05-13T10:00:16.000Z'),
});
assert.equal(result.expired, true);
assert.equal(result.reason, 'command_deadline_elapsed');
assert.equal(result.deadline_at, '2026-05-13T10:00:15.000Z');
});
test('execute command expiry keeps fresh commands eligible for relay submission', () => {
const result = classifyExecuteCommandExpiry({
observed_at: '2026-05-13T10:00:00.000Z',
payload: {
min_deadline_ms: '15000',
},
}, {
now: Date.parse('2026-05-13T10:00:14.999Z'),
});
assert.equal(result.expired, false);
assert.equal(result.reason, null);
});
test('execute command expiry fails closed when timestamps are missing', () => {
const result = classifyExecuteCommandExpiry({
payload: {
min_deadline_ms: '15000',
},
});
assert.equal(result.expired, true);
assert.equal(result.reason, 'command_timestamp_missing');
});

View file

@ -40,3 +40,37 @@ test('executor state store normalizes legacy completed markers to submitted', ()
assert.equal(store.get('cmd-legacy').status, 'submitted'); assert.equal(store.get('cmd-legacy').status, 'submitted');
assert.equal(store.getState().commands['cmd-legacy'].status, 'submitted'); assert.equal(store.getState().commands['cmd-legacy'].status, 'submitted');
}); });
test('executor state store prunes old command records before serving state', () => {
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), 'unrip-executor-'));
const statePath = path.join(stateDir, 'trade-executor-commands.json');
fs.writeFileSync(
statePath,
JSON.stringify({
commands: {
'cmd-old': {
status: 'failed',
updated_at: '2026-05-13T10:00:00.000Z',
},
'cmd-mid': {
status: 'submitted',
updated_at: '2026-05-13T10:01:00.000Z',
},
'cmd-new': {
status: 'processing',
updated_at: '2026-05-13T10:02:00.000Z',
},
},
}),
);
const store = createExecutorStateStore({ stateDir, maxCommands: 2 });
const state = store.getState();
assert.deepEqual(Object.keys(state.commands).sort(), ['cmd-mid', 'cmd-new']);
assert.equal(state.evicted_count, 1);
const summary = store.getSummary({ limit: 1 });
assert.equal(summary.total_commands, 2);
assert.deepEqual(Object.keys(summary.commands), ['cmd-new']);
assert.equal(summary.by_status.processing, 1);
});

View file

@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs';
const source = readFileSync(new URL('../src/apps/history-writer.mjs', import.meta.url), 'utf8'); const source = readFileSync(new URL('../src/apps/history-writer.mjs', import.meta.url), 'utf8');
test('history writer consumes from beginning so first events on newly-created topics are durable', () => { test('history writer replays durable topics but joins the raw quote firehose live', () => {
assert.match(source, /consumer\.subscribe\(\{ topic, fromBeginning: true \}\)/); assert.match(source, /fromBeginning:\s*topic !== config\.kafkaTopicRawNearIntentsQuote/);
assert.doesNotMatch(source, /consumer\.subscribe\(\{ topic, fromBeginning: false \}\)/); assert.match(source, /Raw quote volume is a live firehose/);
}); });

View file

@ -0,0 +1,143 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { refreshQuoteOutcomes } from '../src/lib/postgres.mjs';
const btcAsset = {
assetId: 'nep141:nbtc.bridge.near',
decimals: 8,
};
const eureAsset = {
assetId: 'nep141:eure.omft.near',
decimals: 18,
};
test('quote outcome refresh bounds source queries and joins by recent quote ids', async () => {
const queries = [];
const pool = {
async query(sql, params = []) {
queries.push({ sql, params });
if (sql.includes('FROM trade_execution_results')) {
assert.match(sql, /LIMIT \$1/);
assert.equal(params[0], 2);
return {
rows: [
eventRow({
eventId: 'result-1',
quoteId: 'quote-1',
at: '2026-05-13T10:00:10.000Z',
payload: {
status: 'submitted',
result_code: 'quote_response_ok',
quote_id: 'quote-1',
},
}),
],
};
}
if (sql.includes('FROM execute_trade_commands')) {
assert.match(sql, /quote_id = ANY\(\$1::text\[\]\)/);
assert.deepEqual(params[0], ['quote-1']);
return {
rows: [
eventRow({
eventId: 'cmd-1',
quoteId: 'quote-1',
at: '2026-05-13T10:00:09.000Z',
payload: {
command_id: 'cmd-1',
decision_id: 'decision-1',
quote_id: 'quote-1',
min_deadline_ms: '15000',
asset_in: eureAsset.assetId,
asset_out: btcAsset.assetId,
amount_in: '1000000000000000000',
quote_output: {
amount_out: '1000',
},
},
}),
],
};
}
if (sql.includes('FROM trade_decisions')) {
assert.match(sql, /quote_id = ANY\(\$1::text\[\]\)/);
return {
rows: [
eventRow({
eventId: 'decision-1',
quoteId: 'quote-1',
at: '2026-05-13T10:00:08.000Z',
payload: {
decision_id: 'decision-1',
quote_id: 'quote-1',
decision: 'actionable',
},
}),
],
};
}
if (sql.includes('FROM intent_inventory_snapshots')) {
assert.match(sql, /LIMIT \$1/);
assert.equal(params[0], 3);
return {
rows: [
eventRow({
eventId: 'inventory-1',
at: '2026-05-13T10:00:00.000Z',
payload: {
inventory_id: 'inventory-1',
spendable: {
[btcAsset.assetId]: '2000',
[eureAsset.assetId]: '1000000000000000000',
},
},
}),
eventRow({
eventId: 'inventory-2',
at: '2026-05-13T10:00:12.000Z',
payload: {
inventory_id: 'inventory-2',
spendable: {
[btcAsset.assetId]: '1000',
[eureAsset.assetId]: '2000000000000000000',
},
},
}),
],
};
}
if (sql.includes('INSERT INTO quote_outcome_attributions')) {
return { rows: [], rowCount: 1 };
}
throw new Error(`unexpected query: ${sql}`);
},
};
const records = await refreshQuoteOutcomes(pool, {
btcAsset,
eureAsset,
now: Date.parse('2026-05-13T10:00:20.000Z'),
submissionLimit: 2,
inventoryLimit: 3,
});
assert.equal(records.length, 1);
assert.equal(records[0].quote_id, 'quote-1');
assert.equal(queries.filter((entry) => entry.sql.includes('INSERT INTO quote_outcome_attributions')).length, 1);
});
function eventRow({
eventId,
quoteId = null,
at,
payload,
}) {
return {
event_id: eventId,
observed_at: at,
ingested_at: at,
quote_id: quoteId,
payload,
};
}

View file

@ -0,0 +1,22 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createRecentIdCache } from '../src/core/recent-id-cache.mjs';
test('recent id cache evicts old ids while retaining duplicate checks for recent ids', () => {
const cache = createRecentIdCache({ limit: 2 });
cache.add('quote-1');
cache.add('quote-2');
assert.equal(cache.has('quote-1'), true);
cache.add('quote-3');
assert.equal(cache.has('quote-1'), false);
assert.equal(cache.has('quote-2'), true);
assert.equal(cache.has('quote-3'), true);
assert.deepEqual(cache.getState(), {
count: 2,
limit: 2,
evicted_count: 1,
});
});

View file

@ -0,0 +1,12 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
const source = readFileSync(new URL('../src/apps/strategy-engine.mjs', import.meta.url), 'utf8');
test('strategy duplicate quote tracking is bounded and state-safe', () => {
assert.match(source, /createRecentIdCache\(\{ limit: 5000 \}\)/);
assert.match(source, /seenQuotes\.has/);
assert.match(source, /seenQuotes\.getState\(\)/);
assert.doesNotMatch(source, /seen_quotes:\s*\{\}/);
});

View file

@ -14,3 +14,14 @@ test('own request preflight suppresses maker quote responses to avoid self-match
assert.match(source, /own_request_preflight_in_progress/); assert.match(source, /own_request_preflight_in_progress/);
assert.match(source, /avoid self-matching/); assert.match(source, /avoid self-matching/);
}); });
test('trade executor fails closed on stale execute commands before relay submission', () => {
assert.match(source, /classifyExecuteCommandExpiry/);
assert.match(source, /stale_execute_command/);
assert.match(source, /deadline elapsed before relay submission/);
});
test('trade executor exposes summarized durable command state', () => {
assert.match(source, /stateStore\.getSummary\(\{ limit: 50 \}\)/);
assert.doesNotMatch(source, /durable_state:\s*stateStore\.getState\(\)/);
});

View file

@ -13,7 +13,7 @@ function deploymentBlock(name) {
return match[0]; return match[0];
} }
for (const name of ['near-intents-ingest', 'history-writer', 'operator-dashboard']) { for (const name of ['near-intents-ingest', 'history-writer', 'trade-executor', 'operator-dashboard']) {
test(`${name} has memory guardrails for live quote pressure`, () => { test(`${name} has memory guardrails for live quote pressure`, () => {
const block = deploymentBlock(name); const block = deploymentBlock(name);