| Changed at | +Status | +Reason | +Fingerprint | +
|---|---|---|---|
| {formatTimestamp(payload.changed_at || entry.observed_at || entry.ingested_at)} | +{payload.label || payload.status || 'Unavailable'} | +{payload.decisive_reason || 'No reason stored'} | +{payload.status_fingerprint || 'Unavailable'} | +
| No environment status changes stored yet. | +|||
The protocol is paused.
', + }, + }], + }, + }); + const samePollLater = normalizeNearIntentsStatus({ + observedAt: '2026-04-16T12:41:00.000Z', + servicesResponse, + postEnumsResponse, + postsResponse: { + posts: [{ + id: 'PM7LK6N', + title: '1Click Quoting is temporarily stopped', + post_type: 'incident', + latest_update: { + status_id: 'PSCS3IV', + severity_id: 'P187122', + reported_at: 1776342420000, + impacts: [{ service_id: 'PXQFSY1', severity_id: 'PCIGMKW' }], + message: 'The protocol is paused.
', + }, + }], + }, + }); + const updatedOfficialMessage = normalizeNearIntentsStatus({ + observedAt: '2026-04-16T12:42:00.000Z', + servicesResponse, + postEnumsResponse, + postsResponse: { + posts: [{ + id: 'PM7LK6N', + title: '1Click Quoting is temporarily stopped', + post_type: 'incident', + latest_update: { + status_id: 'PSCS3IV', + severity_id: 'P187122', + reported_at: 1776346020000, + impacts: [{ service_id: 'PXQFSY1', severity_id: 'PCIGMKW' }], + message: 'The protocol remains paused for 12 hours.
', + }, + }], + }, + }); + + assert.equal(first.status_fingerprint, samePollLater.status_fingerprint); + assert.notEqual(first.status_fingerprint, updatedOfficialMessage.status_fingerprint); + + const payload = buildNearIntentsStatusEventPayload(updatedOfficialMessage, { + changedAt: '2026-04-16T12:42:00.000Z', + previousFingerprint: first.status_fingerprint, + }); + assert.equal(payload.status_fingerprint, updatedOfficialMessage.status_fingerprint); + assert.equal(payload.previous_status_fingerprint, first.status_fingerprint); + assert.match(payload.environment_status_id, /^near-intents-status-/); +}); diff --git a/test/operator-dashboard-ui-static.test.mjs b/test/operator-dashboard-ui-static.test.mjs index 5aeef1e..d329ae4 100644 --- a/test/operator-dashboard-ui-static.test.mjs +++ b/test/operator-dashboard-ui-static.test.mjs @@ -7,6 +7,7 @@ const fundsSource = readFileSync(new URL('../src/operator-dashboard/static/pages const stylesSource = readFileSync(new URL('../src/operator-dashboard/static/styles.css', import.meta.url), 'utf8'); const serviceCardSource = readFileSync(new URL('../src/operator-dashboard/static/components/ServiceCard.jsx', import.meta.url), 'utf8'); const statusBarSource = readFileSync(new URL('../src/operator-dashboard/static/components/StatusBar.jsx', import.meta.url), 'utf8'); +const systemSource = readFileSync(new URL('../src/operator-dashboard/static/pages/SystemPage.jsx', import.meta.url), 'utf8'); test('strategy page owns consolidated quote lifecycle and successful trade tables', () => { assert.match(strategySource, /Quote lifecycle/); @@ -55,3 +56,11 @@ test('dashboard UI exposes official NEAR upstream status separately from local f assert.match(serviceCardSource, /Upstream at/); assert.match(serviceCardSource, /decisive_reason/); }); + + +test('system page exposes deduped environmental conditions history', () => { + assert.match(systemSource, /Environmental conditions/); + assert.match(systemSource, /Stored only when the normalized official status changes/); + assert.match(systemSource, /NEAR Intents upstream status changes/); + assert.match(systemSource, /status_fingerprint/); +}); diff --git a/test/operator-dashboard.test.mjs b/test/operator-dashboard.test.mjs index c01b5c2..0c36580 100644 --- a/test/operator-dashboard.test.mjs +++ b/test/operator-dashboard.test.mjs @@ -43,6 +43,7 @@ function buildConfig() { kafkaTopicRefMarketPrice: 'ref.market_price', kafkaTopicStateIntentInventory: 'state.intent_inventory', kafkaTopicOpsAlert: 'ops.alert', + kafkaTopicOpsEnvironmentStatus: 'ops.environment_status', kafkaTopicExecTradeResult: 'exec.trade_result', tradingBtc, tradingEure, @@ -221,6 +222,24 @@ test('basic auth resolves operator identity and reuses a session cookie', () => assert.equal(second.via, 'session_cookie'); }); + +test('live status bar preserves initial upstream status during websocket session ready', () => { + const config = buildConfig(); + const state = createDashboardLiveState({ + config, + nearIntentsStatus: { + status: 'disrupted', + label: 'upstream paused', + observed_at: '2026-04-16T12:40:00.000Z', + decisive_reason: '1Click quoting paused.', + }, + }); + + const statusBar = buildLiveStatusBar(state); + assert.equal(statusBar.near_intents_upstream_label, 'upstream paused'); + assert.equal(statusBar.near_intents_upstream_reason, '1Click quoting paused.'); +}); + test('live quote updates stay capped and publish lifecycle rows without refresh', () => { const config = buildConfig(); const state = createDashboardLiveState({ @@ -1617,3 +1636,106 @@ test('dashboard surfaces NEAR upstream disruption without calling submitted work assert.equal(services['history-writer'].health_status, 'online'); assert.equal(services['history-writer'].upstream_status, null); }); + + +test('bootstrap exposes deduped environment status history as environmental conditions', () => { + const config = buildConfig(); + const dashboard = buildDashboardBootstrap({ + config, + auth: { authenticated: true }, + portfolioMetric: null, + inventorySnapshot: null, + marketPrice: null, + recentQuotes: [], + submissionPage: { page: 1, page_size: 20, total: 0, total_pages: 1, items: [] }, + submissionSummary: { total: 0, last_submission_at: null }, + fundingObservations: [], + recentDepositStatuses: [], + recentTradeDecisions: [], + recentExecuteTradeCommands: [], + recentExecutionResults: [], + recentQuoteOutcomes: [], + recentIntentRequests: [], + recentAlertTransitions: [], + recentEnvironmentStatuses: [{ + observed_at: '2026-04-16T12:40:00.000Z', + ingested_at: '2026-04-16T12:40:01.000Z', + payload: { + environment_status_id: 'env-1', + environment_key: 'near_intents_status_page', + status: 'disrupted', + label: 'upstream paused', + status_fingerprint: 'fp-1', + observed_at: '2026-04-16T12:40:00.000Z', + changed_at: '2026-04-16T12:40:00.000Z', + decisive_reason: '1Click quoting paused.', + }, + }], + serviceSnapshots: [ + { + service: 'near-intents-ingest', + label: 'NEAR Intents Ingest', + base_url: 'http://near-intents-ingest', + reachable: true, + health: { ok: true }, + state: {}, + }, + { + service: 'history-writer', + label: 'History Writer', + base_url: 'http://history-writer', + reachable: true, + health: { ok: true }, + state: { + last_environment_status_write_at: '2026-04-16T12:40:01.000Z', + last_environment_status_duplicate_at: '2026-04-16T12:41:01.000Z', + }, + }, + ], + }); + + assert.equal(dashboard.status_bar.near_intents_upstream_label, 'upstream paused'); + assert.equal(dashboard.system.environment_status.current.status_fingerprint, 'fp-1'); + assert.equal(dashboard.system.environment_status.recent_changes.length, 1); + assert.equal(dashboard.system.persistence.last_environment_status_write_at, '2026-04-16T12:40:01.000Z'); + assert.equal(dashboard.system.persistence.last_environment_status_duplicate_at, '2026-04-16T12:41:01.000Z'); +}); + +test('live environment status updates status bar and dashboard history without refresh', () => { + const config = buildConfig(); + const state = createDashboardLiveState({ config }); + const updates = applyDashboardLiveEvent(state, { + topic: config.kafkaTopicOpsEnvironmentStatus, + event: { + observed_at: '2026-04-16T12:40:00.000Z', + ingested_at: '2026-04-16T12:40:01.000Z', + payload: { + environment_status_id: 'env-live-1', + environment_key: 'near_intents_status_page', + status: 'disrupted', + label: 'upstream paused', + status_fingerprint: 'fp-live-1', + observed_at: '2026-04-16T12:40:00.000Z', + changed_at: '2026-04-16T12:40:00.000Z', + decisive_reason: '1Click quoting paused.', + }, + }, + }); + + assert.equal(updates[0].type, 'status_bar.updated'); + assert.equal(updates[0].status_bar.near_intents_upstream_label, 'upstream paused'); + assert.equal(updates[1].type, 'environment_status.updated'); + assert.equal(updates[1].environment_status.payload.status_fingerprint, 'fp-live-1'); + + const dashboard = { + funds: { profitability: {} }, + status_bar: {}, + system: { environment_status: { recent_changes: [] } }, + }; + const reduced = dashboardReducer({ dashboard, session: { authenticated: true } }, { + type: 'socket.message.received', + payload: updates[1], + }); + assert.equal(reduced.dashboard.system.environment_status.current.status_fingerprint, 'fp-live-1'); + assert.equal(reduced.dashboard.system.environment_status.recent_changes.length, 1); +}); diff --git a/test/ops-sentinel-static.test.mjs b/test/ops-sentinel-static.test.mjs index 4e40a0c..328fe2f 100644 --- a/test/ops-sentinel-static.test.mjs +++ b/test/ops-sentinel-static.test.mjs @@ -11,3 +11,12 @@ test('ops sentinel imports executor containment guard used by runtime evaluation /import\s*\{[\s\S]*shouldContainExecutorForAlerts[\s\S]*\}\s*from\s*'\.\.\/core\/runtime-health\.mjs';/, ); }); + + +test('ops sentinel polls official NEAR status and publishes environment status without alerting', () => { + assert.match(source, /pollNearIntentsEnvironmentStatus/); + assert.match(source, /kafkaTopicOpsEnvironmentStatus/); + assert.match(source, /buildNearIntentsStatusEventPayload/); + assert.match(source, /normalized\.status_fingerprint === state\.last_environment_status_fingerprint/); + assert.match(source, /assertEnvironmentStatusEvent/); +});