mastra-ai/mastra · error · FactoryRuleValidationError

Factory rules must be an object.

Error message

Factory rules must be an object.

What it means

assertFactoryRules is the entry-point validator for a FactoryRules object. Its first check is that the rules argument is a plain object; anything else (null, undefined, array, Map, class instance, string) is rejected before any field-level checks.

Source

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

  if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);
  for (const [stage, sources] of Object.entries(rules)) {
    enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);
    if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);
    for (const [source, leaf] of Object.entries(sources)) {
      enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);
      if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`${label}.${stage}.${source} must be an object.`);
      assertExactKeys(leaf, ['onEnter', 'onExit'], `${label}.${stage}.${source}`);
      for (const handler of Object.values(leaf)) {
        if (handler !== undefined && typeof handler !== 'function') {
          throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);
        }
      }
    }
  }
}

export function assertFactoryRules(rules: unknown): asserts rules is FactoryRules {
  if (!isPlainObject(rules)) throw new FactoryRuleValidationError('Factory rules must be an object.');
  assertExactKeys(rules, ['version', 'work', 'review', 'tools', 'github', 'linear'], 'Factory rules');
  boundedString(rules.version, 'Factory rule version', MAX_VERSION_LENGTH);
  validateBoardRules(rules.work, 'Factory rules.work');
  validateBoardRules(rules.review, 'Factory rules.review');

  if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError('Factory rules.tools must be an object.');
  for (const [toolName, leaf] of Object.entries(rules.tools)) {
    boundedString(toolName, 'Factory tool name', 128, IDENTIFIER_RE);
    if (!isPlainObject(leaf))
      throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);
    assertExactKeys(leaf, ['onResult'], `Factory rules.tools.${toolName}`);
    if (leaf.onResult !== undefined && typeof leaf.onResult !== 'function') {
      throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);
    }
  }

  if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError('Factory rules.github must be an object.');
  for (const [event, leaf] of Object.entries(rules.github)) {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure a complete rules object is passed, e.g. { version, work, review, tools, github, linear }.
  2. Guard config loading: const rules = loadedConfig?.rules ?? {}; before validating.
  3. If using a builder or class instance, convert to a plain object first (e.g. { ...builder.toJSON() }).

Example fix

// before
assertFactoryRules(loadRulesConfig()); // may return null
// after
const config = loadRulesConfig();
assertFactoryRules(config ?? { version: '1', work: {}, review: {}, tools: {}, github: {}, linear: {} });
Defensive patterns

Strategy: try-catch

Validate before calling

function hasCompleteRulesShape(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v) &&
    ['version', 'work', 'review', 'tools', 'github', 'linear'].every(k => k in v);
}
if (!hasCompleteRulesShape(config?.rules)) throw new Error('rules config missing or malformed');

Type guard

const isFactoryRulesLike = (v: unknown): v is Record<string, unknown> =>
  typeof v === 'object' && v !== null && !Array.isArray(v) &&
  (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);

Try / catch

try {
  assertFactoryRules(config.rules);
} catch (e) {
  if (e.name === 'FactoryRuleValidationError' && e.code === 'invalid_factory_rule') {
    console.error('Rules config is not a valid object; falling back to defaults.');
    return defaultFactoryRules();
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling assertFactoryRules / prepare / defaultFactoryRules with null or undefined rules (e.g. a failed config load), an array of rule fragments, or an object created with a non-Object prototype (Object.create(null) is allowed, class instances are not).

Common situations: Config file missing or empty so JSON.parse returns null; forgetting to pass the rules argument; passing a RulesBuilder/Map-like object instead of the plain result.

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/4b868a9d448a73d4. Report an issue: GitHub.