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
- 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.
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
- 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.
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
- Rule metadata must contain finite numbers.
- Rule metadata must not contain cycles.
- Rule metadata contains too many entries.
- Rule metadata must use plain objects.
- Rule metadata contains too many fields.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/668d512cc8b0dfb1.
Report an issue: GitHub.