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
- Trace where the NaN/Infinity value originates and fix the computation producing it
- Validate policy numbers are finite before encoding
- 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
- Guard division denominators before assigning policy values
- Avoid JSON round-trips of Infinity/NaN
- Assert finiteness of computed metrics before building policy objects
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)