{"record":{"id":"127901adc055f748","repo":"thedotmack/claude-mem","slug":"canonical-content-message-canonicalcontent","errorCode":null,"errorMessage":"canonical content: ${message}","messagePattern":"canonical content: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/sync/CanonicalContent.ts","lineNumber":67,"sourceCode":"const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;\nconst ENVELOPE_KEYS = [\n  'body_schema_version',\n  'deleted',\n  'deleted_at',\n  'entity_rev',\n  'id',\n  'kind',\n  'mutation',\n  'origin_device_id',\n  'origin_local_id',\n  'payload',\n  'payload_schema_version',\n  'payload_sha256',\n] as const;\n\nfunction jsonError(message: string): never {\n  logger.debug('CLOUD_SYNC', 'Rejected invalid canonical content', { reason: message });\n  throw new Error(`canonical content: ${message}`);\n}\n\n/** RFC-8259 JSON with recursively sorted object keys and preserved array order. */\nexport function canonicalJson(value: unknown): string {\n  return JSON.stringify(normalizeJson(value, new Set()));\n}\n\nfunction normalizeJson(value: unknown, seen: Set<object>): unknown {\n  if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value)) jsonError('numbers must be finite');\n    if (!Number.isSafeInteger(value) && Number.isInteger(value)) {\n      jsonError('integers must be safe; use decimal strings for uint64 values');\n    }\n    if (Object.is(value, -0)) return 0;\n    return value;\n  }\n  if (typeof value !== 'object') jsonError(`unsupported JSON value ${typeof value}`);","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/CanonicalContent.ts#L49-L85","documentation":"jsonError is the single rejection helper in CanonicalContent.ts: every canonical-content validator (normalizeJson, assertCanonicalDecimal, stableDocumentId, operation builders) throws `canonical content: <reason>` when input does not satisfy the canonical RFC-8259 / deterministic-serialization rules used for cloud sync payloads. It logs the reason at debug level before throwing.","triggerScenarios":"Calling canonicalJson with non-JSON values (undefined, functions, cyclic structures, NaN/Infinity), normalizeJson with duplicate or non-canonical structures it cannot canonicalize, assertCanonicalDecimal with a non-decimal value, or buildContentOperation/buildMutationOperation with payloads failing schema checks (missing payload_schema_version / payload_sha256 shape).","commonSituations":"Syncing documents produced by hand-edited JSON, values like NaN or 1e999 that serialize to null, cyclic objects from runtime state, or cloud-sync responses/records written by a newer client with a different payload schema.","solutions":["Read the trailing message after 'canonical content: ' — it names the exact rule violated.","Pre-sanitize values: replace NaN/Infinity with strings or nulls, break cycles, and only pass JSON-safe data.","Ensure decimals are passed as strings (canonical decimal form) rather than JS numbers.","Confirm payload_schema_version and payload_sha256 are present and current in documents sent to sync."],"exampleFix":"// before\ncanonicalJson({ ts: Date.parse('not-a-date') }); // NaN -> throws\n// after\nconst ts = Number.isFinite(parsedTs) ? parsedTs : 0;\ncanonicalJson({ ts });","handlingStrategy":"validation","validationCode":"function isJsonSafe(v: unknown, seen = new Set()): boolean {\n  if (v === null || typeof v === 'boolean' || typeof v === 'string') return true;\n  if (typeof v === 'number') return Number.isFinite(v);\n  if (typeof v !== 'object' || seen.has(v)) return false;\n  seen.add(v);\n  return Object.values(v as object).every(x => isJsonSafe(x, seen));\n}\nif (!isJsonSafe(doc)) sanitizeBeforeSync(doc);","typeGuard":"function isCanonicalDecimal(v: unknown): v is string {\n  return typeof v === 'string' && /^-?\\d+(\\.\\d+)?$/.test(v);\n}","tryCatchPattern":"try {\n  const canonical = canonicalJson(doc);\n} catch (e) {\n  logger.warn('Skipping non-canonical document', { reason: e.message });\n  return null; // or fix/sanitize and retry\n}","preventionTips":["Pass decimals as strings and timestamps as finite integers into sync payloads.","Never feed runtime objects with cycles, functions, NaN, or Infinity into canonicalJson.","Keep payload_schema_version / payload_sha256 fields present and up to date."],"tags":["json","validation","sync"],"backgroundTag":"schema-validation-failed","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}