Scope NEAR upstream pauses to tracked assets
Some checks failed
deploy / deploy (push) Failing after 37s
Some checks failed
deploy / deploy (push) Failing after 37s
Proof: npm test; npm run operator-dashboard:build; live NEAR Intents status API normalized the current HOT destination-chain incident as operational for btc:mainnet and eth:100 tracked assets. Assumptions: BSC, MONAD, XLAYER, PLASMA, POL, TON, OP, AVAX, STELLAR, and ADI incidents do not block our configured BTC/Gnosis NEAR Intents quote or executor paths unless the official incident also names core 1Click, solver, message bus, protocol-wide swaps, BTC, or Gnosis scope. Still fake: Status relevance is text/service scoped to configured assets; it is not backed by a venue-provided per-asset machine-readable impact matrix.
This commit is contained in:
parent
4adc705a2b
commit
754a95c6d2
6 changed files with 320 additions and 21 deletions
|
|
@ -576,6 +576,7 @@ async function loadNearIntentsStatus() {
|
|||
postsResponse,
|
||||
postEnumsResponse,
|
||||
observedAt: new Date().toISOString(),
|
||||
trackedAssets: config.trackedAssets,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -631,6 +631,7 @@ async function pollNearIntentsEnvironmentStatus() {
|
|||
postsResponse,
|
||||
postEnumsResponse,
|
||||
observedAt,
|
||||
trackedAssets: config.trackedAssets,
|
||||
});
|
||||
|
||||
state.near_intents_status = normalized;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ export function normalizeNearIntentsStatus({
|
|||
servicesResponse = null,
|
||||
postEnumsResponse = null,
|
||||
observedAt = new Date().toISOString(),
|
||||
trackedAssets = [],
|
||||
relevantChains = [],
|
||||
relevantAssetIds = [],
|
||||
relevantServiceNames = [],
|
||||
} = {}) {
|
||||
const posts = Array.isArray(postsResponse?.posts)
|
||||
? postsResponse.posts
|
||||
|
|
@ -41,31 +45,60 @@ export function normalizeNearIntentsStatus({
|
|||
);
|
||||
const serviceById = new Map(services.map((entry) => [entry.id, entry]));
|
||||
|
||||
const incidents = posts
|
||||
const scope = buildRelevanceScope({
|
||||
trackedAssets,
|
||||
relevantChains,
|
||||
relevantAssetIds,
|
||||
relevantServiceNames,
|
||||
});
|
||||
const activeIncidents = posts
|
||||
.map((post) => normalizePost(post, { statusById, severityById, serviceById }))
|
||||
.filter(Boolean)
|
||||
.filter((post) => post.active);
|
||||
const relevantIncidents = activeIncidents.filter((incident) => (
|
||||
isRelevantIncident(incident, scope)
|
||||
));
|
||||
const unrelatedIncidents = activeIncidents.filter((incident) => (
|
||||
!isRelevantIncident(incident, scope)
|
||||
));
|
||||
|
||||
const affectedServices = [...new Set(
|
||||
incidents.flatMap((incident) => incident.impacts.map((impact) => impact.service_name || impact.service_id)),
|
||||
relevantIncidents.flatMap((incident) => incident.impacts.map((impact) => (
|
||||
impact.service_name || impact.service_id
|
||||
))),
|
||||
)].filter(Boolean);
|
||||
const primaryIncident = incidents[0] || null;
|
||||
const status = incidents.length > 0 ? 'disrupted' : 'operational';
|
||||
const label = incidents.length > 0 ? 'upstream paused' : 'operational';
|
||||
const decisiveReason = primaryIncident
|
||||
? [primaryIncident.title, primaryIncident.message_text].filter(Boolean).join(': ')
|
||||
: 'NEAR Intents status page reports no active incident.';
|
||||
const quotingStopped = incidents.some((incident) => /1click|quoting|solver|swap/i.test(
|
||||
`${incident.title || ''} ${incident.message_text || ''}`,
|
||||
const observedAffectedServices = [...new Set(
|
||||
activeIncidents.flatMap((incident) => incident.impacts.map((impact) => (
|
||||
impact.service_name || impact.service_id
|
||||
))),
|
||||
)].filter(Boolean);
|
||||
const primaryIncident = relevantIncidents[0] || null;
|
||||
const primaryUnrelatedIncident = unrelatedIncidents[0] || null;
|
||||
const status = relevantIncidents.length > 0 ? 'disrupted' : 'operational';
|
||||
const label = relevantIncidents.length > 0 ? 'upstream paused' : 'operational';
|
||||
const decisiveReason = buildDecisiveReason({
|
||||
primaryIncident,
|
||||
primaryUnrelatedIncident,
|
||||
activeIncidentCount: activeIncidents.length,
|
||||
scope,
|
||||
});
|
||||
const quotingStopped = relevantIncidents.some((incident) => /1click|quoting|solver|swap/i.test(
|
||||
incidentText(incident),
|
||||
));
|
||||
const normalized = {
|
||||
observed_at: observedAt,
|
||||
source: 'near_intents_status_page',
|
||||
status,
|
||||
label,
|
||||
current_incident_count: incidents.length,
|
||||
current_incidents: incidents,
|
||||
current_incident_count: relevantIncidents.length,
|
||||
current_incidents: relevantIncidents,
|
||||
affected_services: affectedServices,
|
||||
observed_incident_count: activeIncidents.length,
|
||||
observed_incidents: activeIncidents,
|
||||
observed_affected_services: observedAffectedServices,
|
||||
unrelated_incident_count: unrelatedIncidents.length,
|
||||
unrelated_incidents: unrelatedIncidents,
|
||||
relevance_scope: scope.publicScope,
|
||||
quoting_stopped: quotingStopped,
|
||||
decisive_reason: decisiveReason,
|
||||
};
|
||||
|
|
@ -95,6 +128,12 @@ export function buildNearIntentsStatusEventPayload(status, {
|
|||
current_incident_count: status.current_incident_count || 0,
|
||||
current_incidents: status.current_incidents || [],
|
||||
affected_services: status.affected_services || [],
|
||||
observed_incident_count: status.observed_incident_count || status.current_incident_count || 0,
|
||||
observed_incidents: status.observed_incidents || status.current_incidents || [],
|
||||
observed_affected_services: status.observed_affected_services || status.affected_services || [],
|
||||
unrelated_incident_count: status.unrelated_incident_count || 0,
|
||||
unrelated_incidents: status.unrelated_incidents || [],
|
||||
relevance_scope: status.relevance_scope || null,
|
||||
quoting_stopped: status.quoting_stopped ?? null,
|
||||
};
|
||||
}
|
||||
|
|
@ -104,21 +143,160 @@ export function buildNearIntentsStatusFingerprint(status = {}) {
|
|||
status: status.status || 'unknown',
|
||||
label: status.label || null,
|
||||
decisive_reason: status.decisive_reason || null,
|
||||
current_incidents: (status.current_incidents || []).map((incident) => ({
|
||||
id: incident.id || null,
|
||||
title: incident.title || null,
|
||||
status: incident.status || null,
|
||||
severity: incident.severity || null,
|
||||
last_update_at: incident.last_update_at || null,
|
||||
message_text: incident.message_text || null,
|
||||
impacts: incident.impacts || [],
|
||||
})),
|
||||
current_incidents: (status.current_incidents || []).map(stableIncident),
|
||||
observed_incidents: (status.observed_incidents || status.current_incidents || []).map(stableIncident),
|
||||
affected_services: [...(status.affected_services || [])].sort(),
|
||||
observed_affected_services: [...(status.observed_affected_services || [])].sort(),
|
||||
unrelated_incident_count: status.unrelated_incident_count || 0,
|
||||
relevance_scope: status.relevance_scope || null,
|
||||
quoting_stopped: status.quoting_stopped ?? null,
|
||||
};
|
||||
return crypto.createHash('sha256').update(JSON.stringify(stable)).digest('hex');
|
||||
}
|
||||
|
||||
function stableIncident(incident) {
|
||||
return {
|
||||
id: incident.id || null,
|
||||
title: incident.title || null,
|
||||
status: incident.status || null,
|
||||
severity: incident.severity || null,
|
||||
last_update_at: incident.last_update_at || null,
|
||||
message_text: incident.message_text || null,
|
||||
impacts: incident.impacts || [],
|
||||
};
|
||||
}
|
||||
|
||||
function buildDecisiveReason({
|
||||
primaryIncident,
|
||||
primaryUnrelatedIncident,
|
||||
activeIncidentCount,
|
||||
scope,
|
||||
}) {
|
||||
if (primaryIncident) {
|
||||
return [primaryIncident.title, primaryIncident.message_text].filter(Boolean).join(': ');
|
||||
}
|
||||
if (primaryUnrelatedIncident) {
|
||||
const scopeText = scope.publicScope.terms.length
|
||||
? scope.publicScope.terms.slice(0, 8).join(', ')
|
||||
: 'unscoped deployment';
|
||||
return [
|
||||
`NEAR Intents status page has ${activeIncidentCount} active incident(s), but none match this deployment scope (${scopeText}).`,
|
||||
[primaryUnrelatedIncident.title, primaryUnrelatedIncident.message_text].filter(Boolean).join(': '),
|
||||
].filter(Boolean).join(' ');
|
||||
}
|
||||
return 'NEAR Intents status page reports no active incident.';
|
||||
}
|
||||
|
||||
const CORE_INTENTS_SERVICE_PATTERN = /\b(1click|solver|message bus)\b/i;
|
||||
const GLOBAL_INTENTS_DISRUPTION_PATTERN = /\b(1click|quoting|solver|message bus|protocol is paused|swaps are paused|all swaps)\b/i;
|
||||
|
||||
function isRelevantIncident(incident, scope) {
|
||||
if (scope.unfiltered) return true;
|
||||
|
||||
const text = incidentText(incident);
|
||||
if (GLOBAL_INTENTS_DISRUPTION_PATTERN.test(text)) return true;
|
||||
|
||||
const impactedServices = (incident.impacts || [])
|
||||
.map((impact) => `${impact.service_name || ''} ${impact.service_id || ''}`.trim())
|
||||
.filter(Boolean);
|
||||
if (impactedServices.some((service) => CORE_INTENTS_SERVICE_PATTERN.test(service))) return true;
|
||||
if (impactedServices.some((service) => scope.serviceNames.has(normalizeToken(service)))) return true;
|
||||
|
||||
return scope.termPatterns.some((pattern) => pattern.test(text));
|
||||
}
|
||||
|
||||
function incidentText(incident) {
|
||||
return [
|
||||
incident?.title,
|
||||
incident?.message_text,
|
||||
...(incident?.impacts || []).map((impact) => impact.service_name || impact.service_id),
|
||||
].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
function buildRelevanceScope({
|
||||
trackedAssets = [],
|
||||
relevantChains = [],
|
||||
relevantAssetIds = [],
|
||||
relevantServiceNames = [],
|
||||
} = {}) {
|
||||
const assets = Array.isArray(trackedAssets) ? trackedAssets : [];
|
||||
const configuredChains = Array.isArray(relevantChains) ? relevantChains : [relevantChains];
|
||||
const configuredAssetIds = Array.isArray(relevantAssetIds) ? relevantAssetIds : [relevantAssetIds];
|
||||
const configuredServiceNames = Array.isArray(relevantServiceNames)
|
||||
? relevantServiceNames
|
||||
: [relevantServiceNames];
|
||||
const chains = unique([
|
||||
...configuredChains,
|
||||
...assets.map((asset) => asset?.chain),
|
||||
]);
|
||||
const assetIds = unique([
|
||||
...configuredAssetIds,
|
||||
...assets.map((asset) => asset?.assetId),
|
||||
]);
|
||||
const serviceNames = new Set(configuredServiceNames.map(normalizeToken).filter(Boolean));
|
||||
const terms = unique([
|
||||
...chains.flatMap(chainTerms),
|
||||
...assetIds.flatMap(assetTerms),
|
||||
]).map(normalizeToken).filter(Boolean);
|
||||
|
||||
return {
|
||||
unfiltered: assets.length === 0
|
||||
&& configuredChains.filter(Boolean).length === 0
|
||||
&& configuredAssetIds.filter(Boolean).length === 0
|
||||
&& configuredServiceNames.filter(Boolean).length === 0,
|
||||
termPatterns: terms.map(termPattern),
|
||||
serviceNames,
|
||||
publicScope: {
|
||||
chains,
|
||||
asset_ids: assetIds,
|
||||
service_names: [...serviceNames],
|
||||
terms,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function chainTerms(chain) {
|
||||
const normalized = String(chain || '').trim().toLowerCase();
|
||||
const terms = [normalized];
|
||||
if (normalized === 'btc:mainnet') terms.push('btc', 'bitcoin');
|
||||
const [, evmChainId] = normalized.match(/^eth:(\d+)$/) || [];
|
||||
if (evmChainId === '1') terms.push('ethereum', 'eth mainnet');
|
||||
if (evmChainId === '100') terms.push('gnosis', 'gno', 'xdai');
|
||||
return terms;
|
||||
}
|
||||
|
||||
function assetTerms(assetId) {
|
||||
const normalized = String(assetId || '').trim().toLowerCase();
|
||||
const terms = [normalized];
|
||||
if (normalized.includes('nbtc') || normalized.includes('btc.')) {
|
||||
terms.push('btc', 'bitcoin');
|
||||
}
|
||||
if (normalized.includes('gnosis')) {
|
||||
terms.push('gnosis', 'gno', 'xdai');
|
||||
}
|
||||
return terms;
|
||||
}
|
||||
|
||||
function termPattern(term) {
|
||||
const escaped = escapeRegExp(term);
|
||||
if (/^[a-z0-9]+$/i.test(term)) {
|
||||
return new RegExp(`(^|[^a-z0-9])${escaped}([^a-z0-9]|$)`, 'i');
|
||||
}
|
||||
return new RegExp(escaped, 'i');
|
||||
}
|
||||
|
||||
function normalizeToken(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function unique(values) {
|
||||
return [...new Set((values || []).filter(Boolean))];
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function normalizePost(post, { statusById, severityById, serviceById }) {
|
||||
if (!post || typeof post !== 'object') return null;
|
||||
const latestUpdate = post.latest_update || [...(post.updates || [])].pop() || {};
|
||||
|
|
|
|||
|
|
@ -1625,6 +1625,9 @@ function resolveServiceUpstreamStatus(service, nearIntentsStatus) {
|
|||
decisive_reason: nearIntentsStatus.decisive_reason || null,
|
||||
current_incident_count: nearIntentsStatus.current_incident_count || 0,
|
||||
affected_services: nearIntentsStatus.affected_services || [],
|
||||
observed_incident_count: nearIntentsStatus.observed_incident_count ?? null,
|
||||
observed_affected_services: nearIntentsStatus.observed_affected_services || [],
|
||||
unrelated_incident_count: nearIntentsStatus.unrelated_incident_count || 0,
|
||||
quoting_stopped: nearIntentsStatus.quoting_stopped ?? null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,21 @@ const servicesResponse = {
|
|||
services: [
|
||||
{ id: 'PXQFSY1', name: 'Cross-Chain Bridging', display_name: 'Cross-Chain Bridging' },
|
||||
{ id: 'PLT88AT', name: 'Solvers Network', display_name: 'Solvers Network' },
|
||||
{ id: 'PNEJBRE', name: 'Other Blockchains', display_name: 'Other Blockchains' },
|
||||
],
|
||||
};
|
||||
|
||||
const trackedAssets = [
|
||||
{
|
||||
assetId: 'nep141:nbtc.bridge.near',
|
||||
chain: 'btc:mainnet',
|
||||
},
|
||||
{
|
||||
assetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||
chain: 'eth:100',
|
||||
},
|
||||
];
|
||||
|
||||
test('NEAR Intents status normalizer exposes current quoting disruption as upstream paused evidence', () => {
|
||||
const normalized = normalizeNearIntentsStatus({
|
||||
observedAt: '2026-04-16T12:40:00.000Z',
|
||||
|
|
@ -85,6 +97,40 @@ test('resolved NEAR Intents status posts do not make the relay look disrupted',
|
|||
assert.match(normalized.decisive_reason, /no active incident/i);
|
||||
});
|
||||
|
||||
test('scoped NEAR Intents status ignores unrelated paused destination chains', () => {
|
||||
const normalized = normalizeNearIntentsStatus({
|
||||
observedAt: '2026-05-07T15:55:00.000Z',
|
||||
servicesResponse,
|
||||
postEnumsResponse,
|
||||
trackedAssets,
|
||||
postsResponse: {
|
||||
posts: [{
|
||||
id: 'PO2LXSS',
|
||||
title: 'BSC, TON, XML destinations are temporarily paused',
|
||||
post_type: 'incident',
|
||||
latest_update: {
|
||||
status_id: 'PSCS3IV',
|
||||
severity_id: 'P187122',
|
||||
reported_at: 1778165160000,
|
||||
impacts: [{ service_id: 'PNEJBRE', severity_id: 'PCIGMKW' }],
|
||||
message: '<p>HOT bridge is having a reliability incident. Full list of chains: BSC, MONAD, XLAYER, PLASMA, POL, TON, OP, AVAX, STELLAR, ADI</p>',
|
||||
},
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(normalized.status, 'operational');
|
||||
assert.equal(normalized.label, 'operational');
|
||||
assert.equal(normalized.current_incident_count, 0);
|
||||
assert.equal(normalized.observed_incident_count, 1);
|
||||
assert.equal(normalized.unrelated_incident_count, 1);
|
||||
assert.equal(normalized.quoting_stopped, false);
|
||||
assert.deepEqual(normalized.affected_services, []);
|
||||
assert.deepEqual(normalized.observed_affected_services, ['Other Blockchains']);
|
||||
assert.match(normalized.decisive_reason, /none match this deployment scope/);
|
||||
assert.match(normalized.decisive_reason, /BSC, TON, XML/);
|
||||
});
|
||||
|
||||
|
||||
test('NEAR Intents status fingerprint is stable across polls and changes on official updates', () => {
|
||||
const first = normalizeNearIntentsStatus({
|
||||
|
|
|
|||
|
|
@ -1735,6 +1735,76 @@ test('dashboard surfaces NEAR upstream disruption without calling submitted work
|
|||
});
|
||||
|
||||
|
||||
test('dashboard does not pause relay services for unrelated destination-chain incident', () => {
|
||||
const config = buildConfig();
|
||||
const nearIntentsStatus = {
|
||||
source: 'near_intents_status_page',
|
||||
status: 'operational',
|
||||
label: 'operational',
|
||||
observed_at: '2026-05-07T15:55:00.000Z',
|
||||
decisive_reason: 'NEAR Intents status page has 1 active incident(s), but none match this deployment scope.',
|
||||
current_incident_count: 0,
|
||||
affected_services: [],
|
||||
observed_incident_count: 1,
|
||||
observed_affected_services: ['Other Blockchains'],
|
||||
unrelated_incident_count: 1,
|
||||
quoting_stopped: false,
|
||||
};
|
||||
|
||||
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: [],
|
||||
nearIntentsStatus,
|
||||
serviceSnapshots: [
|
||||
{
|
||||
service: 'near-intents-ingest',
|
||||
label: 'NEAR Intents Ingest',
|
||||
base_url: 'http://near-intents-ingest',
|
||||
reachable: true,
|
||||
health: { ok: true },
|
||||
state: { ingest: { connected: true, last_message_at: '2026-05-07T15:54:59.000Z' } },
|
||||
},
|
||||
{
|
||||
service: 'trade-executor',
|
||||
label: 'Trade Executor',
|
||||
base_url: 'http://trade-executor',
|
||||
reachable: true,
|
||||
health: { ok: true },
|
||||
state: { relay: { connected: true, last_message_at: '2026-05-07T15:54:59.000Z' } },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(dashboard.status_bar.near_intents_upstream_status, 'operational');
|
||||
assert.match(dashboard.status_bar.near_intents_upstream_reason, /none match this deployment scope/);
|
||||
|
||||
const services = Object.fromEntries(
|
||||
dashboard.system.service_health.map((service) => [service.service, service]),
|
||||
);
|
||||
assert.equal(services['near-intents-ingest'].health_status, 'online');
|
||||
assert.equal(services['near-intents-ingest'].health_label, 'online');
|
||||
assert.equal(services['near-intents-ingest'].health_ok, true);
|
||||
assert.equal(services['trade-executor'].health_status, 'online');
|
||||
assert.equal(services['trade-executor'].health_label, 'online');
|
||||
assert.equal(services['trade-executor'].upstream_status.status, 'operational');
|
||||
assert.equal(services['trade-executor'].upstream_status.unrelated_incident_count, 1);
|
||||
});
|
||||
|
||||
|
||||
test('bootstrap exposes deduped environment status history as environmental conditions', () => {
|
||||
const config = buildConfig();
|
||||
const dashboard = buildDashboardBootstrap({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue