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

  1. 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).
  2. Check the FactoryRules/FactoryRuleDecision type definitions in rules/types.ts for the exact allowed keys for the node the label names.
  3. 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.
  4. 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

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


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