{"record":{"id":"e20dd931f804a74d","repo":"ruvnet/ruflo","slug":"non-canonical-number-at-path","errorCode":null,"errorMessage":"non-canonical number at ${path}","messagePattern":"non-canonical number at (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/flywheel-receipt.ts","lineNumber":151,"sourceCode":"  /** Task-level paired outcomes behind heldOutDeltas (same order). */\n  pairedOutcomes?: PairedTaskOutcome[];\n  frozenAnchorRegression: number;\n  gates: Record<string, boolean>;\n  resourceEvidence?: Partial<ResourceEvidence>;\n  evidence?: EvaluationEvidence;\n  termVerification?: TermVerification[];\n  now?: number;\n  ttlMs?: number;\n  privateKeyPem?: string;\n  publicKeyPem?: string;\n  bootstrapIterations?: number;\n}\n\nfunction assertJsonValue(value: unknown, path = '$'): void {\n  if (value === null || typeof value === 'string' || typeof value === 'boolean') return;\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value) || Object.is(value, -0)) {\n      throw new Error(`non-canonical number at ${path}`);\n    }\n    return;\n  }\n  if (Array.isArray(value)) {\n    value.forEach((v, i) => {\n      if (v === undefined) throw new Error(`undefined array member at ${path}[${i}]`);\n      assertJsonValue(v, `${path}[${i}]`);\n    });\n    return;\n  }\n  if (typeof value === 'object') {\n    for (const [key, child] of Object.entries(value as Record<string, unknown>)) {\n      if (child === undefined) throw new Error(`undefined property at ${path}.${key}`);\n      assertJsonValue(child, `${path}.${key}`);\n    }\n    return;\n  }\n  throw new Error(`unsupported JSON value at ${path}`);","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/flywheel-receipt.ts#L133-L169","documentation":"assertJsonValue enforces canonical JSON (RFC 8785) for receipt payloads: numbers must be finite and must not be negative zero (-0). NaN and Infinity are not representable in JSON, and -0 serializes distinctly from +0 across implementations, so both are rejected to keep signed receipts byte-deterministic.","triggerScenarios":"Calling the receipt builder/signer with a payload (or nested field) that contains a non-finite number (NaN, Infinity, -Infinity) or a negative-zero value (Object.is(x, -0)), typically from a metric computation that divided by zero, returned NaN, or was produced via multiplication by -1 of zero.","commonSituations":"Latency/cost metrics computed as x/0 yielding Infinity; an undefined propagated into arithmetic producing NaN; normalization code that does value * -1 on a zero producing -0; stats aggregated over an empty set returning NaN.","solutions":["Sanitize numbers before building the payload: coerce NaN/Infinity to null or 0, and normalize -0 to 0.","Compute metrics defensively: guard divide-by-zero and empty-averages.","Validate the payload with assertJsonValue yourself before signing so the error surfaces at the source.","Type the payload builder inputs as numbers-known-finite via a branded type."],"exampleFix":"// before\nconst payload = { mrr: ratio, cost: maybeInfinity };\nsignReceipt(payload); // throws 'non-canonical number' on NaN/Infinity/-0\n\n// after — canonicalize numbers first\nfunction canon(n: number): number | null {\n  if (!Number.isFinite(n)) return null;\n  return Object.is(n, -0) ? 0 : n;\n}\nconst payload = { mrr: canon(ratio), cost: canon(maybeInfinity) };\nsignReceipt(payload);","handlingStrategy":"validation","validationCode":"function canonNumber(n: number): number | null {\n  if (!Number.isFinite(n)) return null;\n  return Object.is(n, -0) ? 0 : n;\n}\nconst payload = { mrr: canonNumber(ratio), cost: canonNumber(maybeInfinity) };\nsignReceipt(payload);","typeGuard":"function isCanonicalNumber(n: unknown): n is number {\n  return typeof n === 'number' && Number.isFinite(n) && !Object.is(n, -0);\n}\nfunction hasNonCanonicalNumbers(v: unknown, path = '$'): boolean {\n  if (typeof v === 'number') return !isCanonicalNumber(v);\n  if (Array.isArray(v)) return v.some((x, i) => hasNonCanonicalNumbers(x, `${path}[${i}]`));\n  if (v && typeof v === 'object') return Object.values(v).some((x) => hasNonCanonicalNumbers(x));\n  return false;\n}","tryCatchPattern":"try {\n  signReceipt(payload);\n} catch (e) {\n  if ((e as Error).message.startsWith('non-canonical number')) {\n    // sanitize all numbers and retry\n    payload = sanitizeNumbers(payload);\n    signReceipt(payload);\n  } else throw e;\n}","preventionTips":["Guard divide-by-zero and empty-set averages that yield NaN/Infinity.","Normalize -0 to 0 (Object.is(x, -0)) before building payloads.","Run hasNonCanonicalNumbers over the payload before signing to fail fast at the source.","Brand metric values as finite-numbers via a type to prevent undefined leaking into arithmetic."],"tags":["json","canonical","receipts","validation","typescript"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}