ruvnet/ruflo · error · TypeError

JCS canonicalization rejects non-finite numbers

Error message

JCS canonicalization rejects non-finite numbers

What it means

canonicalizeProductPlane() implements RFC 8785 (JCS) canonical JSON for the product plane; JCS has no representation for NaN, Infinity, or -Infinity (JSON.stringify would emit null and break cross-implementation canonical form), so any non-finite number in the payload throws TypeError immediately.

Source

Thrown at v3/@claude-flow/security/src/policy/product-plane.ts:1098

  }
}

/**
 * RFC 8785/JCS-compatible canonical JSON for this I-JSON profile.
 *
 * ECMAScript's JSON number serialization supplies the JCS number rendering.
 * Non-finite numbers, sparse arrays, undefined values, non-plain objects, and
 * invalid Unicode are rejected instead of being silently coerced.
 */
export function canonicalizeProductPlane(value: unknown): string {
  if (value === null) return 'null';
  if (typeof value === 'boolean') return value ? 'true' : 'false';
  if (typeof value === 'string') {
    assertUnicodeScalarString(value);
    return JSON.stringify(value);
  }
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) throw new TypeError('JCS canonicalization rejects non-finite numbers');
    return JSON.stringify(value);
  }
  if (Array.isArray(value)) {
    const items: string[] = [];
    for (let index = 0; index < value.length; index++) {
      if (!Object.prototype.hasOwnProperty.call(value, index)) {
        throw new TypeError('JCS canonicalization rejects sparse arrays');
      }
      items.push(canonicalizeProductPlane(value[index]));
    }
    return `[${items.join(',')}]`;
  }
  if (!isRecord(value)) {
    throw new TypeError('JCS canonicalization accepts only JSON-compatible plain objects');
  }
  const entries: string[] = [];
  for (const key of Object.keys(value).sort()) {
    assertUnicodeScalarString(key);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Sanitize the tree before canonicalization: walk it and throw or replace non-finite numbers with null/a finite default at the source.
  2. Fix the computation producing NaN/Infinity (guard divisions, default missing inputs to 0 or omit the field).
  3. If the field is optional, drop the key so canonicalization never sees the value.

Example fix

// before
const payload = { usage: { ratio: tokens / seconds } }; // seconds=0 -> Infinity
canonicalizeProductPlane(payload); // throws

// after
const ratio = seconds > 0 ? tokens / seconds : null;
canonicalizeProductPlane({ usage: { ratio } });
Defensive patterns

Strategy: validation

Validate before calling

function assertFiniteNumbers(value: unknown, at = '$'): void {
  if (typeof value === 'number' && !Number.isFinite(value)) {
    throw new TypeError(`non-finite number at ${at}: ${value}`);
  }
  if (Array.isArray(value)) return value.forEach((v, i) => assertFiniteNumbers(v, `${at}[${i}]`));
  if (value && typeof value === 'object') {
    for (const [k, v] of Object.entries(value)) assertFiniteNumbers(v, `${at}.${k}`);
  }
}
assertFiniteNumbers(payload);
const canonical = canonicalizeProductPlane(payload);

Type guard

function isFiniteNumber(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v);
}

Try / catch

try {
  return canonicalizeProductPlane(payload);
} catch (err) {
  if (err instanceof TypeError && /non-finite/.test(err.message)) {
    throw new PayloadError('payload contains NaN/Infinity — fix the metric producer before signing');
  }
  throw err;
}

Prevention

When it happens

Trigger: canonicalizeProductPlane({ score: NaN }) after aggregating undefined metrics; { ratio: x / 0 } yielding Infinity; payloads built from JSON.parse of documents containing 'Infinity' literals or from float math on missing data.

Common situations: Analytics/telemetry aggregation where a divide-by-zero or missing input degenerates to Infinity/NaN; passing JS computation results straight into a signed/hashable payload; legacy producers that emit non-finite numbers and relied on tolerant JSON elsewhere.

Related errors


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