{"record":{"id":"c27309e5c763904c","repo":"koala73/worldmonitor","slug":"mnd-proxy-response-invalid","errorCode":"MND_PROXY_RESPONSE_INVALID","errorMessage":"MND_PROXY_RESPONSE_INVALID","messagePattern":"MND_PROXY_RESPONSE_INVALID","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/cross-strait-activity/adapters.mjs","lineNumber":1728,"sourceCode":"  if (diagnostic) diagnostic.stage = 'parse';\n  return { text, status: response.status };\n}\n\nasync function fetchBoundedText(fetchFn, url, sourceContract, diagnostic = null) {\n  const { text } = await fetchBoundedTextWithStatus(fetchFn, url, sourceContract, diagnostic);\n  return text;\n}\n\nasync function fetchMndViaProxy(input, init, proxyConfig, proxyRequestFn) {\n  const maxResponseBytes = CROSS_STRAIT_SOURCE_CONTRACTS.taiwanMnd.maxResponseBytes;\n  const result = await proxyRequestFn(String(input), proxyConfig, {\n    headers: init.headers,\n    maxResponseBytes,\n    timeoutMs: REQUEST_TIMEOUT_MS,\n    signal: init.signal,\n  });\n  if (!Number.isInteger(result?.status) || result.status < 200 || result.status > 599) {\n    throw new Error('MND_PROXY_RESPONSE_INVALID');\n  }\n  if (result.status >= 300) {\n    return new Response(null, { status: result.status });\n  }\n  if (!Buffer.isBuffer(result.buffer)) throw new Error('MND_PROXY_RESPONSE_INVALID');\n  if (result.buffer.byteLength > maxResponseBytes) throw new Error('RESPONSE_TOO_LARGE');\n  return new Response(result.status === 204 || result.status === 205 ? null : result.buffer, {\n    status: result.status,\n  });\n}\n\nfunction shouldProxyJapanModFailure(error) {\n  const code = errorCode(error);\n  if (code === 'SOURCE_ERROR' || code === 'TIMEOUT' || code === 'JMOD_INDEX_EMPTY') return true;\n  const status = Number(/^HTTP_(\\d{3})$/u.exec(code)?.[1]);\n  return status === 403\n    || status === 408\n    || status === 425","sourceCodeStart":1710,"sourceCodeEnd":1746,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/cross-strait-activity/adapters.mjs#L1710-L1746","documentation":"fetchMndViaProxy routes Taiwan MND fetches through a configured proxy function (proxyRequestFn). After the proxy returns, the code validates the envelope: result.status must be an integer HTTP status in 200–599. A missing result, a non-integer status, or a status outside that range means the proxy returned a structurally invalid response, so MND_PROXY_RESPONSE_INVALID is thrown instead of building a Response object.","triggerScenarios":"proxyRequestFn resolves with null/undefined, an object without a status field, a status like 0, NaN, a string, or a value <200 or >599 (e.g. a custom proxy error code such as 0 or 999) when fetching the Taiwan MND source.","commonSituations":"Proxy service crashes or returns its own error JSON instead of {status, buffer}; wrong proxyRequestFn injected in tests or DI; proxy library version change altered its return shape (e.g. status moved to result.response.status); network layer returns a 0 status on socket failure.","solutions":["Log the raw proxyRequestFn result to see what shape was actually returned and compare it to the expected {status, buffer} envelope.","Fix the proxy implementation/config so it maps transport failures to a valid HTTP status (200–599) and returns {status, buffer}; a socket failure should surface as its own error, not status 0.","If the proxy library changed its return shape, adapt at the call site (e.g. read result.response.status) or pin/upgrade to a compatible version.","Verify the correct proxyRequestFn is wired for the MND source (not a stub or another source's fetcher)."],"exampleFix":"// before: proxy returns transport-level result with no HTTP status\nconst result = await proxyRequestFn(url, proxyConfig, opts); // { ok: false, err: 'ECONNRESET' }\n\n// after: normalize proxy failures into a valid status envelope before returning\nconst result = await proxyRequestFn(url, proxyConfig, opts);\nif (!result || typeof result.status !== 'number') {\n  throw new Error(`PROXY_TRANSPORT_FAILURE:${result?.err ?? 'unknown'}`); // or map to status 502\n}","handlingStrategy":"validation","validationCode":"// Validate the proxy envelope before handing it to fetchMndViaProxy\nfunction isValidProxyStatus(result) {\n  return Number.isInteger(result?.status) && result.status >= 200 && result.status <= 599;\n}","typeGuard":"function isMndProxyResult(result) {\n  return result != null\n    && Number.isInteger(result.status)\n    && result.status >= 200 && result.status <= 599\n    && (result.status >= 300 || Buffer.isBuffer(result.buffer));\n}","tryCatchPattern":"try {\n  const response = await fetchMndViaProxy(input, init, proxyConfig, proxyRequestFn);\n  return response;\n} catch (error) {\n  if (error?.message === 'MND_PROXY_RESPONSE_INVALID') {\n    logger.error({ proxyUrl: proxyConfig.url }, 'proxy returned invalid status envelope');\n    throw new SourceUnavailableError('MND proxy misbehaving');\n  }\n  throw error;\n}","preventionTips":["Pin and integration-test your proxyRequestFn so its return contract ({status: 200–599, buffer}) is asserted.","Have the proxy map transport failures to valid HTTP statuses (e.g. 502/504) instead of 0 or undefined.","Type-check the proxy result at the proxy boundary, not just at the consumer.","Add a canary test that exercises the real proxy path against a fixture server."],"tags":["proxy","response-validation","taiwan-mnd","network"],"backgroundTag":"unexpected-response-shape","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}