{"record":{"id":"668d512cc8b0dfb1","repo":"mastra-ai/mastra","slug":"rule-metadata-is-not-bounded-json","errorCode":null,"errorMessage":"Rule metadata is not bounded JSON.","messagePattern":"Rule metadata is not bounded JSON\\.","errorType":"validation","errorClass":"FactoryRuleValidationError","httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/rules/validation.ts","lineNumber":103,"sourceCode":"function enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n  if (typeof value !== 'string' || !allowed.includes(value as T)) {\n    throw new FactoryRuleValidationError(`${label} is invalid.`);\n  }\n  return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n  value: unknown,\n  depth = 0,\n  seen = new Set<object>(),\n): FactoryRuleJsonValue {\n  if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n    return value;\n  }\n  if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n    throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n  }\n  if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n  seen.add(value as object);\n  try {\n    if (Array.isArray(value)) {\n      if (value.length > MAX_JSON_COLLECTION_SIZE) {\n        throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n      }\n      return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n    }\n    if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n    const entries = Object.entries(value);\n    if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n      throw new FactoryRuleValidationError('Rule metadata contains too many fields.');\n    }\n    const sanitized: Record<string, FactoryRuleJsonValue> = {};\n    for (const [key, entry] of entries) {\n      const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/rules/validation.ts#L85-L121","documentation":"Rule metadata is depth- and shape-bounded: nesting deeper than MAX_JSON_DEPTH (8) or containing a non-JSON value (function, symbol, bigint, class instance that is not a plain object/array) at that depth check throws this error. The library enforces bounded JSON so persisted metadata cannot blow up storage or serialization.","triggerScenarios":"Metadata with more than 8 levels of nested objects/arrays; passing values like new Date() (not rejected here but functions/symbols/bigints hit the non-object branch), Map/Set instances passed where plain objects are expected at excessive depth, or a deeply nested config object copied wholesale into metadata.","commonSituations":"Dumping an entire API response or parsed config (deeply nested) into metadata; passing class instances or functions instead of plain data; recursive builders that nest dynamically beyond 8 levels.","solutions":["Flatten the metadata structure to fewer than 8 levels of nesting.","Convert class instances, Maps, Sets, and Dates to plain objects/strings/ISO dates before assigning to metadata.","Pick only the small set of fields you actually need instead of spreading a whole object into metadata.","Pre-validate with a depth counter and fail fast in your own rule code."],"exampleFix":"// before\nmetadata: { a: { b: { c: { /* ...8+ levels of nested report */ } } } }\n// after\nmetadata: { reportSummary: JSON.stringify(report) }","handlingStrategy":"validation","validationCode":"function jsonDepth(v: unknown, d = 0): number {\n  if (!v || typeof v !== 'object') return d;\n  return 1 + Math.max(0, ...Object.values(v).map(x => jsonDepth(x, d)));\n}\nif (jsonDepth(metadata) > 8) throw new RangeError('metadata nesting exceeds 8 levels');","typeGuard":"const isPlainJsonValue = (v: unknown): boolean =>\n  v === null || ['string', 'boolean', 'number'].includes(typeof v) ||\n  (Array.isArray(v) ? v.every(isPlainJsonValue) : v?.constructor === Object && Object.values(v).every(isPlainJsonValue));","tryCatchPattern":"try {\n  emit({ type: 'upsertLinkedWorkItem', metadata });\n} catch (e) {\n  if (e instanceof FactoryRuleValidationError && e.message.includes('not bounded JSON')) {\n    emit({ type: 'upsertLinkedWorkItem', metadata: { summary: JSON.stringify(flatten(metadata)).slice(0, 1000) } });\n  } else throw e;\n}","preventionTips":["Design metadata as flat key-value summaries, not mirrored report structures.","Convert Dates to ISO strings, Maps to objects, Sets to arrays before building metadata.","Cap nesting intentionally (aim for 2–3 levels) rather than testing the 8-level limit.","Never spread functions, class instances, or bigints into metadata."],"tags":["validation","json","depth-limit"],"backgroundTag":"schema-validation-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}