{"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":"exception","errorClass":"Error","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/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/session-adapters/canonical-session.js#L47-L83","documentation":"Thrown by the ensureInteger helper when a field must be a non-negative integer but is not. Applied to aggregates.workerCount, and each value in aggregates.states and aggregates.healths. Used to validate the precomputed rollups match the workers array.","triggerScenarios":"aggregates.workerCount set to a float, a negative number, a string like '3', or undefined; a state count value that is a string; aggregates built without Number.isInteger checks.","commonSituations":"An adapter building aggregates manually and leaving workerCount unset; a count coerced to string during serialization elsewhere; negative counts from a buggy reducer; floats from averaging.","solutions":["Build aggregates via buildAggregates(workers) rather than hand-constructing them, so counts are correct integers.","Ensure any custom count is Number.isInteger and >= 0 before assigning.","Re-run buildAggregates after mutating the workers array."],"exampleFix":"// before\naggregates: { workerCount: workers.length.toFixed(0), states: {}, healths: {} } // string\n\n// after\naggregates: buildAggregates(workers) // { workerCount: number, states, healths }","handlingStrategy":"validation","validationCode":"// Prefer buildAggregates so counts are valid non-negative integers.\nconst { buildAggregates } = require('./canonical-session');\naggregates: buildAggregates(workers)","typeGuard":"function isNonNegInt(v) { return Number.isInteger(v) && v >= 0; }","tryCatchPattern":null,"preventionTips":["Never hand-build aggregates; use buildAggregates after finalizing workers.","Re-run buildAggregates whenever the workers array changes."],"tags":["validation","session-schema","canonical-session"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}