{"record":{"id":"cb37ea45f56a7201","repo":"ruvnet/ruflo","slug":"canonical-json-supports-only-arrays-and-plain-obje","errorCode":null,"errorMessage":"canonical JSON supports only arrays and plain objects","messagePattern":"canonical JSON supports only arrays and plain objects","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/repository-state.ts","lineNumber":137,"sourceCode":"        !Number.isFinite(item)\n        || Object.is(item, -0)\n        || (Number.isInteger(item) && !Number.isSafeInteger(item))\n      ) {\n        throw new Error('canonical JSON requires finite, safe, non-negative-zero numbers');\n      }\n      return JSON.stringify(item);\n    }\n    if (item === undefined) throw new Error('canonical JSON does not support undefined');\n    if (typeof item !== 'object') {\n      throw new Error(`canonical JSON does not support ${typeof item}`);\n    }\n    if (ancestors.has(item)) throw new Error('canonical JSON does not support cycles');\n    ancestors.add(item);\n    try {\n      if (Array.isArray(item)) return `[${item.map(encode).join(',')}]`;\n      const prototype = Object.getPrototypeOf(item);\n      if (prototype !== Object.prototype && prototype !== null) {\n        throw new Error('canonical JSON supports only arrays and plain objects');\n      }\n      const entries = Object.entries(item as Record<string, unknown>)\n        .sort(([left], [right]) => codeUnitCompare(left, right));\n      return `{${entries.map(([key, child]) => {\n        assertUnicodeScalarString(key);\n        return `${JSON.stringify(key)}:${encode(child)}`;\n      }).join(',')}}`;\n    } finally {\n      ancestors.delete(item);\n    }\n  };\n  return encode(value);\n}\n\nfunction gitBuffer(repoRoot: string, args: readonly string[]): Buffer {\n  return execFileSync('git', ['-C', repoRoot, ...args], {\n    encoding: 'buffer',\n    stdio: ['ignore', 'pipe', 'pipe'],","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/repository-state.ts#L119-L155","documentation":"Only arrays and objects whose prototype is Object.prototype or null can be canonicalized; any other object — Date, Map, Set, RegExp, Buffer, or a class instance — throws. instanceof Date would encode as '{}' via JSON.stringify, and subclass prototypes vary across runtimes, so non-plain objects are rejected to keep digests byte-stable.","triggerScenarios":"Evidence payloads embedding new Date(...) fields, Map/Set metadata, Buffers from file reads, or domain-class instances passed directly from business logic; objects created with Object.create(null) are the only exotic form allowed.","commonSituations":"Reusing domain models as receipts/evidence; adding createdAt: new Date() instead of an ISO string; node Buffers placed into manifests; ORM row objects that carry constructor prototypes.","solutions":["Map to plain data before recording: Date to toISOString(), Map to [...map.entries()], Buffer to base64/hex strings","Keep evidence payloads typed as plain JSON structures (type Payload = JsonValue) so class instances fail at compile time","Add a development-mode assertion that walks the payload and verifies every object's prototype"],"exampleFix":"// before\ncanonicalJson({ createdAt: new Date(), meta: new Map([['k', 1]]) });\n\n// after\ncanonicalJson({ createdAt: new Date().toISOString(), meta: [['k', 1]] });","handlingStrategy":"type-guard","validationCode":"function toPlainJson(value: unknown): unknown {\n  if (value instanceof Date) return value.toISOString();\n  if (value instanceof Map) return [...value.entries()].map(([k, v]) => [k, toPlainJson(v)]);\n  if (value instanceof Set) return [...value].map(toPlainJson);\n  if (Array.isArray(value)) return value.map(toPlainJson);\n  if (typeof value === 'object' && value !== null) {\n    if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {\n      return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toPlainJson(v)]));\n    }\n    return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toPlainJson(v)]));\n  }\n  return value;\n}","typeGuard":"function isPlainJsonValue(value: unknown): value is CanonicalJsonValue {\n  if (value === null || ['boolean', 'number', 'string'].includes(typeof value)) return true;\n  if (Array.isArray(value)) return value.every(isPlainJsonValue);\n  if (typeof value === 'object') {\n    const proto = Object.getPrototypeOf(value);\n    return (proto === Object.prototype || proto === null) && Object.values(value).every(isPlainJsonValue);\n  }\n  return false;\n}","tryCatchPattern":"try {\n  return canonicalJson(payload);\n} catch (error) {\n  if (error instanceof Error && error.message === 'canonical JSON supports only arrays and plain objects') {\n    return canonicalJson(toPlainJson(payload));\n  }\n  throw error;\n}","preventionTips":["Convert Date to ISO string and Map/Set to arrays at the point evidence is assembled","Never pass ORM entities or class instances; map to plain records first","Keep a PlainJson<T> type alias for all hashed payloads"],"tags":["canonical-json","plain-object","serialization","prototype"],"backgroundTag":"non-plain-object-serialization","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}