{"record":{"id":"71e094abeb0cb091","repo":"ruvnet/ruflo","slug":"jcs-canonicalization-rejects-non-finite-numbers","errorCode":null,"errorMessage":"JCS canonicalization rejects non-finite numbers","messagePattern":"JCS canonicalization rejects non-finite numbers","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/security/src/policy/product-plane.ts","lineNumber":1098,"sourceCode":"  }\n}\n\n/**\n * RFC 8785/JCS-compatible canonical JSON for this I-JSON profile.\n *\n * ECMAScript's JSON number serialization supplies the JCS number rendering.\n * Non-finite numbers, sparse arrays, undefined values, non-plain objects, and\n * invalid Unicode are rejected instead of being silently coerced.\n */\nexport function canonicalizeProductPlane(value: unknown): string {\n  if (value === null) return 'null';\n  if (typeof value === 'boolean') return value ? 'true' : 'false';\n  if (typeof value === 'string') {\n    assertUnicodeScalarString(value);\n    return JSON.stringify(value);\n  }\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value)) throw new TypeError('JCS canonicalization rejects non-finite numbers');\n    return JSON.stringify(value);\n  }\n  if (Array.isArray(value)) {\n    const items: string[] = [];\n    for (let index = 0; index < value.length; index++) {\n      if (!Object.prototype.hasOwnProperty.call(value, index)) {\n        throw new TypeError('JCS canonicalization rejects sparse arrays');\n      }\n      items.push(canonicalizeProductPlane(value[index]));\n    }\n    return `[${items.join(',')}]`;\n  }\n  if (!isRecord(value)) {\n    throw new TypeError('JCS canonicalization accepts only JSON-compatible plain objects');\n  }\n  const entries: string[] = [];\n  for (const key of Object.keys(value).sort()) {\n    assertUnicodeScalarString(key);","sourceCodeStart":1080,"sourceCodeEnd":1116,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/policy/product-plane.ts#L1080-L1116","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Sanitize the tree before canonicalization: walk it and throw or replace non-finite numbers with null/a finite default at the source.","Fix the computation producing NaN/Infinity (guard divisions, default missing inputs to 0 or omit the field).","If the field is optional, drop the key so canonicalization never sees the value."],"exampleFix":"// before\nconst payload = { usage: { ratio: tokens / seconds } }; // seconds=0 -> Infinity\ncanonicalizeProductPlane(payload); // throws\n\n// after\nconst ratio = seconds > 0 ? tokens / seconds : null;\ncanonicalizeProductPlane({ usage: { ratio } });","handlingStrategy":"validation","validationCode":"function assertFiniteNumbers(value: unknown, at = '$'): void {\n  if (typeof value === 'number' && !Number.isFinite(value)) {\n    throw new TypeError(`non-finite number at ${at}: ${value}`);\n  }\n  if (Array.isArray(value)) return value.forEach((v, i) => assertFiniteNumbers(v, `${at}[${i}]`));\n  if (value && typeof value === 'object') {\n    for (const [k, v] of Object.entries(value)) assertFiniteNumbers(v, `${at}.${k}`);\n  }\n}\nassertFiniteNumbers(payload);\nconst canonical = canonicalizeProductPlane(payload);","typeGuard":"function isFiniteNumber(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v);\n}","tryCatchPattern":"try {\n  return canonicalizeProductPlane(payload);\n} catch (err) {\n  if (err instanceof TypeError && /non-finite/.test(err.message)) {\n    throw new PayloadError('payload contains NaN/Infinity — fix the metric producer before signing');\n  }\n  throw err;\n}","preventionTips":["Guard all divisions (denominator > 0) and default missing metrics to 0 or omit the field.","Walk payloads with a finite-number assertion before hashing/signing so failures name the field path.","Never accept non-finite numbers from JSON.parse revivers or external producers into hashable payloads."],"tags":["serialization","jcs","numbers","validation"],"backgroundTag":"non-finite-number-serialization","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}