Skip to content

fix(intent): re-parenting repairs both sides, from every write path - #6845

Merged
delchev merged 1 commit into
masterfrom
fix/rekey-targeted-writes
Aug 19, 2026
Merged

fix(intent): re-parenting repairs both sides, from every write path#6845
delchev merged 1 commit into
masterfrom
fix/rekey-targeted-writes

Conversation

@delchev

@delchev delchev commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #6819.

Re-parenting a record left the vacated group's totals permanently stale, from two independent gaps.

1. -rekeyed was unreachable from the targeted writes. The event exists precisely to carry the row whose grouping moved, but it was published only from the full-row update(). A grouping FK moved by a workflow setter, by a resolves: lookup or by a task-form writer goes through updateProperties — which publishes no -updated either, so neither side of that move was ever recomputed. The move a process makes, on records nobody re-opens by hand, was the one that silently skipped the repair.

2. Roll-ups never consumed -rekeyed at all. A roll-up handler recomputes the parent named by the incoming payload, and the create/update/delete events all name the parent the child belongs to now — so the parent it was moved away from went on counting it forever. Only aggregates subscribed to -rekeyed, and aggregateKeys was populated exclusively from model.getAggregates(), never from a roll-up's via FK.

The change

  • groupingKeys (was aggregateKeys) on the entity's .model — now the union of every aggregate key over the entity and every roll-up via FK whose child it is, plus groupingSourcePk. The old name was aggregates-only, which is exactly why a re-parented roll-up child was invisible.
  • The DAO compares those columns on both write paths. update() keeps publishing the previous row (the group it moved into is recomputed off -updated like any other change). updateProperties snapshots the row as JSON before the incoming values are applied and, when a tracked column actually moved, publishes the previous row and the written one — on that path neither side has an event otherwise. Nothing but the generated aggregate / roll-up handlers subscribes to -rekeyed, so a targeted write still re-fires no reaction; that is what lets it signal them without re-publishing -updated.
  • Roll-ups bind it, as a fourth RollupOnRekey handler. It is the same idempotent recompute keyed on the payload's FK, so one class repairs whichever side it is fed.

The publish stays gated on a tracked column having actually changed, so an ordinary edit costs nothing extra and the transitive cascade still terminates at rest.

Tests

  • GlueRollupRekeyTest (new) — every roll-up binds -rekeyed, with the payload-keyed criteria, additional to the create/delete handlers.
  • EdmIntentGeneratorTest.rollupChildCarriesItsParentFkAsAGroupingKey (new) — a roll-up child carries its parent FK; the parent carries nothing.
  • GlueRollupLatestTest, EdmIntentGeneratorTest — updated for the fourth handler and the renamed model keys.
  • IntentEmissionCoverageIT — the targeted path's before/after comparison and its two publishes, plus the roll-up child's tracking and the ClaimLineClaimRollupOnRekey handler. These assertions fail on master.

Run green locally: engine-intent unit tests, the full -P unit-tests build, IntentEmissionCoverageIT, IntentEngineIT, JavaTemplateIT, ModelGenerationIT, formatter:validate.

Overlap with #6837

#6837 (a count roll-up recomputes on child update) touches the same buildRollups tail and the same engine-intent/CLAUDE.md bullet, and its GlueRollupCountTest counts the emitted handlers — whichever merges second needs a small rebase (the count becomes 4). The two are complementary: #6837 fixes the parent a child moved to on the full-row path, this one fixes the parent it moved away from, and both sides on the targeted path.

Docs

Re-parenting a record left the VACATED group's totals permanently stale, from two
independent gaps.

The "-rekeyed" event exists precisely to carry the row whose grouping moved, but it was
published only from the full-row update(). A grouping FK moved by a workflow setter, by a
resolves: lookup or by a task-form writer goes through the targeted primitive
updateProperties, which publishes no "-updated" either - so NEITHER side of that move was
ever recomputed. And roll-ups never consumed "-rekeyed" at all: their handlers recompute the
parent named by the incoming payload, and the create/update/delete events all name the parent
the child belongs to now, so the parent it was moved away from went on counting it forever.

Three parts:

- The entity's .model now carries groupingKeys - the union of every aggregate key over it AND
  every roll-up `via` FK whose child it is (it was aggregateKeys, aggregates-only, which is why
  a re-parented roll-up child was invisible), plus groupingSourcePk.
- The DAO compares those columns on BOTH write paths. update() keeps publishing the previous
  row (the group it moved into is recomputed off "-updated" like any other change);
  updateProperties snapshots the row as JSON before applying the incoming values and, when a
  tracked column actually moved, publishes the previous row AND the written one - on that path
  neither side has an event otherwise. Nothing but the generated aggregate / roll-up handlers
  subscribes to "-rekeyed", so a targeted write still re-fires no reaction.
- Roll-ups bind it, as a fourth RollupOnRekey handler. It is the same recompute keyed on the
  payload's FK, so one class repairs whichever side the payload names.

Closes #6819
@delchev
delchev force-pushed the fix/rekey-targeted-writes branch from 24eef42 to 0c72939 Compare August 19, 2026 13:19
@delchev
delchev merged commit 269f612 into master Aug 19, 2026
10 checks passed
@delchev
delchev deleted the fix/rekey-targeted-writes branch August 19, 2026 17:41
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.

Re-parenting a child leaves the vacated parent stale: -rekeyed is unreachable from targeted writes and rollups never consume it

1 participant