affaan-m/ECC · error · CapsuleError

capsule.payload_denied

capsule.payload_denied

Error message

payload keys not allowlisted: ${dropped.join(', ')}

What it means

redactPayload() drops any payload keys not on the allowlist for the given lineage/kind, returning them in the dropped array. By default (strict mode) append() rejects the call with 'capsule.payload_denied' listing the disallowed keys instead of silently discarding data. Callers may explicitly opt out with options.strict === false to accept key dropping.

Solutions

  1. Read the dropped list in the message and either remove those keys or rename them to allowlisted ones.
  2. Set options.strict = false only if silently dropping the extra keys is acceptable.
  3. If the field is genuinely needed, extend the payload allowlist in envelope.redactPayload for that lineage/kind.
  4. Project payloads down to the documented minimal schema per entry kind.

Example fix

// before
await capsule.append('fix', 'note', { msg: 'ok', traceId: 'abc' }); // capsule.payload_denied

// after: only allowlisted keys
await capsule.append('fix', 'note', { msg: 'ok traceId=abc' });
// or opt into dropping:
await capsule.append('fix', 'note', { msg: 'ok', traceId: 'abc' }, { strict: false });
Defensive patterns

Strategy: validation

Validate before calling

const { dropped } = envelope.redactPayload(payload, {});
if (dropped.length > 0) console.warn(`keys will be rejected/dropped: ${dropped.join(', ')}`);

Type guard

function hasOnlyAllowlistedKeys(payload, allow) {
  return Object.keys(payload).every(k => allow.includes(k));
}

Try / catch

try {
  await capsule.append(lineage, kind, payload);
} catch (e) {
  if (e instanceof CapsuleError && e.code === 'capsule.payload_denied') {
    const extra = e.detail?.dropped ?? [];
    const trimmed = Object.fromEntries(Object.entries(payload).filter(([k]) => !extra.includes(k)));
    return capsule.append(lineage, kind, trimmed);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling append() with payload keys that are not allowlisted for that entry type — e.g. adding custom fields like { foo: 1 } to a lineage whose envelope only permits { msg } or defined fields, or renaming a field without updating the allowlist.

Common situations: Adding extra debugging fields 'just this once'; a refactor renaming a payload field (e.g. error -> message) so the old key is no longer allowlisted; passing the whole request/config object as payload.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

   * A partial I/O failure is preserved for diagnosis, never silently rolled back.
   */
  append(lineage, kind, payload = {}, options = {}) {
    return withAppendLock(this.dir, () => {
      const state = readCapsule(this.dir);
      if (!state.ok) throw new CapsuleError(state.code, state.reason, { failed_at: state.failed_at });
      if (!envelope.LINEAGES.includes(lineage)) {
        throw new CapsuleError('capsule.bad_lineage', `unknown lineage ${lineage}`);
      }
      const effectClass = options.effect_class || 'SE0';
      const { payload: clean, dropped, findings, errors: payloadErrors } = envelope.redactPayload(payload, options);
      if (payloadErrors.length > 0) {
        throw new CapsuleError('capsule.payload_invalid', payloadErrors.join('; '));
      }
      if (findings.length > 0) {
        throw new CapsuleError('capsule.secret_canary', `payload tripped secret canary ${findings[0].canary} at ${findings[0].path}`, { findings });
      }
      if (dropped.length > 0 && options.strict !== false) {
        throw new CapsuleError('capsule.payload_denied', `payload keys not allowlisted: ${dropped.join(', ')}`, { dropped });
      }
      const body = {
        schema: envelope.SCHEMA_VERSION,
        run_id: state.meta.run_id,
        capsule_id: state.meta.capsule_id,
        seq: state.entries.length,
        ts: nowIso(this.clock),
        lineage,
        kind,
        effect_class: effectClass,
        harness_version: state.meta.harness_version,
        task_family: state.meta.task_family,
        parent_hash: state.root_hash,
        payload: clean,
      };
      const entry = { ...body, entry_hash: envelope.computeEntryHash(body) };
      const errors = envelope.validateEnvelope(entry);
      if (errors.length > 0) throw new CapsuleError('capsule.invalid_entry', errors.join('; '));

View on GitHub (pinned to 8321021c54)