affaan-m/ECC · error · CapsuleError

failure.reason

Error message

failure.reason

What it means

During Capsule.create, the newly built metadata object is validated via metadataFailure(meta); if validation fails, the returned failure's code and reason (message 'failure.reason' — the actual text comes from metadataFailure) are rethrown as a CapsuleError before any directory or files are written. This guards invariants such as invalid identifiers, missing schema/harness/task_family values, or malformed timestamps in the options you supplied.

Solutions

  1. Read the failure.code / error message to see which metadata field failed validation
  2. Validate your run_id/capsule_id (identifier format), harness_version, task_family, and clock output before calling create
  3. Omit options entirely to use the library's generated defaults (newId('run'), newId('capsule'), 'unknown', 'unspecified')
  4. Wrap create in try-catch on the returned CapsuleError code and fix the offending option

Example fix

// before
Capsule.create(dir, { capsule_id: userInput });
// after
const capsuleId = /^[A-Za-z0-9_-]+$/.test(userInput ?? '') ? userInput : undefined;
const capsule = Capsule.create(dir, capsuleId ? { capsule_id: capsuleId } : {});
Defensive patterns

Strategy: validation

Validate before calling

function validateCapsuleOptions(o = {}) {
  const idRe = /^[A-Za-z0-9._-]+$/;
  for (const k of ['run_id', 'capsule_id', 'harness_version', 'task_family']) {
    if (o[k] !== undefined && (typeof o[k] !== 'string' || o[k] === '')) {
      throw new Error(`${k} must be a non-empty string`);
    }
    if (o[k] !== undefined && !idRe.test(o[k])) {
      throw new Error(`${k} contains invalid characters`);
    }
  }
}

Type guard

function isStringOption(v) {
  return v === undefined || (typeof v === 'string' && v.length > 0);
}

Try / catch

try {
  const capsule = Capsule.create(dir, options);
} catch (e) {
  if (e.code && e.code.startsWith('capsule.')) {
    console.error('capsule metadata rejected:', e.code, e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing Capsule.create(dir, { run_id, capsule_id, harness_version, task_family }) with values that violate metadata rules (e.g. empty, wrong format/charset, or of the wrong type such as non-string); a clock option producing an invalid timestamp; supplying null instead of omitting an option.

Common situations: Generating IDs with a custom scheme containing illegal characters; wiring user input straight into capsule_id/run_id; a custom clock whose nowIso output is malformed; refactors that pass undefined-checked but null-valued options.

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/13075be8eb59a1bb. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/eval-harness/capsule.js:152

    this.lastHash = envelope.GENESIS_HASH;
    this.nextSeq = 0;
  }

  static create(dir, options = {}) {
    const resolved = path.resolve(dir);
    if (fs.existsSync(path.join(resolved, META_FILE))) {
      throw new CapsuleError('capsule.exists', `capsule already exists at ${resolved}`);
    }
    const meta = {
      schema: envelope.SCHEMA_VERSION,
      run_id: options.run_id === undefined ? newId('run') : options.run_id,
      capsule_id: options.capsule_id === undefined ? newId('capsule') : options.capsule_id,
      harness_version: options.harness_version === undefined ? 'unknown' : options.harness_version,
      task_family: options.task_family === undefined ? 'unspecified' : options.task_family,
      created_at: nowIso(options.clock),
    };
    const failure = metadataFailure(meta);
    if (failure) throw new CapsuleError(failure.code, failure.reason);
    fs.mkdirSync(resolved, { recursive: true });
    fs.writeFileSync(path.join(resolved, META_FILE), canonicalJson(meta) + '\n', 'utf8');
    fs.writeFileSync(path.join(resolved, JOURNAL_FILE), '', 'utf8');
    return new Capsule(resolved, meta, options);
  }

  static open(dir, options = {}) {
    const resolved = path.resolve(dir);
    const state = readCapsule(resolved);
    if (!state.ok) {
      throw new CapsuleError(state.code, state.reason, { failed_at: state.failed_at });
    }
    const capsule = new Capsule(resolved, state.meta, options);
    if (state.entries.length > 0) {
      const last = state.entries[state.entries.length - 1];
      capsule.lastHash = last.entry_hash;
      capsule.nextSeq = last.seq + 1;
    }

View on GitHub (pinned to 8321021c54)