Skip to content

fix(intent): a count roll-up recomputes when a child changes parents - #6837

Open
delchev wants to merge 1 commit into
masterfrom
fix/rollup-count-updated
Open

fix(intent): a count roll-up recomputes when a child changes parents#6837
delchev wants to merge 1 commit into
masterfrom
fix/rollup-count-updated

Conversation

@delchev

@delchev delchev commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #6820.

What was wrong

GlueIntentGenerator.buildRollups emitted the -updated handler only for op: sum and op: latest:

rollups.add(rollupEntry(base, className + "RollupOnCreate", ""));
if (sum || latest) {
    rollups.add(rollupEntry(base, className + "RollupOnUpdate", "-updated"));
}
rollups.add(rollupEntry(base, className + "RollupOnDelete", "-deleted"));

op defaults to count, so the default case was the excluded one — and it is the case that needs the update handler most. A child changes parents by an ordinary edit of its parent relation (a loan reassigned to another member, a task dragged to another sprint): no child is created, none destroyed, so nothing recomputed. The parent that received the child never counted it, and because a roll-up is recompute-on-event the counter stayed wrong until some unrelated child of that parent happened to be created or deleted — an off-by-one counter, which reads as plausible.

The fix

Emit the update handler for every op. Nothing else changes: the recompute is the same query in all three cases and reads the child rows back from the store (findAll(Criteria.create().eq("<Fk>", entity.<Fk>))), so the handler is idempotent — an edit that touched nothing the roll-up reads finds the value unchanged and, thanks to the existing changed guard, writes nothing and publishes no parent update. The event set is a property of the construct, not of the aggregation, so the emission is no longer op-specific.

The parent the child moved away from is still stale: the update event carries the row's current FK only. That needs roll-ups to consume the -rekeyed event (the mechanism aggregates already uses) and is #6819; this fix alone makes the new parent correct.

Tests

  • GlueRollupCountTest (new, engine-intent) — a count roll-up emits exactly three descriptors, binding "" / -updated / -deleted, all carrying the same op and recompute criteria.
  • IntentEngineIT.rollup_generates_create_and_delete_listeners_that_recompute_the_parent_count — now also asserts the generated LoanMemberRollupOnUpdate binds intent-test-Loan-Loan-updated and recomputes exactly like the create handler.

Verified locally: IntentEngineIT 50/50, IntentEmissionCoverageIT + ModelGenerationIT green, engine-intent + ide-template unit tests 690/690, formatter:validate clean, -P release javadoc clean.

Docs

🤖 Generated with Claude Code

A `rollups:` entry with the default `op: count` got a create and a delete
handler but no `-updated` one - the generator assumed a count can only move
when a child is created or destroyed. A child changes parents by an ordinary
EDIT of its parent relation, so re-parenting recomputed neither count: the
parent that received the child never counted it.

Emit the update handler for every op. The recompute is the same query in all
three cases and it reads the child rows back from the store, so it is
idempotent and never op-specific - an edit that touched nothing the roll-up
reads finds the value unchanged and writes nothing. The parent a child moved
AWAY from is still stale until roll-ups also consume the `-rekeyed` event,
which is filed separately; this alone makes the new parent correct.

Covered by a `GlueRollupCountTest` unit test on the emitted descriptors and
by IntentEngineIT, which now asserts the generated
`LoanMemberRollupOnUpdate` binds the child's `-updated` topic and recomputes
exactly like the create handler.

Closes #6820

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rollups: op: count emits no -updated handler - re-parenting recomputes neither count

1 participant