affaan-m/ECC · error · Error

Canonical session snapshot requires aggregates to be an obje

Error message

Canonical session snapshot requires aggregates to be an object

What it means

snapshot.aggregates is missing or not a plain object. Aggregates must hold workerCount (non-negative integer), states (object of state->count), and healths (object of health->count). The built-in normalizers always build aggregates via buildAggregates(workers); this fires mainly when a snapshot is hand-built or loaded from a tampered/stale recording without aggregates.

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 01e15490f0)

Solutions

  1. Use buildAggregates(workers) exported from canonical-session to compute the object in one call: aggregates: buildAggregates(workers).
  2. If building by hand, set { workerCount: workers.length, states: {...}, healths: {...} }.
  3. When loading a recording missing aggregates, recompute them from workers before re-validating.
  4. Always pass the full snapshot through one of the normalize*Session builders; they call buildAggregates for you.

Example fix

// before
const { buildAggregates } = require('./canonical-session');
const snapshot = { schemaVersion:'ecc.session.v1', adapterId:'custom', session:{...}, workers }; // no aggregates

// after
const snapshot = {
  schemaVersion:'ecc.session.v1', adapterId:'custom', session:{...}, workers,
  aggregates: buildAggregates(workers)
};
Defensive patterns

Strategy: validation

Validate before calling

function ensureAggregates(snapshot) {
  if (!snapshot.aggregates || typeof snapshot.aggregates !== 'object') {
    snapshot.aggregates = buildAggregates(snapshot.workers || []);
  }
  return snapshot;
}

Type guard

function hasValidAggregates(s) {
  const a = s.aggregates;
  return a !== null && typeof a === 'object' && !Array.isArray(a)
    && Number.isInteger(a.workerCount) && a.workerCount === (s.workers || []).length
    && a.states !== null && typeof a.states === 'object'
    && a.healths !== null && typeof a.healths === 'object';
}

Try / catch

try { validateCanonicalSnapshot(snapshot); }
catch (err) {
  if (err.message.includes('aggregates to be an object')) {
    snapshot.aggregates = buildAggregates(snapshot.workers || []);
    validateCanonicalSnapshot(snapshot);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling validateCanonicalSnapshot with a snapshot assembled manually that omits aggregates. Loading a recording written by an older schema that did not persist aggregates. Setting aggregates:null because workerCount is unknown.

Common situations: User constructs a snapshot from raw session data and forgets to compute aggregates. Test fixture that builds only workers. Adapter migration that drops aggregates.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/1b4bfb1928b36ff0. Report an issue: GitHub.