{"record":{"id":"a21ec58046b67e17","repo":"mastra-ai/mastra","slug":"label-contains-an-unsupported-field","errorCode":null,"errorMessage":"${label} contains an unsupported field.","messagePattern":"(.+?) contains an unsupported field\\.","errorType":"validation","errorClass":"FactoryRuleValidationError","httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/rules/validation.ts","lineNumber":67,"sourceCode":"export class FactoryRuleValidationError extends Error {\n  readonly code = 'invalid_factory_rule';\n\n  constructor(message: string) {\n    super(message);\n    this.name = 'FactoryRuleValidationError';\n  }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n  if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n  const prototype = Object.getPrototypeOf(value);\n  return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {\n  const allowed = new Set(keys);\n  if (Object.keys(value).some(key => !allowed.has(key))) {\n    throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);\n  }\n}\n\nfunction boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {\n  if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);\n  const normalized = value.trim();\n  if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {\n    throw new FactoryRuleValidationError(`${label} is invalid.`);\n  }\n  return normalized;\n}\n\nfunction optionalBoundedString(value: unknown, label: string, max: number): string | undefined {\n  if (value === undefined) return undefined;\n  return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/rules/validation.ts#L49-L85","documentation":"`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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nwork: {\n  building: { 'github-issue': { onEnter: handler, description: 'build stage' } },\n}\n// after\nwork: {\n  building: { 'github-issue': { onEnter: handler } },\n}","handlingStrategy":"validation","validationCode":"const ALLOWED_LEAF_KEYS = new Set(['onEnter', 'onExit']);\nconst unknownKeys = Object.keys(leaf).filter(k => !ALLOWED_LEAF_KEYS.has(k));\nif (unknownKeys.length) throw new Error(`Unsupported rule keys: ${unknownKeys.join(', ')}`);","typeGuard":"function hasOnlyKeys<T extends object>(value: T, keys: readonly (keyof T)[]): boolean {\n  return Object.keys(value).every(k => (keys as string[]).includes(k));\n}","tryCatchPattern":"try {\n  assertFactoryRules(rules);\n} catch (e) {\n  if (e instanceof FactoryRuleValidationError && e.code === 'invalid_factory_rule') {\n    // log the label from the message and fail fast with the offending node\n  } else throw e;\n}","preventionTips":["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."],"tags":["validation","schema","factory-rules","typo"],"backgroundTag":"schema-validation-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}