ruvnet/ruflo · error

policy value must be finite

Error message

policy value must be finite

What it means

encodePolicyFractions normalizes policy objects so every fractional value becomes a scale-12 decimal string. Because NaN and Infinity have no decimal representation and would break deterministic receipt signing, the function throws immediately when it encounters a non-finite number.

Solutions

  1. Trace where the NaN/Infinity value originates and fix the computation producing it
  2. Validate policy numbers are finite before encoding
  3. Replace placeholder non-finite values with defined numbers or omit the field

Example fix

// before
const weight = total / count; // count === 0 -> Infinity
// after
const weight = count > 0 ? total / count : 0;
Defensive patterns

Strategy: validation

Validate before calling

const assertFinitePolicy = (obj) => JSON.stringify(obj, (k, v) => { if (typeof v === 'number' && !Number.isFinite(v)) throw new Error(`non-finite value at key ${k}`); return v; });

Type guard

const isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);

Try / catch

try { encodePolicyFractions(policy); } catch (e) { if (e.message === 'policy value must be finite') { /* locate NaN/Infinity in policy, fix computation */ } throw e; }

Prevention

When it happens

Trigger: Passing an object containing NaN, Infinity, or -Infinity to encodePolicyFractions, directly or through receipt-signing helpers such as candidatePolicy/policyCandidateId.

Common situations: Policy values computed from division by zero or uninitialized variables; JSON.parse of a document serialized with Infinity (non-standard); test fixtures using placeholder NaN values.

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 ruvnet/ruflo@2602b642d9 (2026-09-15). Data as JSON: /api/errors/3e1e4f025a0fd65c. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/services/flywheel-receipt.ts:347

 * Encode an opaque policy object so every fractional value is a scale-12
 * decimal string, per ADR-322C rule 2 and conformance-checklist A3.
 *
 * `candidatePolicy` is declared "Opaque to this contract; its shape is owned by
 * policySchemaVersion. Must still satisfy the ADR-322C number rules" — a rule
 * the JSON Schema cannot express for an opaque object, so nothing enforced it
 * and the producer wrote binary floats (`{"alpha": 0.3, "mmrLambda": 0.5}`).
 * Ruflo's own verifier accepted them because `assertJsonValue` only checked
 * finite-and-not-negative-zero; autogenous's stricter verifier correctly
 * rejected the receipt (ruvnet/ruflo#3229, ruvnet/autogenous#15).
 *
 * Integers stay JSON numbers and only non-integers are encoded, which is what
 * the contract's own example shows: `{"hnswEf": 128, "hybridWeight": "0.65"}`.
 * Recurses through nested objects and arrays, because "every fractional value"
 * is not limited to the top level.
 */
export function encodePolicyFractions(value: unknown): unknown {
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) throw new Error('policy value must be finite');
    // Integers are exact in JSON and the contract permits scaled integers, so
    // they are left alone; -0 is normalized away by `decimal`.
    return Number.isInteger(value) && !Object.is(value, -0) ? value : decimal(value);
  }
  if (Array.isArray(value)) return value.map(encodePolicyFractions);
  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>).map(([k, v]) => [k, encodePolicyFractions(v)]),
    );
  }
  return value;
}

function seedFrom(parts: string[]): { seed: number; hex: string } {
  const digest = createHash('sha256').update(parts.join('')).digest();
  return { seed: digest.readUInt32BE(0), hex: digest.toString('hex') };
}

View on GitHub (pinned to 2602b642d9)