affaan-m/ECC · error · Error

Canonical session snapshot requires aggregates to be an…

Error message

Canonical session snapshot requires aggregates to be an object

What it means

validateCanonicalSnapshot requires snapshot.aggregates to be a plain object holding roll-up counts derived from workers. This error fires when aggregates is missing, null, or a non-object, meaning the snapshot was built without its summary section. Aggregates drive dashboards so the validator treats them as mandatory.

Solutions

  1. Add an aggregates object with workerCount (integer equal to workers.length), states {}, and healths {} to the snapshot.
  2. Compute aggregates from the workers array before persisting (counts by state and health).
  3. Regenerate the snapshot via the current adapters, which build aggregates automatically.
  4. Validate snapshots right after construction to catch missing aggregates.

Example fix

// before
const snapshot = { workers };
persistCanonicalSnapshot(snapshot);
// after
const snapshot = {
  workers,
  aggregates: {
    workerCount: workers.length,
    states: {},
    healths: {}
  }
};
persistCanonicalSnapshot(snapshot);
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

function hasAggregates(s) { return !!s && typeof s.aggregates === 'object' && s.aggregates !== null && !Array.isArray(s.aggregates); }

Try / catch

try { persistCanonicalSnapshot(snapshot); } catch (err) { if (String(err.message).includes('aggregates to be an object')) { console.error('Snapshot missing aggregates:', err.message); /* rebuild aggregates from workers */ } else { throw err; } }

Prevention

When it happens

Trigger: Passing a snapshot without the aggregates block to persistCanonicalSnapshot or a normalize* adapter; constructing a snapshot manually with only workers.

Common situations: Older snapshot format without aggregates loaded into current code; a transform that spread workers but dropped aggregates; partial JSON written by a crashed writer.

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/1b4bfb1928b36ff0. Report an issue: GitHub.

Appendix: source

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

    ensureStringAllowEmpty(worker.intent.objective, `workers[${index}].intent.objective`);
    ensureArrayOfStrings(worker.intent.seedPaths, `workers[${index}].intent.seedPaths`);

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

    ensureArrayOfStrings(worker.outputs.summary, `workers[${index}].outputs.summary`);
    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}`);

View on GitHub (pinned to 8321021c54)