mastra-ai/mastra · error · FactoryRuleValidationError
${label} contains an unsupported field.
Error message
${label} contains an unsupported field. What it means
`assertExactKeys` enforces strict, closed object shapes on Factory rules and rule decisions (e.g. board leaf objects may only have `onEnter`/`onExit`, tools only `onResult`, decisions only their documented fields). Any additional/misspelled key causes a `FactoryRuleValidationError` with `code: 'invalid_factory_rule'`. The library deliberately rejects unknown fields so rule authors can't silently assume unsupported options.
Source
Thrown at mastracode/factory/src/rules/validation.ts:67
export class FactoryRuleValidationError extends Error {
readonly code = 'invalid_factory_rule';
constructor(message: string) {
super(message);
this.name = 'FactoryRuleValidationError';
}
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
const allowed = new Set(keys);
if (Object.keys(value).some(key => !allowed.has(key))) {
throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);
}
}
function boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {
if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);
const normalized = value.trim();
if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {
throw new FactoryRuleValidationError(`${label} is invalid.`);
}
return normalized;
}
function optionalBoundedString(value: unknown, label: string, max: number): string | undefined {
if (value === undefined) return undefined;
return boundedString(value, label, max);
}
function enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {View on GitHub (pinned to 75dd419e61)
Solutions
- Remove the unsupported key, or fix its spelling to one of the allowed keys for that node (rules: version/work/review/tools/github/linear; leaf: onEnter/onExit; tools: onResult; github/linear: onEvent).
- Check the FactoryRules/FactoryRuleDecision type definitions in rules/types.ts for the exact allowed keys for the node the label names.
- If the extra data is needed in metadata, move it into the `metadata` field of an `upsertLinkedWorkItem` decision (sanitized JSON) instead of a top-level key.
- If a genuinely new field is required, extend validation.ts and the corresponding type in types.ts rather than bypassing assertExactKeys.
Example fix
// before
work: {
building: { 'github-issue': { onEnter: handler, description: 'build stage' } },
}
// after
work: {
building: { 'github-issue': { onEnter: handler } },
} Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED_LEAF_KEYS = new Set(['onEnter', 'onExit']);
const unknownKeys = Object.keys(leaf).filter(k => !ALLOWED_LEAF_KEYS.has(k));
if (unknownKeys.length) throw new Error(`Unsupported rule keys: ${unknownKeys.join(', ')}`); Type guard
function hasOnlyKeys<T extends object>(value: T, keys: readonly (keyof T)[]): boolean {
return Object.keys(value).every(k => (keys as string[]).includes(k));
} Try / catch
try {
assertFactoryRules(rules);
} catch (e) {
if (e instanceof FactoryRuleValidationError && e.code === 'invalid_factory_rule') {
// log the label from the message and fail fast with the offending node
} else throw e;
} Prevention
- Type rules objects as FactoryRules / FactoryRuleDecision so TypeScript flags excess keys at compile time.
- Keep a runtime assertFactoryRules call in tests for every rules file in the repo.
- When a decision type changes between versions, diff its allowed key list before migrating rule code.
- Never add bespoke extra fields to rule nodes; extend the schema and validator together instead.
When it happens
Trigger: Calling `assertFactoryRules` with rules containing a typo'd or extra key (e.g. `onEnter` vs `onEnters`, an extra `description` field in a board leaf), or `validateFactoryRuleDecision` receiving a decision object with a key not in that decision type's allowed list (e.g. extra `note` on a `transition` decision, or `priority` on a `notify` decision is fine but `stage` is not).
Common situations: Typo in a rule handler key (onResult/onEvent/onEnter/onExit); copying an example with fields from a different factory version; adding a new option before the library supports it; AI-generated rule code inventing plausible-looking fields; leftover fields after renaming a decision type.
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
- Factory rule version is required.
- Rule metadata is too large.
- ${label} must be an object.
- ${label}.${stage} must be an object.
- ${label}.${stage}.${source} must be an object.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a21ec58046b67e17.
Report an issue: GitHub.