{"record":{"id":"ef548453824486d2","repo":"koala73/worldmonitor","slug":"the-submarine-cable-catalog-is-unavailable","errorCode":null,"errorMessage":"The submarine-cable catalog is unavailable","messagePattern":"The submarine-cable catalog is unavailable","errorType":"exception","errorClass":"McpSourceUnavailableError","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/analysis-tools.ts","lineNumber":502,"sourceCode":"        checks,\n      );\n      const cables = submarineCablesToCableInputs(cablesPayload);\n      const graph = buildDependencyGraph({ cables, waterways: MCP_CASCADE_WATERWAYS });\n      const stats = getGraphStats(graph);\n\n      const sourceId = typeof params.source_id === 'string' ? params.source_id.trim() : '';\n      if (!sourceId) {\n        const catalog: Record<string, Array<{ id: string; name: string }>> = {};\n        for (const node of graph.nodes.values()) {\n          if (node.type === 'country') continue;\n          (catalog[node.type] ??= []).push({ id: node.id, name: node.name });\n        }\n        return { ...freshness, data: { catalog, cascade: null, stats } };\n      }\n\n      if (!graph.nodes.has(sourceId)) {\n        if (cablesPayload === null && sourceId.startsWith('cable:')) {\n          throw new McpSourceUnavailableError(\n            'The submarine-cable catalog is unavailable',\n            freshness.unavailable_inputs,\n            freshness.failed_inputs,\n          );\n        }\n        const sample = [...graph.nodes.keys()].filter((id) => !id.startsWith('country-')).slice(0, 12);\n        return {\n          ...freshness,\n          data: { catalog: null, cascade: null, stats },\n          error: `unknown source_id \"${sourceId}\" — call without source_id for the full catalog`,\n          known_id_sample: sample,\n        };\n      }\n\n      const rawLevel = Number(params.disruption_level ?? 1);\n      const disruptionLevel = Math.min(1, Math.max(0.1, Number.isFinite(rawLevel) ? rawLevel : 1));\n      const cascade = calculateCascade(graph, sourceId, disruptionLevel);\n      return { ...freshness, data: { catalog: null, cascade, stats } };","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp/registry/analysis-tools.ts#L484-L520","documentation":"Thrown by the simulate_infrastructure_cascade MCP tool when the user passes a source_id starting with 'cable:' but the submarine-cable catalog cache (infrastructure:submarine-cables:v1) returned null — meaning the graph has no cable nodes, so the requested cable id cannot exist. The tool distinguishes this from a genuinely unknown id: if the cable catalog WERE loaded but the id was wrong, it returns a helpful error envelope with known_id_sample instead of throwing.","triggerScenarios":"Calling simulate_infrastructure_cascade with source_id='cable:<anything>' when the infrastructure:submarine-cables:v1 Redis key is null — before the cable seeder has run, after a Redis flush, or during a TeleGeography fetch outage. The cable-prefix check fires because the tool infers the user intended a cable node that the empty graph cannot contain.","commonSituations":"First call after deploy before the submarine-cable seeder (long 25200-minute staleness budget, so it seeds less frequently) has written its key; a TeleGeography source outage that left the cache empty; Redis eviction of the cable key under memory pressure.","solutions":["Call simulate_infrastructure_cascade with NO source_id first — it returns the full catalog built from whatever data IS present, confirming whether the cable cache is populated.","Wait for the submarine-cable seeder to run and verify seed-meta:infrastructure:submarine-cables exists in GET /api/health.","If you need a non-cable node (chokepoint, pipeline, port), use its correct prefix — those come from curated registries (MCP_CASCADE_WATERWAYS) that do not depend on the cable cache.","Retry after the seeder cycle; the cable table changes slowly so a transient null resolves on the next seed."],"exampleFix":"// before — requests a cable node while the catalog is unseeded\ntool: 'simulate_infrastructure_cascade', params: { source_id: 'cable:sea-me-we-6' }\n// after — fetch the catalog first to confirm availability\ntool: 'simulate_infrastructure_cascade', params: {}","handlingStrategy":"validation","validationCode":"// Before requesting a cable source_id, verify the catalog is populated\nconst catalogResult = await callMcpTool('simulate_infrastructure_cascade', {});\nif (!catalogResult.data?.catalog || Object.keys(catalogResult.data.catalog).length === 0) {\n  // Cable cache is empty — do not request a cable: source_id\n  throw new Error('Submarine-cable catalog not yet seeded');\n}\nconst cableExists = catalogResult.data.catalog.cable?.some(c => c.id === desiredCableId);\nif (!cableExists) throw new Error(`Cable ${desiredCableId} not in catalog`);","typeGuard":"function isMcpSourceUnavailableError(e: unknown): e is { unavailableInputs: string[]; failedInputs: string[] } & Error {\n  return e instanceof Error && (e as any).name === 'McpSourceUnavailableError';\n}","tryCatchPattern":"try {\n  const result = await callMcpTool('simulate_infrastructure_cascade', { source_id: 'cable:sea-me-we-6' });\n} catch (e) {\n  if (isMcpSourceUnavailableError(e) && e.message.includes('submarine-cable')) {\n    // Catalog not seeded — call without source_id to get the catalog once it loads\n    const catalog = await callMcpTool('simulate_infrastructure_cascade', {});\n  } else throw e;\n}","preventionTips":["Always call simulate_infrastructure_cascade without source_id first to get the catalog and confirm the cable cache is live.","Verify the requested source_id exists in the returned catalog before requesting a cascade.","Use non-cable node types (chokepoint, pipeline, port) which come from curated registries, not the seeded cable table."],"tags":["mcp","redis","source-unavailable","infrastructure","submarine-cable"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}