Statically enforce that CanvasConfigUpdater Component update paths are wired into ::preSave() (and ship a post_update)
## Overview
While fixing #3591607 (MR !1175) we hit an easy-to-miss class of bug: a
`CanvasConfigUpdater` method that updates a `Component` was given a
`hook_post_update_N` but was **not** also invoked from
`Component::preSave()`. Because a post-update only runs once per site (and
never for imported config), a component re-saved outside the update path
silently keeps its outdated data. See
[!1175 note 1070980](https://git.drupalcode.org/project/canvas/-/merge_requests/1175#note_1070980)
and
[note 1070982](https://git.drupalcode.org/project/canvas/-/merge_requests/1175#note_1070982).
Nothing currently stops a contributor from adding another such method and
forgetting the `::preSave()` wiring — it's hard to catch in review. As
@wimleers noted, this is a good candidate for deterministic, attribute-driven
enforcement rather than relying on humans (or an LLM) to remember.
## Proposed resolution
Add metadata + custom PHPStan rules:
- `#[ComponentPreSaveUpdate(postUpdate: '…')]` — marks a `CanvasConfigUpdater`
method that must heal a `Component` on save, and records the one-time update
path that heals already-stored config.
- `#[NotAppliedOnComponentPreSave(reason: '…')]` — explicit opt-out, so "must
not run on save" is a stated decision, not an omission.
- Rule 1: every `#[ComponentPreSaveUpdate]` method is (a) invoked from
`Component::preSave()` and (b) names a `post_update` function that exists.
- Rule 2: every public, non-`needs*`, `Component`-taking `CanvasConfigUpdater`
method declares exactly one of the two attributes — so forgetting to even
consider preSave fails the build.
- Annotate the existing updater methods (required-flag, text-value, prop-order,
category-unset, multi-branch reference, list_float hash recompute).
## Remaining tasks
- [x] Land the two attributes in `src/Attribute/`.
- [x] Land the two rules in `phpstan_rules/` and register them in `phpstan.neon`.
- [x] Annotate the existing updater methods.
- [ ] (Optional) Extend Rule 1 to assert the named `post_update` actually
*calls* the method, not just that it exists.
- [ ] (Optional) Consider the same guard for other entities' updaters
(e.g. `updateJavaScriptComponent` → `JavaScriptComponent::preSave()`).
## UI changes
None — developer-facing static analysis only.
## Related
- Follow-up to #3591607.
- Related: #3578767 (evaluating third-party PHPStan rule packages); this is a
project-specific custom rule instead.
issue