ruvnet/ruflo · error · TypeError

Federation canonicalization rejects sparse arrays

Error message

Federation canonicalization rejects sparse arrays

What it means

JCS canonicalization rejects arrays with holes: every index from 0 to length-1 is checked with hasOwnProperty, so [1,,3], new Array(5) with partial assignment, or arrays after delete all fail. Sparse arrays have no representation in JSON's array grammar, and hole handling differs across implementations, which would break byte-exact signature agreement.

Source

Thrown at v3/@claude-flow/plugin-agent-federation/src/application/inbound-dispatcher.ts:180

      throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
    case 'object':
      break;
    default:
      throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
  }

  const object = value as object;
  if (ancestors.has(object)) {
    throw new TypeError('Federation canonicalization rejects cyclic values');
  }
  ancestors.add(object);

  try {
    if (Array.isArray(value)) {
      const items: string[] = [];
      for (let index = 0; index < value.length; index += 1) {
        if (!Object.prototype.hasOwnProperty.call(value, index)) {
          throw new TypeError('Federation canonicalization rejects sparse arrays');
        }
        items.push(canonicalizeJcsValue(value[index], ancestors));
      }
      return `[${items.join(',')}]`;
    }

    const prototype = Object.getPrototypeOf(value);
    if (prototype !== Object.prototype && prototype !== null) {
      throw new TypeError('Federation canonicalization accepts only plain objects');
    }

    const record = value as Record<string, unknown>;
    const entries = Object.keys(record)
      .sort()
      .map((key) => `${JSON.stringify(key)}:${canonicalizeJcsValue(record[key], ancestors)}`);
    return `{${entries.join(',')}}`;
  } finally {
    ancestors.delete(object);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Preallocate with Array.from({ length: n }, () => null) or .fill(null) so every slot exists
  2. Replace delete arr[i] with splice(i, 1) or assignment of null
  3. Densify before signing: map holes to null (e.g. arr.map((v, i) => i in arr ? v : null))
  4. Add a pre-sign assertion that every index below length is an own property

Example fix

// before
const slots = new Array(count);
slots[0] = a; slots[2] = c; // hole at index 1
// after
const slots = Array.from({ length: count }, () => null);
slots[0] = a; slots[2] = c;
Defensive patterns

Strategy: type-guard

Validate before calling

// densify before signing: give every hole an explicit null
const dense = Array.from(arr, (v, i) => (i in arr ? v : null));

Type guard

function isDenseArray(a: unknown[]): boolean {
  return a.every((_, i) => Object.prototype.hasOwnProperty.call(a, i));
}

Prevention

When it happens

Trigger: Building arrays with new Array(n) then assigning only some slots; using delete arr[i]; arrays produced by constructs that skip indices; results collected with sparse index assignment.

Common situations: Slot reservation patterns (const slots = new Array(count)); removing elements with delete instead of splice; data assembled from indexed writes at non-contiguous positions.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/5c16fcb6943c643c. Report an issue: GitHub.