{"record":{"id":"20d8f146822c4e9b","repo":"ruvnet/ruflo","slug":"federation-canonicalization-rejects-non-canonical","errorCode":null,"errorMessage":"Federation canonicalization rejects non-canonical numbers","messagePattern":"Federation canonicalization rejects non-canonical numbers","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/plugin-agent-federation/src/application/inbound-dispatcher.ts","lineNumber":152,"sourceCode":" * values outside the I-JSON-safe subset instead of silently dropping them.\n * The `signature` field itself is excluded because it is what we verify.\n *\n * Federation messages are wrapped as `AgentMessage{id, type, payload,\n * metadata}` on the wire. The `payload` is the actual FederationEnvelope\n * (per `plugin.ts sendToNode`); we canonicalize the payload + the\n * metadata so the receiver verifies the same bytes the sender signed.\n */\nfunction canonicalizeJcsValue(value: unknown, ancestors: Set<object>): string {\n  if (value === null) return 'null';\n\n  switch (typeof value) {\n    case 'boolean':\n      return value ? 'true' : 'false';\n    case 'string':\n      return JSON.stringify(value);\n    case 'number':\n      if (!Number.isFinite(value) || Object.is(value, -0)) {\n        throw new TypeError('Federation canonicalization rejects non-canonical numbers');\n      }\n      if (Number.isInteger(value) && !Number.isSafeInteger(value)) {\n        throw new TypeError('Federation canonicalization rejects unsafe integers');\n      }\n      return JSON.stringify(value);\n    case 'bigint':\n    case 'function':\n    case 'symbol':\n    case 'undefined':\n      throw new TypeError(`Federation canonicalization rejects ${typeof value}`);\n    case 'object':\n      break;\n    default:\n      throw new TypeError(`Federation canonicalization rejects ${typeof value}`);\n  }\n\n  const object = value as object;\n  if (ancestors.has(object)) {","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/plugin-agent-federation/src/application/inbound-dispatcher.ts#L134-L170","documentation":"During JCS (RFC 8785-style) canonicalization of a signed federation envelope, numbers must be exactly representable: NaN, ±Infinity, and negative zero (-0) are rejected because their JSON serialization is undefined or ambiguous, and sender/receiver would verify different bytes. The check uses Number.isFinite plus Object.is(value, -0).","triggerScenarios":"Envelope metadata carrying NaN or Infinity from a failed computation (0/0, overflow); a -0 produced by Math.round(-0.4), a sign flip, or parsing '-0'; division or averaging code writing raw results into signed metadata.","commonSituations":"Metrics/latency fields (elapsed = end - start where both are 0) copied into message metadata; normalization math that can yield -0; user-supplied numbers passed through unvalidated.","solutions":["Sanitize every numeric before it enters a signed envelope: Number.isFinite(v) && !Object.is(v, -0)","Normalize -0 to 0 (v + 0 or `v === 0 ? 0 : v`)","Convey NaN/Infinity as explicit strings or null if they must be transmitted","Add a pre-sign assertion that walks the payload and rejects non-canonical numbers"],"exampleFix":"// before\nenvelope.metadata.ratio = numerator / denominator; // 0/0 -> NaN\n// after\nconst raw = numerator / denominator;\nenvelope.metadata.ratio = Number.isFinite(raw) && !Object.is(raw, -0) ? raw : null;","handlingStrategy":"type-guard","validationCode":"// sanitize numbers before they enter a signed envelope\nfunction sanitizeNumber(v: number): number | null {\n  return Number.isFinite(v) && !Object.is(v, -0) ? v : null;\n}","typeGuard":"function isCanonicalNumber(v: unknown): v is number {\n  return (\n    typeof v === 'number' &&\n    Number.isFinite(v) &&\n    !Object.is(v, -0)\n  );\n}","tryCatchPattern":null,"preventionTips":["Never put raw division/averaging results into signed metadata","Normalize -0 to 0 with `v + 0` when zero is possible","Convey NaN/Infinity as strings or null","Walk the payload with a pre-sign assertion rejecting non-canonical numbers"],"tags":["canonicalization","jcs","numbers","signing"],"backgroundTag":"serialization-unsupported-value","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"}