{"record":{"id":"63a506120cde3161","repo":"koala73/worldmonitor","slug":"get-china-decision-signals-returned-no-canonical-p","errorCode":null,"errorMessage":"get-china-decision-signals returned no canonical payload","messagePattern":"get-china-decision-signals returned no canonical payload","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":1126,"sourceCode":"      },\n    },\n    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n    _execute: async (_params, base, context, execution) => {\n      const url = `${base}/api/intelligence/v1/get-china-decision-signals`;\n      const auth = await buildAuthHeaders(context, 'GET', url, null);\n      const response = await fetchMcpDownstream(url, {\n        headers: { ...auth, 'User-Agent': 'worldmonitor-mcp-edge/1.0' },\n        signal: AbortSignal.timeout(12_000),\n      }, execution);\n      await assertMcpToolFetchOk(response, {\n        operation: 'get-china-decision-signals',\n        tool: 'get_china_decision_signals',\n        auth: context,\n        execution,\n      });\n      const wire = await response.json() as { payloadJson?: unknown };\n      if (typeof wire.payloadJson !== 'string') {\n        throw new Error('get-china-decision-signals returned no canonical payload');\n      }\n      const payload = JSON.parse(wire.payloadJson) as unknown;\n      if (!isChinaDecisionSignalSnapshot(payload)) {\n        throw new Error('get-china-decision-signals returned an invalid canonical payload');\n      }\n      return payload;\n    },\n    _coverageKeys: [\n      'china:policy-events:v1',\n      'military:cross-strait-activity:v1',\n      'military:cross-strait-activity-bootstrap:v1',\n      'market:china:corporate-disclosures:v1',\n      'intelligence:china-decision-signals:v1',\n    ],\n    _apiPaths: [\n      'GET /api/intelligence/v1/get-china-decision-signals',\n    ],\n  },","sourceCodeStart":1108,"sourceCodeEnd":1144,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/api/mcp/registry/rpc-tools.ts#L1108-L1144","documentation":"Plain Error thrown in the get-china-decision-signals tool (api/mcp/registry/rpc-tools.ts:703): the downstream fetch passed auth and returned 200 (assertMcpToolFetchOk did not throw), but response.json() lacks a string payloadJson field. This is a contract violation between the MCP tool and the GET /api/intelligence/v1/get-china-decision-signals handler — the canonical wire shape is { payloadJson: string } and anything else (error envelope with 200, unexpected JSON, missing field) fails fast here rather than corrupting downstream snapshot logic.","triggerScenarios":"Deploying the MCP registry and the intelligence API handler out of sync: handler version that does not yet emit payloadJson, an intermediary (proxy/WAF) returning a 200 JSON body of its own, or a handler bug returning an error envelope without the right status code.","commonSituations":"Partial deploy where api/ routes lag the MCP registry (or vice versa). Staged rollout hitting mixed versions. A gateway rewrite that swallows the handler's error status and returns 200 with an error body. Local dev proxying to a stale backend.","solutions":["curl GET /api/intelligence/v1/get-china-decision-signals directly with valid auth and inspect the JSON — confirm payloadJson exists and is a string","If the handler's response shape changed, update the wire expectation and the isChinaDecisionSignalSnapshot guard together, then redeploy both surfaces","Check for intermediaries returning 200-wrapped error bodies (gateway/proxy config)","Redeploy the lagging surface so registry and handler are version-aligned"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isMissingCanonicalPayload(e) {\n  return e instanceof Error\n    && e.message === 'get-china-decision-signals returned no canonical payload';\n}","tryCatchPattern":"try {\n  const snapshot = await fetchChinaDecisionSignals(context, execution);\n} catch (e) {\n  if (isMissingCanonicalPayload(e)) {\n    // Contract drift between registry and handler — not transient. Probe once to confirm:\n    const probe = await fetch(url, { headers: auth }).then(r => r.json());\n    logContractMismatch('get-china-decision-signals', Object.keys(probe));\n    throw new Error('Downstream contract drift; payloadJson missing', { cause: e });\n  }\n  throw e;\n}","preventionTips":["Add a contract test asserting the endpoint returns { payloadJson: string } on 200","Deploy the MCP registry and intelligence handler as one atomic unit","Treat this error as non-retryable — retrying only re-fetches the same wrong shape"],"tags":["mcp","rpc","contract","china","json"],"backgroundTag":"unexpected-response-shape","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}