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
- Preallocate with Array.from({ length: n }, () => null) or .fill(null) so every slot exists
- Replace delete arr[i] with splice(i, 1) or assignment of null
- Densify before signing: map holes to null (e.g. arr.map((v, i) => i in arr ? v : null))
- 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
- Preallocate with Array.from({ length: n }, () => null), not new Array(n)
- Use splice instead of delete on array elements
- Assert density on arrays built from indexed writes before signing
- Prefer push/concat builders that never skip indices
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
- Federation canonicalization rejects non-canonical numbers
- Federation canonicalization rejects unsafe integers
- Federation canonicalization rejects ${typeof value}
- Federation canonicalization rejects cyclic values
- Federation canonicalization accepts only plain objects
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/5c16fcb6943c643c.
Report an issue: GitHub.