Issue #3591663: Fail validation for props that do not exist on a component

Fixes #3591663.

Root cause

AiResponseValidator::convertToComponentTreeData() builds the client model with the component's own propSources as source. JsonSchemaPropsComponentSourceBase::clientModelToInput() iterates only $client_model['source'] keys, so an AI-supplied prop that does not exist on the component is never visited and is silently dropped before ComponentTreeItemList::validate() runs. The existing garbage-input check in validateComponentInput() only sees post-conversion inputs, so it never fires for these props.

Changes

  • AiResponseValidator collects unknown props during conversion (diff of AI prop keys against propSources) and merges them into the thrown ConstraintViolationException, reusing the wording of the existing garbage-input check: Component `X`: the `Y` prop is not defined.
  • The check applies to sources exposing propSources (SDC and code components). Block components keep their settings-form flow.
  • A scalar props value (for example a quoted YAML mapping) is now rejected instead of silently dropping every prop.
  • Five regression cases added to SetAIGeneratedComponentStructureTest: bogus prop next to valid props, any prop on a zero-prop component, a bogus prop nested in a slot, combined missing-required plus bogus prop, and scalar props.
  • SetAIGeneratedTemplateDataTest sent the non-existent title prop to the card component (which defines heading); it only passed because of this bug. Fixed to heading.

Testing

  • SetAIGeneratedComponentStructureTest (13 tests) and SetAIGeneratedTemplateDataTest (5 tests) pass.
  • Manual: run the issue's reproduce steps; the tool now fails with components.0.[sdc.mercury.hero-billboard].props.heading_test: Component `sdc.mercury.hero-billboard`: the `heading_test` prop is not defined.
  • composer run lint passes.

Known follow-ups (out of scope here)

  • Block-source components: unknown settings keys are still silently stripped by BlockComponent::clientModelToInput(); nothing downstream rejects them.
  • Props added to an auto-saved (unpublished) code-component draft are not in the saved entity's propSources, so replaying them through the AI now fails with a misleading message. A complete fix would layer auto-save data before diffing.
  • Pre-existing: CanvasAiPageBuilderHelper::processSdc() filters Attribute-typed props with a strict string comparison on the raw definition, while propSources uses core's array-aware check. An SDC declaring the array form (type: [Drupal\Core\Template\Attribute]) is advertised in the AI catalog but absent from propSources, so the AI can be led into sending a prop this validator rejects.

AI usage disclosure

Per the Drupal.org AI policy: an AI assistant helped trace the root cause, draft the change and tests, and run the verification. A human reviewed the diff, the reasoning, and the test results before pushing. Kernel tests were executed locally on Drupal 11.3 (18 passing across the two affected test files); PHPCS and PHPStan level 8 show no new issues against the originals.

Merge request reports

Loading