Deprecate `multiple` on input and output definitions in favor of list definitions
_Follows #3583016. Blocked by #3583048, #3583018 and #3583019._
### Problem/Motivation
`multiple` comes from core's `ContextDefinition`. #3583019 keeps it and declares `isMultiple()` on our own interfaces, and it is the last piece of the context system left in the definition API. It means "a list of the declared type." `ListInputDefinition` means the same thing, so there are two ways to declare a list. The two disagree about where constraints go.
Take one input declared both ways, with `Count min 2` on the list and `Length min 3` on each item:
```php
$multiple = new InputDefinition(
data_type: 'string',
label: 'Tags',
description: 'Tags.',
multiple: TRUE,
constraints: ['Count' => ['min' => 2], 'Length' => ['min' => 3]],
);
$list = new ListInputDefinition(
label: 'Tags',
description: 'Tags.',
constraints: ['Count' => ['min' => 2]],
item_definition: new InputDefinition(
data_type: 'string',
label: 'Tag',
description: 'A tag.',
constraints: ['Length' => ['min' => 3]],
),
);
```
Both advertise the same JSON Schema: `minItems: 2` on the array and `minLength: 3` on `items`. Validation gets both wrong, in different ways. These are the results on the #3583019 branch:
| Value | `multiple: TRUE` | `ListInputDefinition` |
| --- | --- | --- |
| `['abcd']` | Fails for each item: `Expected argument of type "array\|\Countable", "string" given` | Passes, even though `Count` asks for 2 |
| `['abcd', 'x']` | Fails for each item, with the same error | One violation on item 1 (correct) |
| `[]` | Passes | Passes, even though `Count` asks for 2 |
`Count` on a `multiple` input fails every value, because the item-level constraint copy (`@todo Fix upstream`, now in `EffectiveDataDefinition::createFrom()`) puts `Count` on each string. `Count` on a list input never runs. #3583004 moved the `Context::validate()` call into an `else` so that maps could skip it, and lists are the `if` in that same chain. So list-level constraints have not been validated since #3583004. #3583048 fixes that on its own.
#3583003 already states the rule: "`multiple: TRUE` is shorthand for 'a list of this'… To constrain the list itself, declare a `ListInputDefinition`." This issue follows that rule, but deprecates the shorthand instead of keeping it.
### Proposed resolution
Two steps, after #3583048 makes list-level constraints enforceable.
**1. Convert `multiple` to a list and deprecate it.** `addInputDefinition()` converts a `multiple: TRUE` definition to a `ListInputDefinition` and triggers a deprecation. It works the same way as the #3583015 output shim:
```php
public function addInputDefinition(string $name, InputDefinitionInterface $definition): static {
if ($definition->isMultiple() && !$definition instanceof ListDataDefinition) {
@trigger_error(sprintf("Declaring the '%s' input with multiple: TRUE is deprecated in tool:1.0.0-beta11 and is removed from tool:1.0.0-rc1. Declare a ListInputDefinition instead. See https://git.drupalcode.org/project/tool/-/work_items/3583049", $name), E_USER_DEPRECATED);
$definition = ListInputDefinition::fromMultiple($definition);
}
$this->inputDefinitions[$name] = $definition;
return $this;
}
```
`ListInputDefinition::fromMultiple()` moves `Count` to the list and leaves every other constraint on a required item. The normalizer already sends `Count` to the array and everything else to `items`, so the advertised schema does not change. #3583048 makes that `Count` enforceable. Nothing depends on today's behavior, because `Count` on a `multiple` input rejects every value.
The same step:
* Converts outputs in `OutputDefinition::fromContextDefinition()`: a core `ContextDefinition` with `multiple: TRUE` becomes a `ListOutputDefinition`. The method already passes `isMultiple()` into the List and Map constructors. The eight canvas_tools outputs are the only users, and they already go through this shim.
* Drops the `$multiple` parameter from `ListInputDefinition` and `ListOutputDefinition`. A list that is also multiple is an undeclared list of lists.
* Moves `comprehensive_types_tool.labels` and the matrix `bounded_maps` fixtures to explicit `ListInputDefinition`s, as #3583003 proposed.
The acceptance check is unchanged matrix snapshots, apart from what #3583041 already changes. #3583041 has landed, so compare against current 1.0.x.
**2. Remove `multiple`** before rc1, in the same release as the #3583015 output shim removal:
* `isMultiple()`/`setMultiple()` on `InputDefinitionInterface` and `OutputDefinitionInterface`, and the `$multiple` constructor parameter on every definition class.
* `fromMultiple()` and the conversion in `addInputDefinition()`.
* The `isMultiple()` half of each branch in the normalizer, `validateInputValue()`, `EffectiveDataDefinition::createFrom()` (including the `@todo Fix upstream` copy), the entity handle transformer and subscriber, `RecursiveToolValueTransformSubscriber`, `InputTypeCoercionSubscriber` and `tool:info`. The list half stays.
* The #3583002 carve-out for "multiple refined to a list." Once both sides are lists, the normal item-type narrowing check covers it.
`ToolContextDefinition` in `tool_ai_connector` sets core's `multiple` when the wrapped definition is a list. The ai module boundary is the only place the flag still exists.
### Remaining tasks
* [x] Land #3583048.
* [x] Land #3583041.
* [ ] Step 1 MR.
* [ ] Step 2 MR before rc1.
### Open questions
* `fromMultiple()` sends `Count` to the list so the advertised `minItems` stays. The #3583003 rule would put it on the item and drop `minItems`. Since this only lives in deprecated code, I'd keep the schema.
* `fromConfigSchema()` turns every `sequence` into a `ListInputDefinition`, but a sequence can have string keys (the existing dynamic-map `@todo`). Removing `multiple` doesn't change that. It's just the one place where "list" doesn't match config schema.
### API changes
* Step 1: deprecated `multiple: TRUE` on input definitions and on core context definitions passed as outputs. Removed the `$multiple` parameter from the list definition classes.
* Step 2: removed `isMultiple()`/`setMultiple()` from our definition interfaces, and the `$multiple` constructor parameter.
* Unchanged: the advertised JSON Schema, the ai module boundary.
### AI usage (if applicable)
* [x] AI Assisted Issue: This issue was generated with AI assistance, but was reviewed and refined by the creator.
* [ ] AI Assisted Code: This code was mainly generated by a human, with AI autocompleting or parts AI generated, but under full human supervision.
* [ ] AI Generated Code: This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human.
* [ ] Vibe Coded: This code was generated by an AI and has only been functionally tested.
issue
GitLab AI Context
Project: project/tool
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/tool/-/raw/1.0.x/README.md — project overview and setup
Repository: https://git.drupalcode.org/project/tool
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD