{"record":{"id":"50e376018d9f4264","repo":"affaan-m/ECC","slug":"canonical-session-snapshot-requires-fieldpath-t-50e376","errorCode":null,"errorMessage":"Canonical session snapshot requires ${fieldPath} to be a non-negative integer","messagePattern":"Canonical session snapshot requires (.+?) to be a non-negative integer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/session-adapters/canonical-session.js","lineNumber":65,"sourceCode":"    throw new Error(`Canonical session snapshot requires ${fieldPath} to be a string or null`);\n  }\n}\n\nfunction ensureBoolean(value, fieldPath) {\n  if (typeof value !== 'boolean') {\n    throw new Error(`Canonical session snapshot requires ${fieldPath} to be a boolean`);\n  }\n}\n\nfunction ensureArrayOfStrings(value, fieldPath) {\n  if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {\n    throw new Error(`Canonical session snapshot requires ${fieldPath} to be an array of strings`);\n  }\n}\n\nfunction ensureInteger(value, fieldPath) {\n  if (!Number.isInteger(value) || value < 0) {\n    throw new Error(`Canonical session snapshot requires ${fieldPath} to be a non-negative integer`);\n  }\n}\n\nconst STALE_THRESHOLD_MS = 5 * 60 * 1000;\n\nfunction parseUpdatedMs(updated) {\n  if (typeof updated !== 'string' || updated.length === 0) return null;\n  const ms = Date.parse(updated);\n  return Number.isNaN(ms) ? null : ms;\n}\n\nfunction deriveWorkerHealth(rawWorker) {\n  const state = (rawWorker.status && rawWorker.status.state) || 'unknown';\n  const completedStates = ['completed', 'succeeded', 'success', 'done'];\n  const failedStates = ['failed', 'error'];\n\n  if (failedStates.includes(state)) return 'degraded';\n  if (completedStates.includes(state)) return 'healthy';","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/session-adapters/canonical-session.js#L47-L83","documentation":"ensureInteger requires the field to be a non-negative integer (Number.isInteger(value) && value >= 0). This error means a numeric count/timestamp field was a float, NaN, negative, or a non-number. Canonical snapshots store counters strictly so comparisons like stale-threshold math (STALE_THRESHOLD_MS) behave predictably.","triggerScenarios":"A snapshot field validated with ensureInteger holds -1, 1.5, NaN, undefined, or a numeric string, when passed through persistCanonicalSnapshot or a normalize* adapter.","commonSituations":"Computing timestamps with Date arithmetic that yields NaN (invalid date parse); sentinel values like -1 for 'unknown'; JSON numbers parsed from strings; floats from division when computing counts.","solutions":["Find the field via ${fieldPath} and ensure it is Math.trunc'd and >= 0","Guard timestamp math: check Number.isFinite before assigning (e.g. Number.isFinite(d.getTime()) ? d.getTime() : 0)","Replace -1/NaN sentinels with a defined non-negative default","Fix the producer adapter to clamp/round the value"],"exampleFix":"// before\nsnapshot.session.lastUpdatedMs = new Date(raw).getTime(); // NaN if raw is invalid\n// after\nconst t = new Date(raw).getTime();\nsnapshot.session.lastUpdatedMs = Number.isFinite(t) && t >= 0 ? Math.trunc(t) : 0;","handlingStrategy":"validation","validationCode":"const okInt = (v) => Number.isInteger(v) && v >= 0;","typeGuard":"const isNonNegativeInteger = (v) => Number.isInteger(v) && v >= 0;","tryCatchPattern":"try { persistCanonicalSnapshot(s); } catch (e) { if (/non-negative integer/.test(e.message)) { /* clamp */ } else throw e; }","preventionTips":["Guard timestamp math with Number.isFinite","Avoid negative sentinels"],"tags":["validation","schema","integer","range"],"backgroundTag":"value-out-of-range","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}