(bug fix): prune usage after change promotion in loop usage finalization#10201
Conversation
A loop/closure that reads an ancestor as a snapshot (`@a.b`) while mutating a nested field (`a.b.c`) silently dropped the mutation, returning a stale value (e.g. 1 instead of 4). In `Usage::finalize_as_scope`, when a changed child has an ancestor that is read as a snapshot, the ancestor is promoted into `usage`/`changes` so the loop reads and writes back the whole ancestor. The usage-pruning pass ran *before* the promotion, so the promoted ancestor's descendants survived in `usage`, violating the antichain invariant. Downstream the loop signature then listed both the ancestor and its descendants, and the reconstruction of the returned ancestor overwrote the mutated child with the stale snapshot input. Fix: reorder the finalization passes — snap_usage, then changes (promotion), then usage — so the usage pass prunes descendants subsumed by any promoted ancestor. Adds an e2e sanity repro.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
PR SummaryMedium Risk Overview In Comments in the promotion logic are updated to describe ancestor promotion vs. stale child snapshots. An e2e CASM sanity case Reviewed by Cursor Bugbot for commit 9f8cd38. Bugbot is set up for automated code reviews on this repo. Configure here. |
TomerStarkware
left a comment
There was a problem hiding this comment.
@TomerStarkware reviewed 2 files and all commit messages, and made 1 comment.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on orizi).

A loop/closure that reads an ancestor as a snapshot (
@a.b) while mutating anested field (
a.b.c) silently dropped the mutation, returning a stale value(e.g. 1 instead of 4).
In
Usage::finalize_as_scope, when a changed child has an ancestor that isread as a snapshot, the ancestor is promoted into
usage/changesso the loopreads and writes back the whole ancestor. The usage-pruning pass ran before
the promotion, so the promoted ancestor's descendants survived in
usage,violating the antichain invariant. Downstream the loop signature then listed
both the ancestor and its descendants, and the reconstruction of the returned
ancestor overwrote the mutated child with the stale snapshot input.
Fix: reorder the finalization passes — snap_usage, then changes (promotion),
then usage — so the usage pass prunes descendants subsumed by any promoted
ancestor. Adds an e2e sanity repro.