{"record":{"id":"8cc417ce1076f0c9","repo":"koala73/worldmonitor","slug":"eccc-invalid-id","errorCode":"ECCC_INVALID_ID","errorMessage":"ECCC_INVALID_ID","messagePattern":"ECCC_INVALID_ID","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/_weather-alert-select.mjs","lineNumber":784,"sourceCode":"        pages += 1;\n        const data = await fetchApprovedWeatherJson(url.toString(), {\n          allowedHosts: [ECCC_HOST], maxBytes, fetchFn, userAgent, byteBudget,\n        });\n        const page = requireAlertFeatures(data);\n        if (data.type !== 'FeatureCollection'\n          || !Number.isSafeInteger(data.numberMatched) || data.numberMatched < 0\n          || !Number.isSafeInteger(data.numberReturned) || data.numberReturned !== page.length\n          || page.length > ECCC_PAGE_SIZE) {\n          throw new Error('ECCC_MALFORMED_PAGE');\n        }\n        if (matched !== undefined && data.numberMatched !== matched) throw new Error('ECCC_COUNT_DRIFT');\n        matched = data.numberMatched;\n        if (statusFeatures.length + page.length > matched\n          || (page.length === 0 && statusFeatures.length < matched)) {\n          throw new Error('ECCC_PAGE_PROGRESS');\n        }\n        for (const feature of page) {\n          if (typeof feature?.id !== 'string' || !feature.id.trim()) throw new Error('ECCC_INVALID_ID');\n          if (seenIds.has(feature.id)) throw new Error('ECCC_DUPLICATE_ID');\n          seenIds.add(feature.id);\n        }\n        statusFeatures.push(...page);\n      } while (statusFeatures.length < matched);\n      features.push(...statusFeatures);\n    } catch (err) {\n      failures.push(err);\n      failedStatuses.push(status);\n    }\n  }\n  if (failures.length === ECCC_LIVE_STATUSES.length) {\n    const detail = failures.map((err) => err?.message || String(err)).join('; ');\n    throw new Error(`ECCC issued and continued fetches both failed: ${detail}`);\n  }\n  // Returns an OBJECT, not a bare array, so a partial fetch cannot be consumed\n  // as if it were the whole set. `issued` and `continued` are separate collections\n  // and each carries alerts the other does not: `continued` is where an ONGOING","sourceCodeStart":766,"sourceCodeEnd":802,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/_weather-alert-select.mjs#L766-L802","documentation":"ECCC_INVALID_ID is thrown while paginating ECCC weather-alert GeoJSON: every feature in a fetched page must carry a non-empty string `id`. The script throws immediately when a feature's id is missing, not a string, or blank after trim, because downstream dedup and status matching key off feature ids. It is a strict data-integrity guard against malformed upstream payloads.","triggerScenarios":"A page from the ECCC alerts API (issued or continued status fetch) contains a feature where `feature.id` is undefined/null, a non-string (e.g. number), or a whitespace-only string; iteration happens inside the do/while loop collecting statusFeatures until numberMatched is reached.","commonSituations":"ECCC changes or drops the `id` property in a GeoJSON response schema; a proxy or cache returns a partially transformed payload; a test stub fixture omits ids; an unexpected HTML/error page parsed leniently yields feature-like objects without ids.","solutions":["Inspect the offending page payload and confirm each feature has a non-empty string `id`; if ECCC changed its schema, update the parsing/validation to the new id field","Check which status (issued vs continued) and page index failed by logging the URL and page before the throw, then replay that request to confirm it is reproducible upstream rather than a transient payload","Add a pre-loop validation or per-feature fallback (e.g. derive id from properties) only if ECCC legitimately emits id-less features, and document the deviation","If caused by a stub/fixture in tests, fix the fixture so every feature has a string id"],"exampleFix":"// before\nfor (const feature of page) {\n  if (typeof feature?.id !== 'string' || !feature.id.trim()) throw new Error('ECCC_INVALID_ID');\n  ...\n}\n// after\nfor (const feature of page) {\n  if (typeof feature?.id !== 'string' || !feature.id.trim()) {\n    console.error('ECCC feature missing id', JSON.stringify(feature).slice(0, 200));\n    throw new Error('ECCC_INVALID_ID');\n  }\n  ...\n}","handlingStrategy":"validation","validationCode":"function hasValidIds(features) {\n  return Array.isArray(features) && features.every((f) => typeof f?.id === 'string' && f.id.trim().length > 0);\n}\nif (!hasValidIds(page)) throw new Error('ECCC_INVALID_ID');","typeGuard":"const isFeatureWithId = (f) => typeof f?.id === 'string' && f.id.trim().length > 0;","tryCatchPattern":"try {\n  await fetchEcccPages();\n} catch (err) {\n  if (err.message === 'ECCC_INVALID_ID') {\n    console.error('Upstream ECCC payload contained a feature without a valid id; skipping cycle');\n  }\n}","preventionTips":["Validate fixtures and stubs against the same id contract as live payloads","Log the raw feature before throwing so schema regressions are diagnosable","Watch ECCC API changelogs for GeoJSON schema changes","Fail the whole page, not just the feature, to avoid partial ingests"],"tags":["validation","data-integrity","weather-api","pagination"],"backgroundTag":"empty-required-field","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"}