fix(intent): re-parenting repairs both sides, from every write path - #6845
Merged
Conversation
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
force-pushed
the
fix/rekey-targeted-writes
branch
from
August 19, 2026 13:19
24eef42 to
0c72939
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #6819.
Re-parenting a record left the vacated group's totals permanently stale, from two independent gaps.
1.
-rekeyedwas unreachable from the targeted writes. The event exists precisely to carry the row whose grouping moved, but it was published only from the full-rowupdate(). A grouping FK moved by a workflow setter, by aresolves:lookup or by a task-form writer goes throughupdateProperties— which publishes no-updatedeither, 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
-rekeyedat 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, andaggregateKeyswas populated exclusively frommodel.getAggregates(), never from a roll-up'sviaFK.The change
groupingKeys(wasaggregateKeys) on the entity's.model— now the union of every aggregate key over the entity and every roll-upviaFK whose child it is, plusgroupingSourcePk. The old name was aggregates-only, which is exactly why a re-parented roll-up child was invisible.update()keeps publishing the previous row (the group it moved into is recomputed off-updatedlike any other change).updatePropertiessnapshots 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.RollupOnRekeyhandler. 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 theClaimLineClaimRollupOnRekeyhandler. These assertions fail onmaster.Run green locally: engine-intent unit tests, the full
-P unit-testsbuild,IntentEmissionCoverageIT,IntentEngineIT,JavaTemplateIT,ModelGenerationIT,formatter:validate.Overlap with #6837
#6837 (a count roll-up recomputes on child update) touches the same
buildRollupstail and the sameengine-intent/CLAUDE.mdbullet, and itsGlueRollupCountTestcounts 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
engine-intentguide gets a rekey bullet, and the assistant guide a line saying re-parenting is handled.