The GitHub Actions job "CI" on grails-core.git/fix/dirty-checking-collection-tracking-8.0.x has failed. Run started by GitHub user codeconsole (triggered by codeconsole).
Head commit for run: e88cb0beb97d90a04aa7de6fc52781f5768cc7f5 / Scott Murphy Heiberg <[email protected]> Keep collection properties dirty-checked after reassignment; track iterator-based removals Interception-based stores (MongoDB) rely exclusively on the DirtyChecking* wrappers — there is no flush-time snapshot comparison — so mutations that escape them are silently lost: save() sees a clean entity and persists nothing. Two real-world escapes: 1. Reassignment through a generated setter stored the raw value, so 'entity.items = []' over a tracked list replaced the wrapper with a plain untracked ArrayList. The common defensive re-init 'if (!entity.items) entity.items = []' triggers this on every load (an empty tracked collection is falsy in Groovy), and because [] == [] the equality-suppressed markDirty never flagged the assignment either. The in-place add() that followed was lost. 2. DirtyCheckingCollection never overrode iterator(), so every iterator-based removal — including Groovy's removeAll(Closure) and retainAll(Closure) and Java's removeIf — bypassed tracking, along with retainAll(Collection), List.sort and List.replaceAll. The fix stays interception-only (no snapshots, no flush-time diffing): - Wrappers override iterator()/listIterator() with dirty-marking iterators plus the missing direct mutators — the same approach as Hibernate's PersistentCollection. - Generated setters for Collection/List/Set/Map-typed properties assign through DirtyCheckingSupport.rewrap, which wraps the incoming value ONLY when the value being replaced was itself a tracked wrapper. A never-tracked property (transient instance, or a store like Hibernate with its own dirty checking) stores the raw value as before, and non-collection properties compile to identical bytecode. - A replacement wrapper is flagged isAssigned() so PersistentEntityCodec takes the full-rewrite path rather than per-element diffing — a replacement's layout need not match the stored array (a same-size replacement of clean elements previously emitted no update at all once wrapped). Specs reproduce each escape before the fix: DirtyCheckingCollectionSpec (wrapper mutation paths), DirtyCheckCollectionReassignmentSpec (setter reassignment), and EmbeddedCollectionDirtyTrackingSpec (end-to-end against MongoDB, replicating the production shape where an auto-timestamped entity dropped an embedded-collection add). Report URL: https://github.com/apache/grails-core/actions/runs/33448845373 With regards, GitHub Actions via GitBox
