affaan-m/ECC · error · Error

Canonical session snapshot requires aggregates.states to be…

Error message

Canonical session snapshot requires aggregates.states to be an object

What it means

validateCanonicalSnapshot requires snapshot.aggregates.states to be a plain object mapping state names to integer counts. This error fires when aggregates.states is missing, null, or a non-object. The states histogram lets consumers render per-state worker counts without rescanning the workers array.

Solutions

  1. Add aggregates.states as a plain object of state -> integer counts derived from the workers array.
  2. Convert any Map/arr form to a plain object (Object.fromEntries) before persisting.
  3. Rebuild the whole aggregates block from workers in one helper so states is never omitted.
  4. Validate snapshots post-construction.

Example fix

// before
aggregates.states = new Map([['idle', 2]]);
// after
aggregates.states = Object.fromEntries(
  Object.entries(workers.reduce((acc, w) => { acc[w.state] = (acc[w.state] || 0) + 1; return acc; }, {}))
);
Defensive patterns

Strategy: validation

Validate before calling

if (!snapshot.aggregates?.states || typeof snapshot.aggregates.states !== 'object' || Array.isArray(snapshot.aggregates.states)) throw new Error('aggregates.states must be a plain object');

Type guard

function hasStatesHistogram(a) { return !!a && typeof a.states === 'object' && a.states !== null && !Array.isArray(a.states); }

Try / catch

try { persistCanonicalSnapshot(snapshot); } catch (err) { if (String(err.message).includes('aggregates.states to be an object')) { console.error('Missing states histogram:', err.message); /* derive states from workers and retry */ } else { throw err; } }

Prevention

When it happens

Trigger: Persisting a snapshot whose aggregates block includes workerCount but omits states; assigning states as a Map or array instead of a plain object (e.g. after JSON round-trip of a Map).

Common situations: Hand-built aggregates that only set workerCount; JSON.stringify of a Map producing {} silently; older snapshot versions with a different aggregates shape.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/93df044b91e0c325. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/session-adapters/canonical-session.js:245

    ensureArrayOfStrings(worker.outputs.validation, `workers[${index}].outputs.validation`);
    ensureArrayOfStrings(worker.outputs.remainingRisks, `workers[${index}].outputs.remainingRisks`);

    if (!isObject(worker.artifacts)) {
      throw new Error(`Canonical session snapshot requires workers[${index}].artifacts to be an object`);
    }
  });

  if (!isObject(snapshot.aggregates)) {
    throw new Error('Canonical session snapshot requires aggregates to be an object');
  }

  ensureInteger(snapshot.aggregates.workerCount, 'aggregates.workerCount');
  if (snapshot.aggregates.workerCount !== snapshot.workers.length) {
    throw new Error('Canonical session snapshot requires aggregates.workerCount to match workers.length');
  }

  if (!isObject(snapshot.aggregates.states)) {
    throw new Error('Canonical session snapshot requires aggregates.states to be an object');
  }

  if (!isObject(snapshot.aggregates.healths)) {
    throw new Error('Canonical session snapshot requires aggregates.healths to be an object');
  }

  for (const [state, count] of Object.entries(snapshot.aggregates.states)) {
    ensureString(state, 'aggregates.states key');
    ensureInteger(count, `aggregates.states.${state}`);
  }

  for (const [health, count] of Object.entries(snapshot.aggregates.healths)) {
    ensureString(health, 'aggregates.healths key');
    ensureInteger(count, `aggregates.healths.${health}`);
  }

  return snapshot;
}

View on GitHub (pinned to 8321021c54)