{"record":{"id":"03cab321455250a4","repo":"affaan-m/ECC","slug":"canonical-session-snapshot-requires-aggregates-hea","errorCode":null,"errorMessage":"Canonical session snapshot requires aggregates.healths to be an object","messagePattern":"Canonical session snapshot requires aggregates\\.healths to be an object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/session-adapters/canonical-session.js","lineNumber":249,"sourceCode":"      throw new Error(`Canonical session snapshot requires workers[${index}].artifacts to be an object`);\n    }\n  });\n\n  if (!isObject(snapshot.aggregates)) {\n    throw new Error('Canonical session snapshot requires aggregates to be an object');\n  }\n\n  ensureInteger(snapshot.aggregates.workerCount, 'aggregates.workerCount');\n  if (snapshot.aggregates.workerCount !== snapshot.workers.length) {\n    throw new Error('Canonical session snapshot requires aggregates.workerCount to match workers.length');\n  }\n\n  if (!isObject(snapshot.aggregates.states)) {\n    throw new Error('Canonical session snapshot requires aggregates.states to be an object');\n  }\n\n  if (!isObject(snapshot.aggregates.healths)) {\n    throw new Error('Canonical session snapshot requires aggregates.healths to be an object');\n  }\n\n  for (const [state, count] of Object.entries(snapshot.aggregates.states)) {\n    ensureString(state, 'aggregates.states key');\n    ensureInteger(count, `aggregates.states.${state}`);\n  }\n\n  for (const [health, count] of Object.entries(snapshot.aggregates.healths)) {\n    ensureString(health, 'aggregates.healths key');\n    ensureInteger(count, `aggregates.healths.${health}`);\n  }\n\n  return snapshot;\n}\n\nfunction resolveRecordingDir(options = {}) {\n  if (typeof options.recordingDir === 'string' && options.recordingDir.length > 0) {\n    return path.resolve(options.recordingDir);","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/session-adapters/canonical-session.js#L231-L267","documentation":"validateCanonicalSnapshot requires snapshot.aggregates.healths to be a plain object mapping health values to integer counts. This error fires when aggregates.healths is missing, null, or not a plain object. Like states, the healths histogram is a mandatory roll-up of worker.health values.","triggerScenarios":"Persisting a snapshot whose aggregates omits healths; assigning a Map, null, or array; manually copying workerCount/states but forgetting healths.","commonSituations":"Partial aggregates written by custom scripts; schema drift from older snapshot versions; lossy JSON transforms dropping healths.","solutions":["Add aggregates.healths as a plain object of health -> integer counts computed from workers.","Rebuild the entire aggregates object from workers in one place so healths is always present.","Convert Maps to plain objects with Object.fromEntries before persisting.","Validate snapshots after construction and in tests."],"exampleFix":"// before\nconst aggregates = { workerCount: workers.length, states: {} };\n// after\nconst healths = workers.reduce((acc, w) => { acc[w.health] = (acc[w.health] || 0) + 1; return acc; }, {});\nconst aggregates = { workerCount: workers.length, states: {}, healths };","handlingStrategy":"validation","validationCode":"if (!snapshot.aggregates?.healths || typeof snapshot.aggregates.healths !== 'object' || Array.isArray(snapshot.aggregates.healths)) throw new Error('aggregates.healths must be a plain object');","typeGuard":"function hasHealthsHistogram(a) { return !!a && typeof a.healths === 'object' && a.healths !== null && !Array.isArray(a.healths); }","tryCatchPattern":"try { persistCanonicalSnapshot(snapshot); } catch (err) { if (String(err.message).includes('aggregates.healths to be an object')) { console.error('Missing healths histogram:', err.message); /* derive healths from workers and retry */ } else { throw err; } }","preventionTips":["Derive healths in the same helper that derives states so neither is omitted.","Use plain objects, never Maps or arrays, for histogram fields.","Regenerate old-format snapshots via current adapters.","Assert full aggregate shape in unit tests."],"tags":["schema-validation","session-snapshot"],"backgroundTag":"schema-validation-failed","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"}