Clarify executor controls and alert history
All checks were successful
deploy / deploy (push) Successful in 36s

Proof: Dashboard system controls and alert history stay operator-legible under runtime health flapping without implying nonexistent arm behavior.

Assumptions: Manual executor arming remains intentionally absent from the dashboard for this turn, so resume should mean intake resume only.

Still fake: Ops-sentinel still emits raw runtime transition churn underneath; this change collapses it in the dashboard instead of changing runtime alert hysteresis.
This commit is contained in:
philipp 2026-04-08 21:27:03 +02:00
parent 69be378784
commit 3c1ad1dde4
3 changed files with 151 additions and 8 deletions

View file

@ -130,8 +130,8 @@ const CONTROL_DEFINITIONS = [
action: 'pause',
method: 'POST',
path: '/pause',
label: 'Pause Executor',
description: 'Pause trade-executor command consumption without moving funds.',
label: 'Pause Executor Intake',
description: 'Pause trade-executor command consumption without changing armed state.',
page: 'system',
risk_class: 'safe',
},
@ -140,8 +140,8 @@ const CONTROL_DEFINITIONS = [
action: 'resume',
method: 'POST',
path: '/resume',
label: 'Resume Executor',
description: 'Resume trade-executor command consumption.',
label: 'Resume Executor Intake',
description: 'Resume trade-executor command consumption without changing armed state.',
page: 'system',
risk_class: 'safe',
},
@ -329,11 +329,11 @@ export function buildDashboardBootstrap({
const activeAlerts = normalizeAlertList(
servicesByName['ops-sentinel']?.state?.active_alerts || [],
);
const recentAlerts = normalizeAlertList(
const recentAlerts = summarizeRecentAlertTransitions(normalizeAlertList(
servicesByName['ops-sentinel']?.state?.recent_transitions
|| recentAlertTransitions?.map((entry) => entry.payload)
|| [],
);
));
const profitability = buildProfitabilitySummary({
metric: portfolioMetric,
successfulTradeSummary,
@ -1072,9 +1072,39 @@ function normalizeAlert(alert) {
cleared_at: alert.cleared_at || null,
last_evaluated_at: alert.last_evaluated_at || null,
details: alert.details || {},
transition_count: Number(alert.transition_count || 1),
raised_count: Number(alert.raised_count || (alert.status === 'raised' ? 1 : 0)),
cleared_count: Number(alert.cleared_count || (alert.status === 'cleared' ? 1 : 0)),
};
}
function summarizeRecentAlertTransitions(alerts) {
const summaries = new Map();
for (const alert of alerts || []) {
const key = buildAlertKey(alert);
const existing = summaries.get(key);
if (!existing) {
summaries.set(key, {
...alert,
transition_count: alert.transition_count || 1,
raised_count: alert.status === 'raised' ? 1 : 0,
cleared_count: alert.status === 'cleared' ? 1 : 0,
});
continue;
}
existing.transition_count += 1;
existing.raised_count += alert.status === 'raised' ? 1 : 0;
existing.cleared_count += alert.status === 'cleared' ? 1 : 0;
}
return [...summaries.values()].sort((left, right) => sortTimestamps(
right.cleared_at || right.raised_at || right.last_evaluated_at,
left.cleared_at || left.raised_at || left.last_evaluated_at,
));
}
function appendUniqueRecentQuote(quotes, nextQuote, limit) {
const deduped = [nextQuote, ...quotes.filter((quote) => quote.quote_id !== nextQuote.quote_id)];
return deduped.slice(0, limit);

View file

@ -13,15 +13,31 @@ export default function AlertsGrid({ items, emptyMessage = 'No alerts are active
<div className="service-card" key={`${item.alert_code}:${item.raised_at || item.cleared_at || index}`}>
<div className="service-head">
<strong>{item.alert_code}</strong>
<Pill label={item.severity} stateLabel={item.severity} />
<div className="pills">
<Pill label={item.status || 'unknown'} stateLabel={item.status || 'unknown'} />
<Pill label={item.severity} stateLabel={item.severity} />
</div>
</div>
<div className="service-detail">
<div>{item.reason}</div>
<div>{`Scope ${item.service_scope}`}</div>
<div>{formatTimestamp(item.raised_at || item.cleared_at || item.last_evaluated_at)}</div>
<div>{formatTransitionTimestamp(item)}</div>
{item.transition_count > 1 ? (
<div>{`Transitions ${item.transition_count} (raised ${item.raised_count}, cleared ${item.cleared_count})`}</div>
) : null}
</div>
</div>
))}
</div>
);
}
function formatTransitionTimestamp(item) {
if (item.status === 'cleared' && item.cleared_at) {
return `Cleared ${formatTimestamp(item.cleared_at)}`;
}
if (item.raised_at) {
return `Raised ${formatTimestamp(item.raised_at)}`;
}
return formatTimestamp(item.last_evaluated_at);
}

View file

@ -105,6 +105,10 @@ test('control routing only resolves the allowlisted safe dashboard actions', ()
service: 'liquidity-manager',
action: 'refresh',
});
const resumeExecutor = resolveDashboardControl({
service: 'trade-executor',
action: 'resume',
});
const risky = resolveDashboardControl({
service: 'strategy-engine',
action: 'arm',
@ -112,6 +116,8 @@ test('control routing only resolves the allowlisted safe dashboard actions', ()
assert.equal(refresh?.path, '/refresh');
assert.equal(refresh?.risk_class, 'safe');
assert.equal(resumeExecutor?.path, '/resume');
assert.equal(resumeExecutor?.label, 'Resume Executor Intake');
assert.equal(risky, null);
});
@ -557,6 +563,97 @@ test('ingest disconnected still renders as a critical transport failure', () =>
assert.match(ingest.health_reasons.join(' '), /websocket disconnected/);
});
test('recent alert history collapses repeated flapping transitions into one readable entry', () => {
const config = buildConfig();
const bootstrap = buildDashboardBootstrap({
config,
auth: {
authenticated: true,
subject: 'local-operator',
mode: 'stub',
roles: ['operator'],
},
portfolioMetric: null,
inventorySnapshot: null,
marketPrice: null,
recentQuotes: [],
successfulTrades: {
page: 1,
page_size: 20,
total: 0,
total_pages: 1,
items: [],
},
successfulTradeSummary: {
total: 0,
last_successful_trade_at: null,
},
fundingObservations: [],
recentTradeDecisions: [],
recentAlertTransitions: [],
serviceSnapshots: [
{
service: 'ops-sentinel',
label: 'Ops Sentinel',
base_url: 'http://ops-sentinel',
reachable: true,
health: { ok: true },
state: {
active_alerts: [],
recent_transitions: [
{
alert_code: 'near_intents_quotes_stale',
status: 'raised',
severity: 'critical',
reason: 'quote truth stale',
service_scope: 'near-intents-ingest',
pair: config.activePair,
raised_at: '2026-04-04T09:33:00.000Z',
first_raised_at: '2026-04-04T09:30:00.000Z',
cleared_at: null,
last_evaluated_at: '2026-04-04T09:33:00.000Z',
details: {},
},
{
alert_code: 'near_intents_quotes_stale',
status: 'cleared',
severity: 'critical',
reason: 'quote truth stale',
service_scope: 'near-intents-ingest',
pair: config.activePair,
raised_at: '2026-04-04T09:31:00.000Z',
first_raised_at: '2026-04-04T09:30:00.000Z',
cleared_at: '2026-04-04T09:32:00.000Z',
last_evaluated_at: '2026-04-04T09:32:00.000Z',
details: {},
},
{
alert_code: 'near_intents_quotes_stale',
status: 'raised',
severity: 'critical',
reason: 'quote truth stale',
service_scope: 'near-intents-ingest',
pair: config.activePair,
raised_at: '2026-04-04T09:31:00.000Z',
first_raised_at: '2026-04-04T09:30:00.000Z',
cleared_at: null,
last_evaluated_at: '2026-04-04T09:31:00.000Z',
details: {},
},
],
},
},
],
});
assert.equal(bootstrap.system.alerts.recent.length, 1);
assert.equal(bootstrap.system.alerts.recent[0].alert_code, 'near_intents_quotes_stale');
assert.equal(bootstrap.system.alerts.recent[0].status, 'raised');
assert.equal(bootstrap.system.alerts.recent[0].transition_count, 3);
assert.equal(bootstrap.system.alerts.recent[0].raised_count, 2);
assert.equal(bootstrap.system.alerts.recent[0].cleared_count, 1);
});
test('funding summary includes credited bridge deposits without observer-backed funding observations', () => {
const config = buildConfig();
const bootstrap = buildDashboardBootstrap({