mastra-ai/mastra · error · FactoryRuleValidationError

Rule metadata is not bounded JSON.

Error message

Rule metadata is not bounded JSON.

What it means

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.

Source

Thrown at mastracode/factory/src/rules/validation.ts:103

function enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {
  if (typeof value !== 'string' || !allowed.includes(value as T)) {
    throw new FactoryRuleValidationError(`${label} is invalid.`);
  }
  return value as T;
}

export function normalizeFactoryRuleJsonValue(
  value: unknown,
  depth = 0,
  seen = new Set<object>(),
): FactoryRuleJsonValue {
  if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');
    return value;
  }
  if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {
    throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');
  }
  if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');
  seen.add(value as object);
  try {
    if (Array.isArray(value)) {
      if (value.length > MAX_JSON_COLLECTION_SIZE) {
        throw new FactoryRuleValidationError('Rule metadata contains too many entries.');
      }
      return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));
    }
    if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');
    const entries = Object.entries(value);
    if (entries.length > MAX_JSON_COLLECTION_SIZE) {
      throw new FactoryRuleValidationError('Rule metadata contains too many fields.');
    }
    const sanitized: Record<string, FactoryRuleJsonValue> = {};
    for (const [key, entry] of entries) {
      const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Flatten the metadata structure to fewer than 8 levels of nesting.
  2. Convert class instances, Maps, Sets, and Dates to plain objects/strings/ISO dates before assigning to metadata.
  3. Pick only the small set of fields you actually need instead of spreading a whole object into metadata.
  4. Pre-validate with a depth counter and fail fast in your own rule code.

Example fix

// before
metadata: { a: { b: { c: { /* ...8+ levels of nested report */ } } } }
// after
metadata: { reportSummary: JSON.stringify(report) }
Defensive patterns

Strategy: validation

Validate before calling

function jsonDepth(v: unknown, d = 0): number {
  if (!v || typeof v !== 'object') return d;
  return 1 + Math.max(0, ...Object.values(v).map(x => jsonDepth(x, d)));
}
if (jsonDepth(metadata) > 8) throw new RangeError('metadata nesting exceeds 8 levels');

Type guard

const isPlainJsonValue = (v: unknown): boolean =>
  v === null || ['string', 'boolean', 'number'].includes(typeof v) ||
  (Array.isArray(v) ? v.every(isPlainJsonValue) : v?.constructor === Object && Object.values(v).every(isPlainJsonValue));

Try / catch

try {
  emit({ type: 'upsertLinkedWorkItem', metadata });
} catch (e) {
  if (e instanceof FactoryRuleValidationError && e.message.includes('not bounded JSON')) {
    emit({ type: 'upsertLinkedWorkItem', metadata: { summary: JSON.stringify(flatten(metadata)).slice(0, 1000) } });
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/668d512cc8b0dfb1. Report an issue: GitHub.