| import { getCorsHeaders, isDisallowedOrigin } from './_cors.js'; |
| import { jsonResponse } from './_json-response.js'; |
| import { USER_API_KEY_GATEWAY_VALIDATION_ERROR, validateApiKey } from './_api-key.js'; |
| |
| |
| |
| |
| import { unwrapEnvelope } from './_seed-envelope.js'; |
| |
| import { redisPipeline, getRedisCredentials } from './_upstash-json.js'; |
| import { CII_RISK_SCORE_CACHE_KEYS } from './_cii-risk-cache-keys.js'; |
| import { BOOTSTRAP_CACHE_KEYS } from './_bootstrap-tier-keys.js'; |
|
|
| export const config = { runtime: 'edge' }; |
|
|
| |
| |
| |
| const HEALTH_VERDICT_SNAPSHOT_BASE_KEY = 'health:verdict:v1'; |
| function healthVerdictRedisKey(baseKey, vercelEnv, commitSha) { |
| if (!vercelEnv || vercelEnv === 'production') return baseKey; |
| return `${vercelEnv}:${commitSha?.slice(0, 8) || 'dev'}:${baseKey}`; |
| } |
| const HEALTH_VERDICT_SNAPSHOT_KEY = healthVerdictRedisKey( |
| HEALTH_VERDICT_SNAPSHOT_BASE_KEY, |
| process.env.VERCEL_ENV, |
| process.env.VERCEL_GIT_COMMIT_SHA, |
| ); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const HEALTH_VERDICT_COMPACT_SNAPSHOT_BASE_KEY = 'health:verdict:compact:v1'; |
| const HEALTH_VERDICT_COMPACT_SNAPSHOT_KEY = healthVerdictRedisKey( |
| HEALTH_VERDICT_COMPACT_SNAPSHOT_BASE_KEY, |
| process.env.VERCEL_ENV, |
| process.env.VERCEL_GIT_COMMIT_SHA, |
| ); |
| const HEALTH_VERDICT_SNAPSHOT_TTL_SECONDS = 60; |
| |
| |
| const CHINA_COVERAGE_SUMMARY_KEY = 'health:china-coverage:v1'; |
| const HEALTH_VERDICT_SNAPSHOT_TTL_MS = HEALTH_VERDICT_SNAPSHOT_TTL_SECONDS * 1_000; |
| const HEALTH_VERDICT_REFRESH_LOCK_KEY = `${HEALTH_VERDICT_SNAPSHOT_KEY}:refresh-lock`; |
| |
| |
| const HEALTH_VERDICT_REFRESH_LOCK_TTL_SECONDS = 30; |
| const HEALTH_VERDICT_REFRESH_WAIT_ATTEMPTS = 45; |
| const HEALTH_VERDICT_REFRESH_WAIT_MS = 3_000; |
| |
| |
| |
| const HEALTH_VERDICT_MIN_REDIS_TIMEOUT_MS = 100; |
| const HEALTH_VERDICT_RELEASE_LOCK_SCRIPT = [ |
| "if redis.call('get', KEYS[1]) == ARGV[1] then", |
| " return redis.call('del', KEYS[1])", |
| 'end', |
| 'return 0', |
| ].join('\n'); |
|
|
| |
| |
| |
| const IRAN_EVENTS_ENABLED = (process.env.IRAN_EVENTS_ENABLED ?? 'false').toLowerCase() === 'true'; |
|
|
| const BOOTSTRAP_KEYS = { |
| earthquakes: 'seismology:earthquakes:v1', |
| outages: 'infra:outages:v1', |
| sectors: 'market:sectors:v2', |
| etfFlows: 'market:etf-flows:v1', |
| climateAnomalies: 'climate:anomalies:v2', |
| climateDisasters: 'climate:disasters:v1', |
| climateAirQuality: 'climate:air-quality:v1', |
| co2Monitoring: 'climate:co2-monitoring:v1', |
| oceanIce: 'climate:ocean-ice:v1', |
| wildfires: 'wildfire:fires:v1', |
| wildfiresBootstrap: 'wildfire:fires-bootstrap:v1', |
| marketQuotes: 'market:stocks-bootstrap:v1', |
| commodityQuotes: 'market:commodities-bootstrap:v1', |
| cyberThreats: 'cyber:threats-bootstrap:v2', |
| techReadiness: 'economic:worldbank-techreadiness:v1', |
| progressData: 'economic:worldbank-progress:v1', |
| renewableEnergy: 'economic:worldbank-renewable:v1', |
| positiveGeoEvents: 'positive_events:geo-bootstrap:v1', |
| riskScores: CII_RISK_SCORE_CACHE_KEYS.stale, |
| naturalEvents: 'natural:events:v1', |
| flightDelays: 'aviation:delays-bootstrap:v2', |
| newsInsights: 'news:insights:v1', |
| predictionMarkets: 'prediction:markets-bootstrap:v1', |
| cryptoQuotes: 'market:crypto:v1', |
| gulfQuotes: 'market:gulf-quotes:v1', |
| stablecoinMarkets: 'market:stablecoins:v1', |
| unrestEvents: 'unrest:events:v1', |
| iranEvents: 'conflict:iran-events:v1', |
| ucdpEvents: 'conflict:ucdp-events:v1', |
| ucdpEventsBootstrap: 'conflict:ucdp-events-bootstrap:v1', |
| weatherAlerts: 'weather:alerts:v1', |
| spending: 'economic:spending:v1', |
| techEvents: 'research:tech-events-bootstrap:v1', |
| gdeltIntel: 'intelligence:gdelt-intel:v1', |
| correlationCards: 'correlation:cards-bootstrap:v1', |
| crossStraitActivity: 'military:cross-strait-activity:v1', |
| crossStraitActivityBootstrap: 'military:cross-strait-activity-bootstrap:v1', |
| forecasts: 'forecast:predictions:v2', |
| forecastsBootstrap: 'forecast:predictions-bootstrap:v1', |
| securityAdvisories: 'intelligence:advisories-bootstrap:v1', |
| customsRevenue: 'trade:customs-revenue:v1', |
| comtradeFlows: 'comtrade:flows:v1', |
| blsSeries: 'bls:series:v1', |
| sanctionsPressure: 'sanctions:pressure:v1', |
| crossSourceSignals: 'intelligence:cross-source-signals:v1', |
| sanctionsEntities: 'sanctions:entities:v1', |
| radiationWatch: 'radiation:observations:v1', |
| consumerPricesOverview: 'consumer-prices:overview:ae', |
| consumerPricesCategories: 'consumer-prices:categories:ae:30d', |
| consumerPricesMovers: 'consumer-prices:movers:ae:30d', |
| consumerPricesSpread: 'consumer-prices:retailer-spread:ae:essentials-ae', |
| consumerPricesFreshness: 'consumer-prices:freshness:ae', |
| groceryBasket: 'economic:grocery-basket:v1', |
| bigmac: 'economic:bigmac:v1', |
| fuelPrices: 'economic:fuel-prices:v1', |
| faoFoodPriceIndex: 'economic:fao-ffpi:v1', |
| nationalDebt: 'economic:national-debt:v1', |
| defiTokens: 'market:defi-tokens:v1', |
| aiTokens: 'market:ai-tokens:v1', |
| otherTokens: 'market:other-tokens:v1', |
| fredBatch: 'economic:fred:v1:FEDFUNDS:0', |
| ecbEstr: 'economic:fred:v1:ESTR:0', |
| ecbEuribor3m: 'economic:fred:v1:EURIBOR3M:0', |
| ecbEuribor6m: 'economic:fred:v1:EURIBOR6M:0', |
| ecbEuribor1y: 'economic:fred:v1:EURIBOR1Y:0', |
| fearGreedIndex: 'market:fear-greed:v1', |
| breadthHistory: 'market:breadth-history:v1', |
| euYieldCurve: 'economic:yield-curve-eu:v1', |
| earningsCalendar: 'market:earnings-calendar:v1', |
| econCalendar: 'economic:econ-calendar:v1', |
| chinaMacro: BOOTSTRAP_CACHE_KEYS.chinaMacro, |
| chinaReleaseCalendar: BOOTSTRAP_CACHE_KEYS.chinaReleaseCalendar, |
| chinaCorporateDisclosures: 'market:china:corporate-disclosures:v1', |
| chinaPolicyEvents: 'china:policy-events:v1', |
| chinaDecisionSignals: BOOTSTRAP_CACHE_KEYS.chinaDecisionSignals, |
| cotPositioning: 'market:cot:v1', |
| hyperliquidFlow: 'market:hyperliquid:flow:v1', |
| crudeInventories: 'economic:crude-inventories:v1', |
| natGasStorage: 'economic:nat-gas-storage:v1', |
| spr: 'economic:spr:v1', |
| refineryInputs: 'economic:refinery-inputs:v1', |
| ecbFxRates: 'economic:ecb-fx-rates:v1', |
| eurostatCountryData: 'economic:eurostat-country-data:v1', |
| eurostatHousePrices: 'economic:eurostat:house-prices:v1', |
| eurostatGovDebtQ: 'economic:eurostat:gov-debt-q:v1', |
| eurostatIndProd: 'economic:eurostat:industrial-production:v1', |
| euGasStorage: 'economic:eu-gas-storage:v1', |
| euFsi: 'economic:fsi-eu:v1', |
| shippingStress: 'supply_chain:shipping_stress:v1', |
| diseaseOutbreaks: 'health:disease-outbreaks:v1', |
| healthAirQuality: 'health:air-quality:v1', |
| socialVelocity: 'intelligence:social:reddit:v1', |
| wsbTickers: 'intelligence:wsb-tickers:v1', |
| vpdTrackerRealtime: 'health:vpd-tracker:realtime:v1', |
| vpdTrackerHistorical: 'health:vpd-tracker:historical:v1', |
| electricityPrices: 'energy:electricity:v1:index', |
| gasStorageCountries: 'energy:gas-storage:v1:_countries', |
| aaiiSentiment: 'market:aaii-sentiment:v1', |
| cryptoSectors: 'market:crypto-sectors:v1', |
| ddosAttacks: 'cf:radar:ddos:v1', |
| economicStress: 'economic:stress-index:v1', |
| trafficAnomalies: 'cf:radar:traffic-anomalies:v1', |
| }; |
|
|
| const STANDALONE_KEYS = { |
| chinaCoverage: CHINA_COVERAGE_SUMMARY_KEY, |
| hkoWarnings: 'weather:hko-warnings:v1', |
| humanitarianSummary: 'conflict:humanitarian:v1', |
| |
| |
| newsFeedHealth: 'news:feed-health:v1', |
| newsRecallBenchmark: 'news:recall-benchmark:v1', |
| serviceStatuses: 'infra:service-statuses:v1', |
| macroSignals: 'economic:macro-signals:v1', |
| energyPrices: 'economic:energy:v1:all', |
| bisPolicy: 'economic:bis:policy:v1', |
| bisExchange: 'economic:bis:eer:v1', |
| fxYoy: 'economic:fx:yoy:v1', |
| sharedFxRates: 'shared:fx-rates:v1', |
| bisCredit: 'economic:bis:credit:v1', |
| bisDsr: 'economic:bis:dsr:v1', |
| bisPropertyResidential: 'economic:bis:property-residential:v1', |
| bisPropertyCommercial: 'economic:bis:property-commercial:v1', |
| imfMacro: 'economic:imf:macro:v2', |
| imfGrowth: 'economic:imf:growth:v1', |
| imfLabor: 'economic:imf:labor:v1', |
| imfExternal: 'economic:imf:external:v1', |
| |
| wbExternalDebt: 'economic:wb-external-debt:v1', |
| bisLbs: 'economic:bis-lbs:v1', |
| fatfListing: 'economic:fatf-listing:v1', |
| climateZoneNormals: 'climate:zone-normals:v1', |
| shippingRates: 'supply_chain:shipping:v2', |
| chokepoints: 'supply_chain:chokepoints:v4', |
| minerals: 'supply_chain:minerals:v2', |
| giving: 'giving:summary:v2', |
| gpsjam: 'intelligence:gpsjam:v2', |
| |
| |
| secCikMap: 'intelligence:sec-cik-map:v1', |
| sec8kStream: 'intelligence:sec-8k-stream:v1', |
| theaterPosture: 'theater_posture:sebuf:stale:v1', |
| theaterPostureLive: 'theater-posture:sebuf:v1', |
| theaterPostureBackup: 'theater-posture:sebuf:backup:v1', |
| riskScoresLive: CII_RISK_SCORE_CACHE_KEYS.live, |
| usniFleet: 'usni-fleet:sebuf:v1', |
| usniFleetStale: 'usni-fleet:sebuf:stale:v1', |
| faaDelays: 'aviation:delays:faa:v1', |
| intlDelays: 'aviation:delays:intl:v3', |
| notamClosures: 'aviation:notam:closures:v2', |
| positiveEventsLive: 'positive-events:geo:v1', |
| cableHealth: 'cable-health-v1', |
| submarineCables: 'infrastructure:submarine-cables:v1', |
| cyberThreatsRpc: 'cyber:threats:v2', |
| militaryBases: 'military:bases:active', |
| militaryFlights: 'military:flights:v1', |
| militaryFlightsStale: 'military:flights:stale:v1', |
| |
| |
| |
| |
| militaryCii: 'intelligence:military-cii:v1', |
| globalTendersSam: 'economic:global-tenders:v1:source:sam', |
| globalTendersTed: 'economic:global-tenders:v1:source:ted', |
| globalTendersContractsFinder: 'economic:global-tenders:v1:source:contracts-finder', |
| globalTendersCanadaBuys: 'economic:global-tenders:v1:source:canada-buys', |
| globalTendersGets: 'economic:global-tenders:v1:source:gets', |
| globalTendersWorldBank: 'economic:global-tenders:v1:source:world-bank', |
| crossStraitActivityTaiwanMnd: 'military:cross-strait-activity:v1:source:taiwan-mnd', |
| crossStraitActivityJapanMod: 'military:cross-strait-activity:v1:source:japan-mod', |
| defensePatents: 'patents:defense:latest', |
| temporalAnomalies: 'temporal:anomalies:v1', |
| displacement: `displacement:summary:v1:${new Date().getUTCFullYear()}`, |
| displacementPrev: `displacement:summary:v1:${new Date().getUTCFullYear() - 1}`, |
| acledIntel: 'conflict:acled:v1:all:0:0', |
| satellites: 'intelligence:satellites:tle:v1', |
| portwatch: 'supply_chain:portwatch:v1', |
| portwatchDisruptions: 'portwatch:disruptions:active:v1', |
| portwatchPortActivity: 'supply_chain:portwatch-ports:v1:_countries', |
| corridorrisk: 'supply_chain:corridorrisk:v1', |
| chokepointTransits: 'supply_chain:chokepoint_transits:v1', |
| transitSummaries: 'supply_chain:transit-summaries:v1', |
| |
| |
| comtradeBilateralHs4: 'seed-meta:comtrade:bilateral-hs4', |
| thermalEscalation: 'thermal:escalation:v1', |
| thermalEscalationBootstrap: 'thermal:escalation-bootstrap:v1', |
| tariffTrendsUs: 'trade:tariffs:v1:840:all:10', |
| militaryForecastInputs: 'military:forecast-inputs:stale:v1', |
| gscpi: 'economic:fred:v1:GSCPI:0', |
| forecastFredWalcl: 'economic:fred:v1:WALCL:0', |
| forecastFredT10y2y: 'economic:fred:v1:T10Y2Y:0', |
| forecastFredUnrate: 'economic:fred:v1:UNRATE:0', |
| forecastFredCpiaucsl: 'economic:fred:v1:CPIAUCSL:0', |
| forecastFredDgs10: 'economic:fred:v1:DGS10:0', |
| forecastFredVixcls: 'economic:fred:v1:VIXCLS:0', |
| forecastFredGdp: 'economic:fred:v1:GDP:0', |
| forecastFredM2sl: 'economic:fred:v1:M2SL:0', |
| forecastFredDcoilwtico: 'economic:fred:v1:DCOILWTICO:0', |
| marketImplications: 'intelligence:market-implications:v1', |
| hormuzTracker: 'supply_chain:hormuz_tracker:v1', |
| simulationPackageLatest: 'forecast:simulation-package:latest', |
| simulationOutcomeLatest: 'forecast:simulation-outcome:latest', |
| newsThreatSummary: 'news:threat:summary:v1', |
| climateNews: 'climate:news-intelligence:v1', |
| pizzint: 'intelligence:pizzint:seed:v1', |
| resilienceStaticIndex: 'resilience:static:index:v1', |
| resilienceStaticFao: 'resilience:static:fao', |
| resilienceRanking: 'resilience:ranking:v25', |
| productCatalog: 'product-catalog:v3', |
| energySpineCountries: 'energy:spine:v1:_countries', |
| energyExposure: 'energy:exposure:v1:index', |
| energyMixAll: 'energy:mix:v1:_all', |
| regulatoryActions: 'regulatory:actions:v1', |
| energyIntelligence: 'energy:intelligence:feed:v1', |
| ieaOilStocks: 'energy:iea-oil-stocks:v1:index', |
| oilStocksAnalysis: 'energy:oil-stocks-analysis:v1', |
| eiaPetroleum: 'energy:eia-petroleum:v1', |
| jodiGas: 'energy:jodi-gas:v1:_countries', |
| lngVulnerability: 'energy:lng-vulnerability:v1', |
| jodiOil: 'energy:jodi-oil:v1:_countries', |
| chokepointBaselines: 'energy:chokepoint-baselines:v1', |
| portwatchChokepointsRef: 'portwatch:chokepoints:ref:v1', |
| chokepointFlows: 'energy:chokepoint-flows:v1', |
| emberElectricity: 'energy:ember:v1:_all', |
| resilienceIntervals: 'resilience:intervals:v9:US', |
| sprPolicies: 'energy:spr-policies:v1', |
| pipelinesGas: 'energy:pipelines:gas:v1', |
| pipelinesOil: 'energy:pipelines:oil:v1', |
| storageFacilities: 'energy:storage-facilities:v1', |
| fuelShortages: 'energy:fuel-shortages:v1', |
| energyDisruptions: 'energy:disruptions:v1', |
| energyCrisisPolicies: 'energy:crisis-policies:v1', |
| regionalSnapshots: 'intelligence:regional-snapshots:summary:v1', |
| regionalBriefs: 'intelligence:regional-briefs:summary:v1', |
| recoveryFiscalSpace: 'resilience:recovery:fiscal-space:v1', |
| recoveryReserveAdequacy: 'resilience:recovery:reserve-adequacy:v1', |
| recoveryExternalDebt: 'resilience:recovery:external-debt:v1', |
| recoveryImportHhi: 'resilience:recovery:import-hhi:v1', |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| recoveryReexportShare: 'resilience:recovery:reexport-share:v1', |
| recoverySovereignWealth: 'resilience:recovery:sovereign-wealth:v1', |
| |
| |
| |
| |
| |
| lowCarbonGeneration: 'resilience:low-carbon-generation:v1', |
| fossilElectricityShare: 'resilience:fossil-electricity-share:v1', |
| powerLosses: 'resilience:power-losses:v1', |
| goldExtended: 'market:gold-extended:v1', |
| goldEtfFlows: 'market:gold-etf-flows:v1', |
| goldCbReserves: 'market:gold-cb-reserves:v1', |
| |
| |
| |
| |
| |
| chokepointFlowsRelayHeartbeat: 'relay:heartbeat:chokepoint-flows', |
| climateNewsRelayHeartbeat: 'relay:heartbeat:climate-news', |
| telegramFeed: 'intelligence:telegram-feed:v1', |
| digestNotifications: 'digest:last-run', |
| webcams: 'webcam:cameras:active', |
| forecastResolutions: 'forecast:resolutions:v1', |
| forecastScorecard: 'forecast:scorecard:v1', |
| forecastBets: 'forecast:bets:history:v1', |
| forecastFunnel: 'forecast:funnel:health:v1', |
| researchArxivHnTrending: 'research:arxiv:v1:cs.AI::50', |
| |
| |
| |
| |
| |
| |
| |
| intelHistoryIngestConflictAcled: 'intel-history:ingest-health:conflict:acled-intel:v1', |
| intelHistoryIngestMilitaryCrossStrait: 'intel-history:ingest-health:military:cross-strait-activity:v1', |
| intelHistoryIngestEnergyIntelligence: 'intel-history:ingest-health:energy:intelligence:v1', |
| }; |
|
|
| const SEED_META = { |
| |
| |
| |
| |
| |
| |
| |
| |
| humanitarianSummary: { key: 'seed-meta:conflict:humanitarian', maxStaleMin: 300, minRecordCount: 23 }, |
| chinaCoverage: { key: 'seed-meta:health:china-coverage', maxStaleMin: 180 }, |
| earthquakes: { key: 'seed-meta:seismology:earthquakes', maxStaleMin: 30 }, |
| wildfires: { key: 'seed-meta:wildfire:fires', maxStaleMin: 360 }, |
| wildfiresBootstrap: { key: 'seed-meta:wildfire:fires-bootstrap', maxStaleMin: 360 }, |
| outages: { key: 'seed-meta:infra:outages', maxStaleMin: 30 }, |
| climateAnomalies: { key: 'seed-meta:climate:anomalies', maxStaleMin: 540 }, |
| climateDisasters: { key: 'seed-meta:climate:disasters', maxStaleMin: 720 }, |
| climateAirQuality:{ key: 'seed-meta:health:air-quality', maxStaleMin: 180 }, |
| climateZoneNormals: { key: 'seed-meta:climate:zone-normals', maxStaleMin: 89280 }, |
| co2Monitoring: { key: 'seed-meta:climate:co2-monitoring', maxStaleMin: 4320 }, |
| oceanIce: { key: 'seed-meta:climate:ocean-ice', maxStaleMin: 2880 }, |
| climateNews: { key: 'seed-meta:climate:news-intelligence', maxStaleMin: 90 }, |
| unrestEvents: { key: 'seed-meta:unrest:events', maxStaleMin: 120 }, |
| cyberThreats: { key: 'seed-meta:cyber:threats', maxStaleMin: 240 }, |
| cryptoQuotes: { key: 'seed-meta:market:crypto', maxStaleMin: 30 }, |
| etfFlows: { key: 'seed-meta:market:etf-flows', maxStaleMin: 60 }, |
| gulfQuotes: { key: 'seed-meta:market:gulf-quotes', maxStaleMin: 30 }, |
| stablecoinMarkets:{ key: 'seed-meta:market:stablecoins', maxStaleMin: 60 }, |
| naturalEvents: { key: 'seed-meta:natural:events', maxStaleMin: 540 }, |
| hkoWarnings: { key: 'seed-meta:weather:hko-warnings', maxStaleMin: 540 }, |
| flightDelays: { key: 'seed-meta:aviation:faa', maxStaleMin: 90 }, |
| notamClosures: { key: 'seed-meta:aviation:notam', maxStaleMin: 240 }, |
| predictionMarkets: { key: 'seed-meta:prediction:markets', maxStaleMin: 90, minRecordCount: 20 }, |
| newsInsights: { key: 'seed-meta:news:insights', maxStaleMin: 30 }, |
| |
| newsFeedHealth: { key: 'seed-meta:news:feed-health', maxStaleMin: 2880 }, |
| newsRecallBenchmark: { key: 'seed-meta:news:recall-benchmark', maxStaleMin: 2880 }, |
| marketQuotes: { key: 'seed-meta:market:stocks', maxStaleMin: 30 }, |
| commodityQuotes: { key: 'seed-meta:market:commodities', maxStaleMin: 30 }, |
| goldExtended: { key: 'seed-meta:market:gold-extended', maxStaleMin: 30 }, |
| goldEtfFlows: { key: 'seed-meta:market:gold-etf-flows', maxStaleMin: 2880 }, |
| goldCbReserves: { key: 'seed-meta:market:gold-cb-reserves', maxStaleMin: 44640 }, |
| |
| |
| cableHealth: { key: 'seed-meta:cable-health', maxStaleMin: 90 }, |
| submarineCables: { key: 'seed-meta:infrastructure:submarine-cables', maxStaleMin: 25200 }, |
| macroSignals: { key: 'seed-meta:economic:macro-signals', maxStaleMin: 150 }, |
| chinaMacro: { key: 'seed-meta:economic:china-macro-transport', maxStaleMin: 4_320 }, |
| chinaReleaseCalendar: { key: 'seed-meta:economic:china-release-calendar', maxStaleMin: 4_320 }, |
| chinaCorporateDisclosures: { key: 'seed-meta:market:china-corporate-disclosures', maxStaleMin: 180 }, |
| crossStraitActivity: { key: 'seed-meta:military:cross-strait-activity', maxStaleMin: 720 }, |
| crossStraitActivityBootstrap: { key: 'seed-meta:military:cross-strait-activity-bootstrap', maxStaleMin: 720 }, |
| crossStraitActivityTaiwanMnd: { key: 'seed-meta:military:cross-strait-activity:taiwan-mnd', maxStaleMin: 720 }, |
| crossStraitActivityJapanMod: { key: 'seed-meta:military:cross-strait-activity:japan-mod', maxStaleMin: 720 }, |
| chinaPolicyEvents: { key: 'seed-meta:china:policy-events', maxStaleMin: 2_160 }, |
| chinaDecisionSignals: { key: 'seed-meta:intelligence:china-decision-signals', maxStaleMin: 60, minRecordCount: 6 }, |
| energyPrices: { key: 'seed-meta:economic:energy-prices', maxStaleMin: 150 }, |
| bisPolicy: { key: 'seed-meta:economic:bis', maxStaleMin: 10080 }, |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| bisDsr: { key: 'seed-meta:economic:bis-dsr', maxStaleMin: 2160 }, |
| bisPropertyResidential:{ key: 'seed-meta:economic:bis-property-residential', maxStaleMin: 2160 }, |
| bisPropertyCommercial: { key: 'seed-meta:economic:bis-property-commercial', maxStaleMin: 2160 }, |
| imfMacro: { key: 'seed-meta:economic:imf-macro', maxStaleMin: 100800 }, |
| imfGrowth: { key: 'seed-meta:economic:imf-growth', maxStaleMin: 100800 }, |
| imfLabor: { key: 'seed-meta:economic:imf-labor', maxStaleMin: 100800 }, |
| imfExternal: { key: 'seed-meta:economic:imf-external', maxStaleMin: 100800 }, |
| |
| |
| wbExternalDebt: { key: 'seed-meta:economic:wb-external-debt', maxStaleMin: 100800 }, |
| bisLbs: { key: 'seed-meta:economic:bis-lbs', maxStaleMin: 14400 }, |
| fatfListing: { key: 'seed-meta:economic:fatf-listing', maxStaleMin: 60480 }, |
| shippingRates: { key: 'seed-meta:supply_chain:shipping', maxStaleMin: 420 }, |
| chokepoints: { key: 'seed-meta:supply_chain:chokepoints', maxStaleMin: 60, minRecordCount: 13 }, |
| |
| |
| fxYoy: { key: 'seed-meta:economic:fx-yoy', maxStaleMin: 1500 }, |
| sharedFxRates: { key: 'seed-meta:shared:fx-rates', maxStaleMin: 3600 }, |
| gpsjam: { key: 'seed-meta:intelligence:gpsjam', maxStaleMin: 1440 }, |
| positiveGeoEvents:{ key: 'seed-meta:positive-events:geo', maxStaleMin: 60 }, |
| riskScores: { key: 'seed-meta:intelligence:risk-scores', maxStaleMin: 30, minRecordCount: 3 }, |
| iranEvents: { key: 'seed-meta:conflict:iran-events', maxStaleMin: 20160 }, |
| ucdpEvents: { key: 'seed-meta:conflict:ucdp-events', maxStaleMin: 420 }, |
| ucdpEventsBootstrap: { key: 'seed-meta:conflict:ucdp-events-bootstrap', maxStaleMin: 420 }, |
| acledIntel: { key: 'seed-meta:conflict:acled-intel', maxStaleMin: 38 }, |
| militaryFlights: { key: 'seed-meta:military:flights', maxStaleMin: 30 }, |
| militaryCii: { key: 'seed-meta:intelligence:military-cii', maxStaleMin: 45 }, |
| defensePatents: { key: 'seed-meta:military:defense-patents', maxStaleMin: 25200 }, |
| satellites: { key: 'seed-meta:intelligence:satellites', maxStaleMin: 240 }, |
| temporalAnomalies:{ key: 'seed-meta:temporal:anomalies', maxStaleMin: 45 }, |
| weatherAlerts: { key: 'seed-meta:weather:alerts', maxStaleMin: 45 }, |
| spending: { key: 'seed-meta:economic:spending', maxStaleMin: 120 }, |
| globalTenders: { key: 'seed-meta:economic:global-tenders', maxStaleMin: 180 }, |
| globalTendersSam: { key: 'seed-meta:economic:global-tenders:sam', maxStaleMin: 240 }, |
| globalTendersTed: { key: 'seed-meta:economic:global-tenders:ted', maxStaleMin: 180 }, |
| globalTendersContractsFinder: { key: 'seed-meta:economic:global-tenders:contracts-finder', maxStaleMin: 180 }, |
| globalTendersCanadaBuys: { key: 'seed-meta:economic:global-tenders:canada-buys', maxStaleMin: 180 }, |
| globalTendersGets: { key: 'seed-meta:economic:global-tenders:gets', maxStaleMin: 180 }, |
| globalTendersWorldBank: { key: 'seed-meta:economic:global-tenders:world-bank', maxStaleMin: 180 }, |
| techEvents: { key: 'seed-meta:research:tech-events', maxStaleMin: 480 }, |
| researchArxivHnTrending: { key: 'seed-meta:research:arxiv-hn-trending', maxStaleMin: 150 }, |
| gdeltIntel: { key: 'seed-meta:intelligence:gdelt-intel', maxStaleMin: 45 }, |
| telegramFeed: { key: 'seed-meta:intelligence:telegram-feed:v1', maxStaleMin: 10 }, |
| digestNotifications: { key: 'seed-meta:digest:last-run', maxStaleMin: 90 }, |
| forecasts: { key: 'seed-meta:forecast:predictions', maxStaleMin: 90 }, |
| forecastsBootstrap: { key: 'seed-meta:forecast:predictions-bootstrap', maxStaleMin: 90 }, |
| forecastResolutions: { key: 'seed-meta:forecast:resolutions', maxStaleMin: 2160 }, |
| forecastScorecard: { key: 'seed-meta:forecast:scorecard', maxStaleMin: 2160 }, |
| forecastBets: { key: 'seed-meta:forecast:bets', maxStaleMin: 2880 }, |
| forecastMarketsResolution: { key: 'seed-meta:prediction:markets-resolution', maxStaleMin: 2160 }, |
| forecastFunnel: { key: 'seed-meta:forecast:funnel:health:v1', maxStaleMin: 180 }, |
| sectors: { key: 'seed-meta:market:sectors', maxStaleMin: 30 }, |
| techReadiness: { key: 'seed-meta:economic:worldbank-techreadiness:v1', maxStaleMin: 10080 }, |
| progressData: { key: 'seed-meta:economic:worldbank-progress:v1', maxStaleMin: 10080 }, |
| renewableEnergy: { key: 'seed-meta:economic:worldbank-renewable:v1', maxStaleMin: 10080 }, |
| intlDelays: { key: 'seed-meta:aviation:intl', maxStaleMin: 90 }, |
| |
| theaterPosture: { key: 'seed-meta:theater-posture', maxStaleMin: 60 }, |
| correlationCards: { key: 'seed-meta:correlation:cards', maxStaleMin: 30 }, |
| portwatch: { key: 'seed-meta:supply_chain:portwatch', maxStaleMin: 720 }, |
| portwatchDisruptions: { key: 'seed-meta:portwatch:disruptions', maxStaleMin: 150 }, |
| portwatchPortActivity: { key: 'seed-meta:supply_chain:portwatch-ports', maxStaleMin: 2160, minRecordCount: 174 }, |
| corridorrisk: { key: 'seed-meta:supply_chain:corridorrisk', maxStaleMin: 120 }, |
| chokepointTransits: { key: 'seed-meta:supply_chain:chokepoint_transits', maxStaleMin: 30 }, |
| transitSummaries: { key: 'seed-meta:supply_chain:transit-summaries', maxStaleMin: 30 }, |
| usniFleet: { key: 'seed-meta:military:usni-fleet', maxStaleMin: 720 }, |
| securityAdvisories: { key: 'seed-meta:intelligence:advisories', maxStaleMin: 120 }, |
| secCikMap: { key: 'seed-meta:intelligence:sec-cik-map', maxStaleMin: 2880, minRecordCount: 5000 }, |
| sec8kStream: { key: 'seed-meta:intelligence:sec-8k-stream', maxStaleMin: 120, minRecordCount: 50 }, |
| customsRevenue: { key: 'seed-meta:trade:customs-revenue', maxStaleMin: 1440 }, |
| comtradeFlows: { key: 'seed-meta:trade:comtrade-flows', maxStaleMin: 2880 }, |
| comtradeBilateralHs4: { key: 'seed-meta:comtrade:bilateral-hs4', maxStaleMin: 50400, minRecordCount: 110 }, |
| blsSeries: { key: 'seed-meta:economic:bls-series', maxStaleMin: 2880 }, |
| sanctionsPressure: { key: 'seed-meta:sanctions:pressure', maxStaleMin: 720 }, |
| crossSourceSignals: { key: 'seed-meta:intelligence:cross-source-signals', maxStaleMin: 30 }, |
| regionalSnapshots: { key: 'seed-meta:intelligence:regional-snapshots', maxStaleMin: 720 }, |
| regionalBriefs: { key: 'seed-meta:intelligence:regional-briefs', maxStaleMin: 20160 }, |
| sanctionsEntities: { key: 'seed-meta:sanctions:entities', maxStaleMin: 1440 }, |
| radiationWatch: { key: 'seed-meta:radiation:observations', maxStaleMin: 30 }, |
| groceryBasket: { key: 'seed-meta:economic:grocery-basket', maxStaleMin: 10080 }, |
| bigmac: { key: 'seed-meta:economic:bigmac', maxStaleMin: 10080 }, |
| fuelPrices: { key: 'seed-meta:economic:fuel-prices', maxStaleMin: 10080 }, |
| faoFoodPriceIndex: { key: 'seed-meta:economic:fao-ffpi', maxStaleMin: 86400 }, |
| thermalEscalation: { key: 'seed-meta:thermal:escalation', maxStaleMin: 360 }, |
| thermalEscalationBootstrap: { key: 'seed-meta:thermal:escalation-bootstrap', maxStaleMin: 360 }, |
| nationalDebt: { key: 'seed-meta:economic:national-debt', maxStaleMin: 86400 }, |
| tariffTrendsUs: { key: 'seed-meta:trade:tariffs:v1:840:all:10', maxStaleMin: 540 }, |
| |
| consumerPricesOverview: { key: 'seed-meta:consumer-prices:overview:ae', maxStaleMin: 1500 }, |
| consumerPricesCategories: { key: 'seed-meta:consumer-prices:categories:ae:30d', maxStaleMin: 1500 }, |
| consumerPricesMovers: { key: 'seed-meta:consumer-prices:movers:ae:30d', maxStaleMin: 1500 }, |
| consumerPricesSpread: { key: 'seed-meta:consumer-prices:retailer-spread:ae:essentials-ae', maxStaleMin: 1500 }, |
| consumerPricesFreshness: { key: 'seed-meta:consumer-prices:freshness:ae', maxStaleMin: 1500 }, |
| |
| defiTokens: { key: 'seed-meta:market:token-panels', maxStaleMin: 90 }, |
| aiTokens: { key: 'seed-meta:market:token-panels', maxStaleMin: 90 }, |
| otherTokens: { key: 'seed-meta:market:token-panels', maxStaleMin: 90 }, |
| fredBatch: { key: 'seed-meta:economic:fred:v1:FEDFUNDS:0', maxStaleMin: 1500 }, |
| forecastFredWalcl: { key: 'seed-meta:economic:fred:v1:WALCL:0', maxStaleMin: 1500 }, |
| forecastFredT10y2y: { key: 'seed-meta:economic:fred:v1:T10Y2Y:0', maxStaleMin: 1500 }, |
| forecastFredUnrate: { key: 'seed-meta:economic:fred:v1:UNRATE:0', maxStaleMin: 1500 }, |
| forecastFredCpiaucsl: { key: 'seed-meta:economic:fred:v1:CPIAUCSL:0', maxStaleMin: 1500 }, |
| forecastFredDgs10: { key: 'seed-meta:economic:fred:v1:DGS10:0', maxStaleMin: 1500 }, |
| forecastFredVixcls: { key: 'seed-meta:economic:fred:v1:VIXCLS:0', maxStaleMin: 1500 }, |
| forecastFredGdp: { key: 'seed-meta:economic:fred:v1:GDP:0', maxStaleMin: 1500 }, |
| forecastFredM2sl: { key: 'seed-meta:economic:fred:v1:M2SL:0', maxStaleMin: 1500 }, |
| forecastFredDcoilwtico: { key: 'seed-meta:economic:fred:v1:DCOILWTICO:0', maxStaleMin: 1500 }, |
| ecbEstr: { key: 'seed-meta:economic:ecb-short-rates', maxStaleMin: 4320 }, |
| ecbEuribor3m: { key: 'seed-meta:economic:ecb-short-rates', maxStaleMin: 4320 }, |
| ecbEuribor6m: { key: 'seed-meta:economic:ecb-short-rates', maxStaleMin: 4320 }, |
| ecbEuribor1y: { key: 'seed-meta:economic:ecb-short-rates', maxStaleMin: 4320 }, |
| gscpi: { key: 'seed-meta:economic:gscpi', maxStaleMin: 2880 }, |
| fearGreedIndex: { key: 'seed-meta:market:fear-greed', maxStaleMin: 720 }, |
| breadthHistory: { key: 'seed-meta:market:breadth-history', maxStaleMin: 5760 }, |
| hormuzTracker: { key: 'seed-meta:supply_chain:hormuz_tracker', maxStaleMin: 2880 }, |
| earningsCalendar: { key: 'seed-meta:market:earnings-calendar', maxStaleMin: 1440 }, |
| econCalendar: { key: 'seed-meta:economic:econ-calendar', maxStaleMin: 1440 }, |
| cotPositioning: { key: 'seed-meta:market:cot', maxStaleMin: 14400 }, |
| hyperliquidFlow: { key: 'seed-meta:market:hyperliquid-flow', maxStaleMin: 30 }, |
| crudeInventories: { key: 'seed-meta:economic:crude-inventories', maxStaleMin: 20160 }, |
| natGasStorage: { key: 'seed-meta:economic:nat-gas-storage', maxStaleMin: 20160 }, |
| spr: { key: 'seed-meta:economic:spr', maxStaleMin: 20160 }, |
| refineryInputs: { key: 'seed-meta:economic:refinery-inputs', maxStaleMin: 20160 }, |
| ecbFxRates: { key: 'seed-meta:economic:ecb-fx-rates', maxStaleMin: 5760 }, |
| eurostatCountryData: { key: 'seed-meta:economic:eurostat-country-data', maxStaleMin: 4320 }, |
| eurostatHousePrices: { key: 'seed-meta:economic:eurostat-house-prices', maxStaleMin: 60 * 24 * 50 }, |
| eurostatGovDebtQ: { key: 'seed-meta:economic:eurostat-gov-debt-q', maxStaleMin: 60 * 24 * 14 }, |
| eurostatIndProd: { key: 'seed-meta:economic:eurostat-industrial-production', maxStaleMin: 60 * 24 * 5 }, |
| euGasStorage: { key: 'seed-meta:economic:eu-gas-storage', maxStaleMin: 2880 }, |
| euYieldCurve: { key: 'seed-meta:economic:yield-curve-eu', maxStaleMin: 4320 }, |
| euFsi: { key: 'seed-meta:economic:fsi-eu', maxStaleMin: 5760 }, |
| newsThreatSummary: { key: 'seed-meta:news:threat-summary', maxStaleMin: 60 }, |
| shippingStress: { key: 'seed-meta:supply_chain:shipping_stress', maxStaleMin: 45 }, |
| diseaseOutbreaks: { key: 'seed-meta:health:disease-outbreaks', maxStaleMin: 2880 }, |
| healthAirQuality: { key: 'seed-meta:health:air-quality', maxStaleMin: 180 }, |
| socialVelocity: { key: 'seed-meta:intelligence:social-reddit', maxStaleMin: 540 }, |
| wsbTickers: { key: 'seed-meta:intelligence:wsb-tickers', maxStaleMin: 540 }, |
| pizzint: { key: 'seed-meta:intelligence:pizzint', maxStaleMin: 30 }, |
| productCatalog: { key: 'seed-meta:product-catalog', maxStaleMin: 1080 }, |
| vpdTrackerRealtime: { key: 'seed-meta:health:vpd-tracker', maxStaleMin: 2880 }, |
| vpdTrackerHistorical: { key: 'seed-meta:health:vpd-tracker', maxStaleMin: 2880 }, |
| resilienceStaticIndex: { key: 'seed-meta:resilience:static', maxStaleMin: 576000 }, |
| resilienceStaticFao: { key: 'seed-meta:resilience:static', maxStaleMin: 576000 }, |
| resilienceRanking: { key: 'seed-meta:resilience:ranking', maxStaleMin: 840 }, |
| resilienceIntervals: { key: 'seed-meta:resilience:intervals', maxStaleMin: 840 }, |
| energyExposure: { key: 'seed-meta:economic:owid-energy-mix', maxStaleMin: 50400 }, |
| energyMixAll: { key: 'seed-meta:economic:owid-energy-mix', maxStaleMin: 50400 }, |
| regulatoryActions: { key: 'seed-meta:regulatory:actions', maxStaleMin: 360 }, |
| energySpineCountries: { key: 'seed-meta:energy:spine', maxStaleMin: 2880 }, |
| electricityPrices: { key: 'seed-meta:energy:electricity-prices', maxStaleMin: 2880 }, |
| gasStorageCountries: { key: 'seed-meta:energy:gas-storage-countries', maxStaleMin: 2880 }, |
| energyIntelligence: { key: 'seed-meta:energy:intelligence', maxStaleMin: 720 }, |
| jodiOil: { key: 'seed-meta:energy:jodi-oil', maxStaleMin: 60 * 24 * 40 }, |
| ieaOilStocks: { key: 'seed-meta:energy:iea-oil-stocks', maxStaleMin: 60 * 24 * 40 }, |
| oilStocksAnalysis: { key: 'seed-meta:energy:oil-stocks-analysis', maxStaleMin: 60 * 24 * 50 }, |
| eiaPetroleum: { key: 'seed-meta:energy:eia-petroleum', maxStaleMin: 4320 }, |
| jodiGas: { key: 'seed-meta:energy:jodi-gas', maxStaleMin: 60 * 24 * 40 }, |
| lngVulnerability: { key: 'seed-meta:energy:jodi-gas', maxStaleMin: 60 * 24 * 40 }, |
| chokepointBaselines: { key: 'seed-meta:energy:chokepoint-baselines', maxStaleMin: 60 * 24 * 400 }, |
| sprPolicies: { key: 'seed-meta:energy:spr-policies', maxStaleMin: 60 * 24 * 400 }, |
| pipelinesGas: { key: 'seed-meta:energy:pipelines-gas', maxStaleMin: 20_160 }, |
| pipelinesOil: { key: 'seed-meta:energy:pipelines-oil', maxStaleMin: 20_160 }, |
| storageFacilities: { key: 'seed-meta:energy:storage-facilities', maxStaleMin: 20_160 }, |
| fuelShortages: { key: 'seed-meta:energy:fuel-shortages', maxStaleMin: 2880 }, |
| energyDisruptions: { key: 'seed-meta:energy:disruptions', maxStaleMin: 20_160 }, |
| energyCrisisPolicies: { key: 'seed-meta:energy:crisis-policies', maxStaleMin: 60 * 24 * 400 }, |
| aaiiSentiment: { key: 'seed-meta:market:aaii-sentiment', maxStaleMin: 20160 }, |
| portwatchChokepointsRef: { key: 'seed-meta:portwatch:chokepoints-ref', maxStaleMin: 60 * 24 * 14 }, |
| chokepointFlows: { key: 'seed-meta:energy:chokepoint-flows', maxStaleMin: 720 }, |
| |
| |
| |
| |
| chokepointFlowsRelayHeartbeat: { key: 'relay:heartbeat:chokepoint-flows', maxStaleMin: 480 }, |
| climateNewsRelayHeartbeat: { key: 'relay:heartbeat:climate-news', maxStaleMin: 60 }, |
| emberElectricity: { key: 'seed-meta:energy:ember', maxStaleMin: 2880 }, |
| cryptoSectors: { key: 'seed-meta:market:crypto-sectors', maxStaleMin: 120 }, |
| ddosAttacks: { key: 'seed-meta:cf:radar:ddos', maxStaleMin: 60 }, |
| economicStress: { key: 'seed-meta:economic:stress-index', maxStaleMin: 180 }, |
| marketImplications: { key: 'seed-meta:intelligence:market-implications', maxStaleMin: 120 }, |
| trafficAnomalies: { key: 'seed-meta:cf:radar:traffic-anomalies', maxStaleMin: 60 }, |
| chokepointExposure: { key: 'seed-meta:supply_chain:chokepoint-exposure', maxStaleMin: 2880 }, |
| recoveryFiscalSpace: { key: 'seed-meta:resilience:recovery:fiscal-space', maxStaleMin: 129600 }, |
| recoveryReserveAdequacy: { key: 'seed-meta:resilience:recovery:reserve-adequacy', maxStaleMin: 86400 }, |
| recoveryExternalDebt: { key: 'seed-meta:resilience:recovery:external-debt', maxStaleMin: 86400 }, |
| recoveryImportHhi: { key: 'seed-meta:resilience:recovery:import-hhi', maxStaleMin: 50400 }, |
| |
| recoveryReexportShare: { key: 'seed-meta:resilience:recovery:reexport-share', maxStaleMin: 86400 }, |
| recoverySovereignWealth: { key: 'seed-meta:resilience:recovery:sovereign-wealth', maxStaleMin: 86400 }, |
| |
| |
| |
| |
| lowCarbonGeneration: { key: 'seed-meta:resilience:low-carbon-generation', maxStaleMin: 11520 }, |
| fossilElectricityShare: { key: 'seed-meta:resilience:fossil-electricity-share', maxStaleMin: 11520 }, |
| powerLosses: { key: 'seed-meta:resilience:power-losses', maxStaleMin: 11520 }, |
| webcams: { key: 'seed-meta:webcam:cameras:geo', maxStaleMin: 1440 }, |
| |
| |
| |
| |
| |
| |
| |
| intelHistoryIngestConflictAcled: { key: 'seed-meta:intel-history:conflict:acled-intel', maxStaleMin: 38 }, |
| intelHistoryIngestMilitaryCrossStrait: { key: 'seed-meta:intel-history:military:cross-strait-activity', maxStaleMin: 720 }, |
| intelHistoryIngestEnergyIntelligence: { key: 'seed-meta:intel-history:energy:intelligence', maxStaleMin: 720 }, |
| }; |
|
|
| |
| |
| |
| if (!IRAN_EVENTS_ENABLED) { |
| delete BOOTSTRAP_KEYS.iranEvents; |
| delete SEED_META.iranEvents; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const ON_DEMAND_KEYS = new Set([ |
| |
| |
| |
| |
| 'chinaCoverage', |
| 'riskScoresLive', |
| 'usniFleetStale', 'positiveEventsLive', |
| 'bisPolicy', 'bisExchange', 'bisCredit', |
| |
| |
| |
| |
| 'macroSignals', 'chokepoints', 'minerals', 'giving', |
| 'cyberThreatsRpc', 'militaryBases', 'displacement', |
| 'corridorrisk', |
| 'serviceStatuses', |
| 'militaryForecastInputs', |
| |
| |
| 'simulationPackageLatest', |
| 'simulationOutcomeLatest', |
| |
| |
| |
| |
| |
| |
| 'newsFeedHealth', |
| 'newsRecallBenchmark', |
| 'newsThreatSummary', |
| 'resilienceRanking', |
| 'recoveryFiscalSpace', 'recoveryReserveAdequacy', 'recoveryExternalDebt', |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 'displacementPrev', |
| 'fxYoy', |
| |
| |
| |
| 'hyperliquidFlow', |
| |
| |
| 'chokepointFlowsRelayHeartbeat', |
| |
| |
| |
| |
| |
| |
| 'climateNewsRelayHeartbeat', |
| |
| |
| 'digestNotifications', |
| |
| |
| |
| |
| 'eiaPetroleum', |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 'intelHistoryIngestConflictAcled', |
| 'intelHistoryIngestMilitaryCrossStrait', |
| 'intelHistoryIngestEnergyIntelligence', |
| ]); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const ACTIVATION_MARKERS = { |
| chinaCoverage: 'seed-activated:health:china-coverage', |
| newsFeedHealth: 'seed-activated:news:feed-health', |
| newsRecallBenchmark: 'seed-activated:news:recall-benchmark', |
| |
| |
| |
| intelHistoryIngestConflictAcled: 'seed-activated:intel-history:conflict:acled-intel', |
| intelHistoryIngestMilitaryCrossStrait: 'seed-activated:intel-history:military:cross-strait-activity', |
| intelHistoryIngestEnergyIntelligence: 'seed-activated:intel-history:energy:intelligence', |
| }; |
|
|
| const EMPTY_DATA_OK_KEYS = new Set([ |
| 'notamClosures', 'faaDelays', 'intlDelays', 'gpsjam', 'positiveGeoEvents', 'weatherAlerts', |
| 'earningsCalendar', 'econCalendar', 'cotPositioning', |
| 'usniFleet', |
| 'newsThreatSummary', |
| 'recoveryFiscalSpace', |
| 'ddosAttacks', 'trafficAnomalies', |
| 'resilienceStaticFao', |
| 'cableHealth', |
| 'forecastBets', |
| 'forecastFunnel', |
| |
| |
| |
| 'thermalEscalationBootstrap', |
| 'ucdpEventsBootstrap', |
| 'forecastsBootstrap', |
| 'wildfiresBootstrap', |
| 'crossStraitActivityBootstrap', |
| ]); |
|
|
| |
| |
| |
| |
| |
| |
| const MISSING_DATA_IS_FAILURE_KEYS = new Set([ |
| |
| |
| |
| 'cableHealth', |
| 'notamClosures', |
| 'thermalEscalationBootstrap', |
| 'ucdpEventsBootstrap', |
| 'wildfiresBootstrap', |
| 'forecastsBootstrap', |
| 'positiveGeoEvents', |
| 'crossStraitActivityBootstrap', |
| ]); |
|
|
| |
| |
| const ZERO_RECORD_DATA_OK_KEYS = new Set([ |
| ...EMPTY_DATA_OK_KEYS, |
| 'globalTendersSam', 'globalTendersTed', 'globalTendersContractsFinder', 'globalTendersCanadaBuys', 'globalTendersGets', 'globalTendersWorldBank', |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 'consumerPricesSpread', |
| |
| |
| |
| |
| |
| |
| |
| 'outages', |
| |
| |
| |
| 'chinaCorporateDisclosures', |
| |
| |
| |
| |
| |
| 'intelHistoryIngestConflictAcled', |
| 'intelHistoryIngestMilitaryCrossStrait', |
| 'intelHistoryIngestEnergyIntelligence', |
| ]); |
|
|
| |
| |
| const CASCADE_GROUPS = { |
| theaterPosture: ['theaterPosture', 'theaterPostureLive', 'theaterPostureBackup'], |
| theaterPostureLive: ['theaterPosture', 'theaterPostureLive', 'theaterPostureBackup'], |
| theaterPostureBackup: ['theaterPosture', 'theaterPostureLive', 'theaterPostureBackup'], |
| militaryFlights: ['militaryFlights', 'militaryFlightsStale'], |
| militaryFlightsStale: ['militaryFlights', 'militaryFlightsStale'], |
| |
| |
| displacement: ['displacement', 'displacementPrev'], |
| displacementPrev: ['displacement', 'displacementPrev'], |
| }; |
|
|
|
|
| const NEG_SENTINEL = '__WM_NEG__'; |
|
|
|
|
| function parseRedisValue(raw) { |
| if (!raw || raw === NEG_SENTINEL) return null; |
| try { return JSON.parse(raw); } catch { return raw; } |
| } |
|
|
| |
| |
| |
| |
| function strlenIsData(strlen) { |
| return strlen > 0 && strlen !== NEG_SENTINEL.length; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| const LIST_DATA_KEYS = new Set([ |
| STANDALONE_KEYS.forecastBets, |
| ]); |
|
|
| function dataLenCommand(redisKey) { |
| return [LIST_DATA_KEYS.has(redisKey) ? 'LLEN' : 'STRLEN', redisKey]; |
| } |
|
|
| function keyHasData(redisKey, len) { |
| return LIST_DATA_KEYS.has(redisKey) ? len > 0 : strlenIsData(len); |
| } |
|
|
| function readSeedMeta(seedCfg, keyMetaValues, keyMetaErrors, now) { |
| if (!seedCfg) { |
| return { hasMeta: false, seedAge: null, seedStale: null, seedError: false, sourceUnavailable: false, sourceBlocked: false, metaReadFailed: false, metaCount: null, contentAge: null }; |
| } |
| |
| |
| if (keyMetaErrors.get(seedCfg.key)) { |
| return { hasMeta: false, seedAge: null, seedStale: null, seedError: false, sourceUnavailable: false, sourceBlocked: false, metaReadFailed: true, metaCount: null, contentAge: null }; |
| } |
| |
| |
| |
| |
| |
| const meta = unwrapEnvelope(parseRedisValue(keyMetaValues.get(seedCfg.key))).data; |
| const metaCount = meta?.count ?? meta?.recordCount ?? null; |
| const errorCode = typeof meta?.errorCode === 'string' |
| && /^[A-Z0-9_]{1,64}$/.test(meta.errorCode) |
| ? meta.errorCode |
| : null; |
| if (meta?.status === 'error') { |
| return { |
| hasMeta: true, |
| seedAge: null, |
| seedStale: true, |
| seedError: true, |
| sourceUnavailable: false, |
| sourceBlocked: false, |
| metaReadFailed: false, |
| metaCount, |
| contentAge: null, |
| errorCode, |
| }; |
| } |
| let seedAge = null; |
| let seedStale = true; |
| const fetchedAt = Number(meta?.fetchedAt); |
| if (Number.isFinite(fetchedAt) && fetchedAt > 0) { |
| seedAge = Math.round((now - fetchedAt) / 60_000); |
| seedStale = seedAge > seedCfg.maxStaleMin; |
| } |
| |
| |
| |
| |
| |
| const sourceUnavailable = meta?.sourceState === 'unavailable'; |
| |
| |
| |
| const sourceBlocked = meta?.sourceState === 'blocked'; |
| |
| |
| |
| const sourceDegraded = typeof meta?.sourceState === 'string' |
| && meta.sourceState !== 'ok' |
| && !sourceUnavailable |
| && !sourceBlocked; |
| |
| |
| |
| |
| |
| let contentAge = null; |
| if (meta && typeof meta.maxContentAgeMin === 'number') { |
| const newestItemAt = (typeof meta.newestItemAt === 'number') ? meta.newestItemAt : null; |
| const contentAgeMin = newestItemAt == null ? null : Math.round((now - newestItemAt) / 60_000); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const isFutureDated = contentAgeMin != null && contentAgeMin < 0; |
| contentAge = { |
| newestItemAt, |
| oldestItemAt: (typeof meta.oldestItemAt === 'number') ? meta.oldestItemAt : null, |
| maxContentAgeMin: meta.maxContentAgeMin, |
| contentAgeMin, |
| contentStale: contentAgeMin == null || isFutureDated || contentAgeMin > meta.maxContentAgeMin, |
| }; |
| } |
| return { |
| hasMeta: meta != null, |
| seedAge, |
| seedStale, |
| seedError: sourceDegraded, |
| sourceUnavailable, |
| sourceBlocked, |
| metaReadFailed: false, |
| metaCount, |
| contentAge, |
| errorCode, |
| }; |
| } |
|
|
| function isCascadeCovered(name, hasData, keyStrens, keyErrors) { |
| const siblings = CASCADE_GROUPS[name]; |
| if (!siblings || hasData) return false; |
| for (const sibling of siblings) { |
| if (sibling === name) continue; |
| const sibKey = STANDALONE_KEYS[sibling] ?? BOOTSTRAP_KEYS[sibling]; |
| if (!sibKey) continue; |
| if (keyErrors.get(sibKey)) continue; |
| if (keyHasData(sibKey, keyStrens.get(sibKey) ?? 0)) return true; |
| } |
| return false; |
| } |
|
|
| function classifyKey(name, redisKey, opts, ctx) { |
| const { keyStrens, keyErrors, keyMetaValues, keyMetaErrors, now } = ctx; |
| const seedCfg = SEED_META[name]; |
| const isOnDemand = !!opts.allowOnDemand && ON_DEMAND_KEYS.has(name) |
| && !(ctx.activatedNames && ctx.activatedNames.has(name)); |
|
|
| const meta = readSeedMeta(seedCfg, keyMetaValues, keyMetaErrors, now); |
|
|
| |
| |
| |
| if (keyErrors.get(redisKey) || meta.metaReadFailed) { |
| const entry = { status: 'REDIS_PARTIAL', records: null }; |
| if (seedCfg) entry.maxStaleMin = seedCfg.maxStaleMin; |
| return entry; |
| } |
|
|
| const strlen = keyStrens.get(redisKey) ?? 0; |
| const hasData = keyHasData(redisKey, strlen); |
| const { |
| hasMeta, |
| seedAge, |
| seedStale, |
| seedError, |
| sourceUnavailable, |
| sourceBlocked, |
| metaCount, |
| contentAge, |
| errorCode, |
| } = meta; |
|
|
| |
| |
| const records = hasData ? (metaCount ?? 1) : 0; |
| const cascadeCovered = isCascadeCovered(name, hasData, keyStrens, keyErrors); |
|
|
| let status; |
| |
| |
| |
| |
| |
| if (sourceUnavailable) status = 'NOT_CONFIGURED'; |
| else if (sourceBlocked && name !== 'crossStraitActivityJapanMod') { |
| |
| |
| status = 'SEED_ERROR'; |
| } |
| else if (sourceBlocked && hasData && records > 0 && seedStale !== true) status = 'SOURCE_BLOCKED'; |
| else if (seedError) status = 'SEED_ERROR'; |
| else if (!hasData) { |
| if (cascadeCovered) status = 'OK_CASCADE'; |
| else if (MISSING_DATA_IS_FAILURE_KEYS.has(name) && hasMeta && seedStale !== true) status = 'EMPTY'; |
| else if (EMPTY_DATA_OK_KEYS.has(name)) status = seedStale === true ? 'STALE_SEED' : 'OK'; |
| else if (isOnDemand) status = 'EMPTY_ON_DEMAND'; |
| else status = 'EMPTY'; |
| } else if (records === 0) { |
| |
| |
| if (ZERO_RECORD_DATA_OK_KEYS.has(name)) status = seedStale === true ? 'STALE_SEED' : 'OK'; |
| else if (isOnDemand) status = 'EMPTY_ON_DEMAND'; |
| else status = 'EMPTY_DATA'; |
| } else if (seedStale === true) status = 'STALE_SEED'; |
| |
| |
| |
| |
| |
| else if (seedCfg?.minRecordCount != null && records < seedCfg.minRecordCount) status = 'COVERAGE_PARTIAL'; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| else if (contentAge && contentAge.contentStale) status = 'STALE_CONTENT'; |
| else status = 'OK'; |
|
|
| const entry = { status, records }; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (isOnDemand) entry.onDemand = true; |
| if (seedAge !== null) entry.seedAgeMin = seedAge; |
| if (seedCfg) entry.maxStaleMin = seedCfg.maxStaleMin; |
| if (seedCfg?.minRecordCount != null) entry.minRecordCount = seedCfg.minRecordCount; |
| if (status === 'SEED_ERROR' && errorCode) entry.errorCode = errorCode; |
| |
| |
| |
| if (contentAge) { |
| entry.contentAgeMin = contentAge.contentAgeMin; |
| entry.maxContentAgeMin = contentAge.maxContentAgeMin; |
| } |
| return entry; |
| } |
|
|
| const STATUS_COUNTS = { |
| OK: 'ok', |
| OK_CASCADE: 'ok', |
| |
| |
| |
| |
| |
| NOT_CONFIGURED: 'ok', |
| |
| |
| |
| |
| SOURCE_BLOCKED: 'ok', |
| STALE_SEED: 'warn', |
| SEED_ERROR: 'warn', |
| EMPTY_ON_DEMAND: 'warn', |
| REDIS_PARTIAL: 'warn', |
| COVERAGE_PARTIAL: 'warn', |
| |
| |
| |
| |
| STALE_CONTENT: 'warn', |
| CHINA_DEGRADED: 'warn', |
| CHINA_UNAVAILABLE: 'crit', |
| EMPTY: 'crit', |
| EMPTY_DATA: 'crit', |
| }; |
|
|
| function isValidChinaCoverageSummary(candidate) { |
| if ( |
| !candidate |
| || typeof candidate !== 'object' |
| || candidate.schemaVersion !== 1 |
| || candidate.countryCode !== 'CN' |
| || !['healthy', 'degraded', 'unavailable'].includes(candidate.status) |
| || typeof candidate.evaluatedAt !== 'string' |
| || !Number.isFinite(Date.parse(candidate.evaluatedAt)) |
| || !Array.isArray(candidate.entries) |
| || candidate.entries.length === 0 |
| || candidate.entries.length > 100 |
| || !candidate.counts |
| || typeof candidate.counts !== 'object' |
| ) { |
| return false; |
| } |
|
|
| const ids = new Set(); |
| const actual = { |
| total: candidate.entries.length, |
| launched: 0, |
| planned: 0, |
| blocked: 0, |
| healthy: 0, |
| degraded: 0, |
| unavailable: 0, |
| }; |
| for (const entry of candidate.entries) { |
| if ( |
| !entry |
| || typeof entry !== 'object' |
| || typeof entry.id !== 'string' |
| || entry.id.length === 0 |
| || entry.id.length > 100 |
| || ids.has(entry.id) |
| || !['launched', 'planned', 'blocked'].includes(entry.launchStatus) |
| || !Array.isArray(entry.reasonCodes) |
| || entry.reasonCodes.length > 16 |
| || entry.reasonCodes.some((reason) => typeof reason !== 'string' || reason.length > 64) |
| ) { |
| return false; |
| } |
| ids.add(entry.id); |
| actual[entry.launchStatus]++; |
| if (entry.launchStatus === 'launched') { |
| if (!['healthy', 'degraded', 'unavailable'].includes(entry.status)) return false; |
| actual[entry.status]++; |
| } else if (entry.status !== entry.launchStatus) { |
| return false; |
| } |
| } |
|
|
| if (actual.launched === 0) return false; |
| for (const [name, value] of Object.entries(actual)) { |
| if (candidate.counts[name] !== value) return false; |
| } |
| const expectedStatus = actual.unavailable === actual.launched |
| ? 'unavailable' |
| : actual.degraded > 0 || actual.unavailable > 0 |
| ? 'degraded' |
| : 'healthy'; |
| return candidate.status === expectedStatus; |
| } |
|
|
| function projectChinaCoverageStatus(raw, readError = false) { |
| if (readError) { |
| return { status: 'REDIS_PARTIAL', chinaStatus: null, reason: 'SUMMARY_READ_FAILED' }; |
| } |
| const candidate = typeof raw === 'string' |
| ? unwrapEnvelope(parseRedisValue(raw)).data |
| : unwrapEnvelope(raw).data; |
| if (!isValidChinaCoverageSummary(candidate)) { |
| return { status: 'CHINA_UNAVAILABLE', chinaStatus: 'unavailable', reason: 'SUMMARY_INVALID' }; |
| } |
|
|
| const status = { |
| healthy: 'OK', |
| degraded: 'CHINA_DEGRADED', |
| unavailable: 'CHINA_UNAVAILABLE', |
| }[candidate.status] ?? 'CHINA_UNAVAILABLE'; |
| const problems = Array.isArray(candidate.entries) |
| ? candidate.entries |
| .filter((entry) => entry?.launchStatus === 'launched' && entry?.status !== 'healthy') |
| .map((entry) => ({ id: entry.id, status: entry.status, reasonCodes: entry.reasonCodes ?? [] })) |
| : []; |
| return { |
| status, |
| chinaStatus: candidate.status, |
| evaluatedAt: candidate.evaluatedAt ?? null, |
| counts: candidate.counts ?? null, |
| ...(problems.length > 0 ? { problems } : {}), |
| }; |
| } |
|
|
| function composeChinaCoverageStatus(entry, raw, readError = false) { |
| if (!entry || !['OK', 'STALE_SEED', 'SEED_ERROR'].includes(entry.status)) return entry; |
|
|
| const seedStatus = entry.status; |
| const projected = projectChinaCoverageStatus(raw, readError); |
| if (seedStatus === 'OK') return { ...entry, ...projected }; |
|
|
| |
| |
| |
| |
| if (projected.status === 'OK') { |
| return { ...entry, ...projected, status: seedStatus, seedStatus }; |
| } |
| return { ...entry, ...projected, seedStatus }; |
| } |
|
|
| function parseHealthVerdictSnapshot(raw, now, { requireChecks = true } = {}) { |
| if (typeof raw !== 'string') return null; |
| const snapshot = parseRedisValue(raw); |
| if (!snapshot || typeof snapshot !== 'object') return null; |
| if (typeof snapshot.status !== 'string' || typeof snapshot.checkedAt !== 'string') return null; |
| if (!snapshot.summary || typeof snapshot.summary !== 'object') return null; |
| |
| |
| if (requireChecks && (!snapshot.checks || typeof snapshot.checks !== 'object')) return null; |
|
|
| const checkedAtMs = Date.parse(snapshot.checkedAt); |
| const ageMs = now - checkedAtMs; |
| |
| |
| if (!Number.isFinite(checkedAtMs) || ageMs < 0 || ageMs > HEALTH_VERDICT_SNAPSHOT_TTL_MS) return null; |
| return snapshot; |
| } |
|
|
| async function releaseHealthVerdictRefreshLock(lockToken) { |
| if (!lockToken) return; |
| await redisPipeline([[ |
| 'EVAL', |
| HEALTH_VERDICT_RELEASE_LOCK_SCRIPT, |
| '1', |
| HEALTH_VERDICT_REFRESH_LOCK_KEY, |
| lockToken, |
| ]], 4_000).catch(() => null); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function isProblemStatus(status) { |
| return STATUS_COUNTS[status] !== 'ok'; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function collectFailureLogProblems(checks) { |
| const entries = Object.entries(checks) |
| .filter(([, c]) => isProblemStatus(c.status) && c.status !== 'EMPTY_ON_DEMAND'); |
| return { |
| problemKeys: entries.map(([k, c]) => `${k}:${c.status}${c.seedAgeMin != null ? `(${c.seedAgeMin}min)` : ''}`), |
| |
| |
| sigKeys: entries.map(([k, c]) => `${k}:${c.status}`).sort(), |
| }; |
| } |
|
|
| function healthResponseBody(snapshot, compact) { |
| const body = { |
| status: snapshot.status, |
| summary: snapshot.summary, |
| checkedAt: snapshot.checkedAt, |
| }; |
|
|
| if (!compact) { |
| body.checks = snapshot.checks; |
| return body; |
| } |
|
|
| |
| |
| |
| |
| |
| const problems = snapshot.checks |
| ? Object.fromEntries(Object.entries(snapshot.checks).filter( |
| ([name, check]) => name !== 'chinaDecisionSignals' && isProblemStatus(check.status), |
| )) |
| : { ...(snapshot.problems ?? {}) }; |
| |
| |
| |
| delete problems.chinaDecisionSignals; |
| if (Object.keys(problems).length > 0) body.problems = problems; |
| return body; |
| } |
|
|
| |
| |
| |
| |
| |
| function buildCompactVerdictSnapshot(snapshot) { |
| return healthResponseBody(snapshot, true); |
| } |
|
|
| function healthResponse(snapshot, compact, headers) { |
| let responseHeaders = headers; |
| if (compact) { |
| const { 'CF-Cache-Status': _bypassMarker, ...base } = headers; |
| responseHeaders = { |
| ...base, |
| 'Cache-Control': 'no-store, max-age=0', |
| 'CDN-Cache-Control': 'no-store', |
| }; |
| } |
|
|
| return new Response(JSON.stringify(healthResponseBody(snapshot, compact), null, compact ? 0 : 2), { |
| status: 200, |
| headers: responseHeaders, |
| }); |
| } |
|
|
| export default async function handler(req, ctx) { |
| if (isDisallowedOrigin(req)) { |
| return new Response('Forbidden', { status: 403 }); |
| } |
|
|
| const cors = getCorsHeaders(req, 'GET, OPTIONS'); |
| const headers = { |
| 'Content-Type': 'application/json', |
| 'Cache-Control': 'private, no-store, max-age=0', |
| 'CDN-Cache-Control': 'no-store', |
| 'CF-Cache-Status': 'BYPASS', |
| ...cors, |
| }; |
|
|
| if (req.method === 'OPTIONS') { |
| return new Response(null, { status: 204, headers }); |
| } |
|
|
| const url = new URL(req.url); |
| const compact = url.searchParams.get('compact') === '1'; |
| const wantsHistory = url.searchParams.get('history') === '1'; |
|
|
| if (!compact || wantsHistory) { |
| const keyCheck = await validateApiKey(req, { forceKey: true }); |
| if (keyCheck.required && !keyCheck.valid) { |
| const error = keyCheck.error === USER_API_KEY_GATEWAY_VALIDATION_ERROR |
| ? 'Invalid API key' |
| : keyCheck.error; |
| |
| |
| |
| |
| |
| |
| |
| |
| return jsonResponse( |
| { |
| error, |
| hint: 'Detailed health requires an operator/enterprise API key. Public status: /api/health?compact=1', |
| }, |
| 401, |
| { |
| ...headers, |
| 'WWW-Authenticate': 'ApiKey realm="worldmonitor-health", header="X-WorldMonitor-Key"', |
| } |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (wantsHistory) { |
| const results = await redisPipeline( |
| [ |
| ['GET', 'health:last-failure'], |
| ['LRANGE', 'health:failure-log', '0', '-1'], |
| ], |
| 4_000, |
| ); |
| const parseJson = (raw) => { |
| if (typeof raw !== 'string') return null; |
| try { return JSON.parse(raw); } catch { return null; } |
| }; |
| const lastFailureRaw = results?.[0]?.result; |
| const failureLogRaw = results?.[1]?.result; |
| const body = { |
| lastFailure: parseJson(lastFailureRaw), |
| failureLog: Array.isArray(failureLogRaw) |
| ? failureLogRaw.map(parseJson).filter((e) => e !== null) |
| : [], |
| checkedAt: new Date().toISOString(), |
| }; |
| return new Response(JSON.stringify(body, null, 2), { status: 200, headers }); |
| } |
|
|
| const now = Date.now(); |
|
|
| |
| |
| |
| let refreshLockToken = null; |
| let ownsSnapshotRefreshLock = false; |
| try { |
| if (!getRedisCredentials()) throw new Error('Redis not configured'); |
| |
| |
| |
| const snapshotKey = compact ? HEALTH_VERDICT_COMPACT_SNAPSHOT_KEY : HEALTH_VERDICT_SNAPSHOT_KEY; |
| const snapshotResult = await redisPipeline([['GET', snapshotKey]], 4_000); |
| if (!snapshotResult) throw new Error('Redis request failed'); |
| if (snapshotResult[0]?.error) throw new Error('Redis snapshot read failed'); |
| const cachedSnapshot = parseHealthVerdictSnapshot(snapshotResult[0]?.result, Date.now(), { requireChecks: !compact }); |
| if (cachedSnapshot) return healthResponse(cachedSnapshot, compact, headers); |
|
|
| refreshLockToken = `${now}:${crypto.randomUUID()}`; |
| let lockResult = await redisPipeline([[ |
| 'SET', |
| HEALTH_VERDICT_REFRESH_LOCK_KEY, |
| refreshLockToken, |
| 'EX', |
| String(HEALTH_VERDICT_REFRESH_LOCK_TTL_SECONDS), |
| 'NX', |
| ]], 4_000); |
| if (!lockResult || lockResult[0]?.error) throw new Error('Redis snapshot lock failed'); |
| ownsSnapshotRefreshLock = lockResult[0]?.result === 'OK'; |
|
|
| if (!ownsSnapshotRefreshLock) { |
| |
| |
| |
| |
| const waitDeadline = Date.now() + HEALTH_VERDICT_REFRESH_WAIT_MS; |
| for ( |
| let attempt = 0; |
| attempt < HEALTH_VERDICT_REFRESH_WAIT_ATTEMPTS && Date.now() < waitDeadline; |
| attempt++ |
| ) { |
| const backoffMs = Math.min(100 * (2 ** attempt), 1_000); |
| const jitterMs = Math.floor(Math.random() * 50); |
| const sleepMs = Math.min(backoffMs + jitterMs, Math.max(0, waitDeadline - Date.now())); |
| await new Promise((resolve) => setTimeout(resolve, sleepMs)); |
| const remainingMs = waitDeadline - Date.now(); |
| if (remainingMs < HEALTH_VERDICT_MIN_REDIS_TIMEOUT_MS) break; |
| const redisTimeoutMs = Math.min(4_000, remainingMs); |
| const refreshedResult = await redisPipeline([['GET', snapshotKey]], redisTimeoutMs); |
| if (!refreshedResult || refreshedResult[0]?.error) throw new Error('Redis snapshot wait failed'); |
| const refreshedSnapshot = parseHealthVerdictSnapshot(refreshedResult[0]?.result, Date.now(), { requireChecks: !compact }); |
| if (refreshedSnapshot) return healthResponse(refreshedSnapshot, compact, headers); |
|
|
| lockResult = await redisPipeline([[ |
| 'SET', |
| HEALTH_VERDICT_REFRESH_LOCK_KEY, |
| refreshLockToken, |
| 'EX', |
| String(HEALTH_VERDICT_REFRESH_LOCK_TTL_SECONDS), |
| 'NX', |
| ]], redisTimeoutMs); |
| if (!lockResult || lockResult[0]?.error) throw new Error('Redis snapshot lock retry failed'); |
| ownsSnapshotRefreshLock = lockResult[0]?.result === 'OK'; |
| if (ownsSnapshotRefreshLock) break; |
| } |
| |
| |
| |
| |
| } |
| } catch (err) { |
| if (ownsSnapshotRefreshLock) await releaseHealthVerdictRefreshLock(refreshLockToken); |
| return jsonResponse({ |
| status: 'REDIS_DOWN', |
| error: err.message, |
| checkedAt: new Date(now).toISOString(), |
| }, 503, headers); |
| } |
|
|
| const allDataKeys = [ |
| ...Object.values(BOOTSTRAP_KEYS), |
| ...Object.values(STANDALONE_KEYS), |
| ]; |
| const allMetaKeys = Object.values(SEED_META).map(s => s.key); |
| const activationEntries = Object.entries(ACTIVATION_MARKERS); |
|
|
| |
| |
| |
| |
| let results; |
| try { |
| const commands = [ |
| ...allDataKeys.map(dataLenCommand), |
| ...allMetaKeys.map(k => ['GET', k]), |
| ...activationEntries.map(([, marker]) => ['EXISTS', marker]), |
| ['GET', CHINA_COVERAGE_SUMMARY_KEY], |
| ]; |
| if (!getRedisCredentials()) throw new Error('Redis not configured'); |
| results = await redisPipeline(commands, 8_000); |
| if (!results) throw new Error('Redis request failed'); |
| } catch (err) { |
| if (ownsSnapshotRefreshLock) await releaseHealthVerdictRefreshLock(refreshLockToken); |
| |
| |
| |
| |
| |
| return jsonResponse({ |
| status: 'REDIS_DOWN', |
| error: err.message, |
| checkedAt: new Date(now).toISOString(), |
| }, 503, headers); |
| } |
|
|
| |
| |
| const keyStrens = new Map(); |
| const keyErrors = new Map(); |
| for (let i = 0; i < allDataKeys.length; i++) { |
| const r = results[i]; |
| if (r?.error) keyErrors.set(allDataKeys[i], r.error); |
| keyStrens.set(allDataKeys[i], r?.result ?? 0); |
| } |
| |
| |
| |
| const keyMetaValues = new Map(); |
| const keyMetaErrors = new Map(); |
| for (let i = 0; i < allMetaKeys.length; i++) { |
| const r = results[allDataKeys.length + i]; |
| if (r?.error) keyMetaErrors.set(allMetaKeys[i], r.error); |
| keyMetaValues.set(allMetaKeys[i], r?.result ?? null); |
| } |
| |
| |
| |
| const activatedNames = new Set(); |
| for (let i = 0; i < activationEntries.length; i++) { |
| const r = results[allDataKeys.length + allMetaKeys.length + i]; |
| if (!r?.error && Number(r?.result) === 1) activatedNames.add(activationEntries[i][0]); |
| } |
| const chinaCoverageResult = results[allDataKeys.length + allMetaKeys.length + activationEntries.length]; |
| const chinaCoverageRaw = chinaCoverageResult?.error ? null : chinaCoverageResult?.result; |
|
|
| const classifyCtx = { keyStrens, keyErrors, keyMetaValues, keyMetaErrors, activatedNames, now }; |
| const checks = {}; |
| const counts = { ok: 0, warn: 0, onDemandWarn: 0, staleContent: 0, crit: 0 }; |
| let totalChecks = 0; |
|
|
| const sources = [ |
| [BOOTSTRAP_KEYS, { allowOnDemand: false }], |
| [STANDALONE_KEYS, { allowOnDemand: true }], |
| ]; |
| for (const [registry, opts] of sources) { |
| for (const [name, redisKey] of Object.entries(registry)) { |
| totalChecks++; |
| let entry = classifyKey(name, redisKey, opts, classifyCtx); |
| if (name === 'chinaCoverage') { |
| entry = composeChinaCoverageStatus(entry, chinaCoverageRaw, Boolean(chinaCoverageResult?.error)); |
| } |
| checks[name] = entry; |
| const bucket = STATUS_COUNTS[entry.status] ?? 'warn'; |
| counts[bucket]++; |
| if (entry.status === 'EMPTY_ON_DEMAND') counts.onDemandWarn++; |
| |
| |
| |
| |
| if (entry.status === 'STALE_CONTENT') counts.staleContent++; |
| } |
| } |
|
|
| |
| |
| const realWarnCount = counts.warn - counts.onDemandWarn; |
| const critCount = counts.crit; |
|
|
| let overall; |
| if (critCount === 0 && realWarnCount === 0) overall = 'HEALTHY'; |
| else if (critCount === 0) overall = 'WARNING'; |
| |
| |
| else if (critCount / totalChecks <= 0.03) overall = 'DEGRADED'; |
| else overall = 'UNHEALTHY'; |
|
|
| if (overall !== 'HEALTHY') { |
| |
| |
| |
| const { problemKeys, sigKeys } = collectFailureLogProblems(checks); |
| console.log('[health] %s problems=[%s]', overall, problemKeys.join(', ')); |
| const failureLogEntry = { |
| at: new Date(now).toISOString(), |
| status: overall, |
| critCount, |
| warnCount: realWarnCount, |
| problems: problemKeys, |
| }; |
| |
| |
| |
| |
| |
| const sig = `${overall}|${sigKeys.join(',')}`; |
| const prevSigResult = await redisPipeline([['GET', 'health:failure-log-sig']], 4_000).catch(() => null); |
| const prevSig = prevSigResult?.[0]?.result ?? ''; |
| const persistCmds = [ |
| ['SET', 'health:last-failure', JSON.stringify(failureLogEntry), 'EX', 86400], |
| ]; |
| if (sig !== prevSig) { |
| persistCmds.push( |
| ['LPUSH', 'health:failure-log', JSON.stringify(failureLogEntry)], |
| ['LTRIM', 'health:failure-log', 0, 49], |
| ['EXPIRE', 'health:failure-log', 86400 * 7], |
| ['SET', 'health:failure-log-sig', sig, 'EX', 86400], |
| ); |
| } |
| const persist = redisPipeline(persistCmds, 4_000).catch(() => {}); |
| if (ctx && typeof ctx.waitUntil === 'function') ctx.waitUntil(persist); |
| } else { |
| |
| |
| |
| const clear = redisPipeline([['DEL', 'health:failure-log-sig']], 4_000).catch(() => {}); |
| if (ctx && typeof ctx.waitUntil === 'function') ctx.waitUntil(clear); |
| } |
|
|
| const verdictSnapshot = { |
| status: overall, |
| summary: { |
| total: totalChecks, |
| ok: counts.ok, |
| |
| |
| warn: realWarnCount, |
| onDemandWarn: counts.onDemandWarn, |
| |
| |
| |
| staleContent: counts.staleContent, |
| crit: critCount, |
| }, |
| checkedAt: new Date(now).toISOString(), |
| checks, |
| }; |
|
|
| |
| |
| |
| |
| |
| const snapshotWriteResult = await redisPipeline([ |
| [ |
| 'SET', |
| HEALTH_VERDICT_SNAPSHOT_KEY, |
| JSON.stringify(verdictSnapshot), |
| 'EX', |
| String(HEALTH_VERDICT_SNAPSHOT_TTL_SECONDS), |
| ], |
| [ |
| 'SET', |
| HEALTH_VERDICT_COMPACT_SNAPSHOT_KEY, |
| JSON.stringify(buildCompactVerdictSnapshot(verdictSnapshot)), |
| 'EX', |
| String(HEALTH_VERDICT_SNAPSHOT_TTL_SECONDS), |
| ], |
| ], 4_000).catch(() => null); |
| const snapshotWriteFailed = !snapshotWriteResult |
| || snapshotWriteResult.length !== 2 |
| || snapshotWriteResult.some((entry) => entry?.error); |
| if (ownsSnapshotRefreshLock) await releaseHealthVerdictRefreshLock(refreshLockToken); |
| |
| |
| |
| if (snapshotWriteFailed) console.warn('[health] verdict snapshot write failed'); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| return healthResponse(verdictSnapshot, compact, headers); |
| } |
|
|
| |
| |
| |
| |
| export const __testing__ = { |
| readSeedMeta, |
| classifyKey, |
| healthResponseBody, |
| collectFailureLogProblems, |
| ACTIVATION_MARKERS, |
| STATUS_COUNTS, |
| |
| |
| LIST_DATA_KEYS, |
| dataLenCommand, |
| HEALTH_VERDICT_SNAPSHOT_KEY, |
| HEALTH_VERDICT_COMPACT_SNAPSHOT_KEY, |
| buildCompactVerdictSnapshot, |
| HEALTH_VERDICT_SNAPSHOT_TTL_SECONDS, |
| HEALTH_VERDICT_REFRESH_LOCK_KEY, |
| HEALTH_VERDICT_REFRESH_WAIT_MS, |
| CHINA_COVERAGE_SUMMARY_KEY, |
| projectChinaCoverageStatus, |
| composeChinaCoverageStatus, |
| healthVerdictRedisKey, |
| parseHealthVerdictSnapshot, |
| |
| |
| |
| BOOTSTRAP_KEYS, |
| STANDALONE_KEYS, |
| SEED_META, |
| EMPTY_DATA_OK_KEYS, |
| MISSING_DATA_IS_FAILURE_KEYS, |
| ZERO_RECORD_DATA_OK_KEYS, |
| |
| |
| |
| ON_DEMAND_KEYS, |
| }; |
|
|