#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:
- 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 intoComponentSourceManagerwould be circular; draining lazily ingetStorablePropShape()was rejected because the deferral is deliberate (config:media_type_listis invalidated before the MediaType's source field exists). - Narrow the cold-cache guard. The guard skipping
generateComponents()on a cold cache was added inb9254bb4dto 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:installwith 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
- 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'simage_media_type.sdc.canvas_test_sdc.imagekeeps only its pre-media-type version until a full cache rebuild; the same sequence through the browser installer produces the correct two-version history. - With this MR, the new version is minted at the recipe boundary itself and a subsequent
hook_rebuild()changes nothing. - Covered by the new
ComponentVersionRecipeBoundaryTest(testRecipeBoundaryMintsNewVersions,testSingleProcessInstallNeedsNoCacheRebuild) — both verified to fail with the fix reverted, and coveringimage-gallery(array prop) thanks to !1475 (merged). Commit 2 also removes thesetCacheCreated()test workarounds, soPropShapeRepositoryTest::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.