#3591916 Drain invalidated prop shapes before component generation; narrow the cold-cache guard

Closes #3591916

Depends on !1475 (merged) — this branch is based on it and includes its two commits; only 0f53719b6 and 8316a7023 are new here. The array-prop test coverage below needs !1475 (merged)'s fix to pass.

What this MR does

Two changes to PersistentPropShapeRepository, one per commit:

  1. Drain invalidated prop shapes before generating components. New public resolveInvalidatedPropShapes() processes the queued re-resolutions; it is called at the three full-regeneration triggers (RecipeSubscriber::ensureComponentsExist(), hook_rebuild(), hook_modules_installed()), so the regeneration pass at a recipe's own boundary computes version hashes from fresh shapes instead of shapes queued for teardown. It lives at the call sites because injecting the repository into ComponentSourceManager would be circular; draining lazily in getStorablePropShape() was rejected because the deferral is deliberate (config:media_type_list is invalidated before the MediaType's source field exists).
  2. Narrow the cold-cache guard. The guard skipping generateComponents() on a cold cache was added in b9254bb4d to prevent JavascriptComponent resave churn — a rationale retired within the same MR when #3556327 (closed) made generation saves idempotent (confirmed in the !145 (merged) discussion). It now skips generation only when nothing was invalidated (pure first-time resolution), so a single-process install (drush site:install with recipes) can no longer end with stale hashes that only heal on the next cache rebuild. Lock semantics unchanged: nothing runs generation while holding the collector lock.

No update path: sites already carrying stale hashes self-heal on their next cache rebuild, module install, or recipe apply — which every deploy performs.

Testing steps

  1. On 1.x (or !1475 (merged)) without this MR: in a single process, apply a recipe installing canvas_test_sdc, then a recipe applying core's image_media_type. sdc.canvas_test_sdc.image keeps only its pre-media-type version until a full cache rebuild; the same sequence through the browser installer produces the correct two-version history.
  2. With this MR, the new version is minted at the recipe boundary itself and a subsequent hook_rebuild() changes nothing.
  3. Covered by the new ComponentVersionRecipeBoundaryTest (testRecipeBoundaryMintsNewVersions, testSingleProcessInstallNeedsNoCacheRebuild) — both verified to fail with the fix reverted, and covering image-gallery (array prop) thanks to !1475 (merged). Commit 2 also removes the setCacheCreated() test workarounds, so PropShapeRepositoryTest::testStorablePropShapeAlter() and ::testArrayPropShapeInheritsItemPropShapeCacheTags() now exercise the real production invalidation path.

Notes for reviewers

The empirical investigation behind this (six install-sequence scenarios, drush-vs-browser divergence) is documented in #3591916.

Written with AI assistance (Claude Fable 5 / Claude Opus 4.8): investigation, fix, and tests were AI-drafted and human-reviewed.

Merge request reports

Loading