Skip to content

fix(intent): an expansion reconciles its child set instead of rebuilding it - #6847

Merged
delchev merged 1 commit into
masterfrom
fix/expansion-diff-not-rebuild
Aug 19, 2026
Merged

fix(intent): an expansion reconciles its child set instead of rebuilding it#6847
delchev merged 1 commit into
masterfrom
fix/expansion-diff-not-rebuild

Conversation

@delchev

@delchev delchev commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #6817.

The failure

The generated expansions handler regenerated a master's child rows by deleting every existing row and then recreating the set. A client-Java handler has no transaction boundary — each delete and each save commits on its own — so a failure partway through the recreation left the deletes committed with only some of the inserts applied: rows gone for good, a stale count column, and roll-ups that had consumed a shrink which never really happened. Nothing read as inconsistent, because no individual write failed. The template's own repair check only ran on a later delivery, i.e. "when a human edits the master again".

The fix — diff, don't rebuild

Expansion.java.template now reconciles:

  • a period the new span covers with no row gains one;
  • a row whose period the new span no longer covers is deleted;
  • a row the span still covers is kept — same row, same identifier, with whatever was edited on it;
  • a second row on an already kept period (or an undated row) is deleted, which is what makes a set left inconsistent by an earlier partial run converge.

A failure can now leave the set incomplete — repaired on the next delivery — but can no longer destroy a row the span still wants. It is also cheaper: most span edits move an edge, so most rows were being deleted and recreated identically.

With spread, a kept row's share is derived from the master's total and the row count, so it is re-spread in place through updateDerived — the targeted single-column write that still publishes "-updated", so a roll-up over these rows recomputes exactly as it did when the row was deleted and recreated. The last-row rounding remainder is unchanged.

The descriptor gained childPk (the child's primary key) — that is what addresses a kept row for the in-place re-spread — carried through GlueGenerator.bindExpansion.

The interim option in the issue (build the new set first, delete afterwards) is not taken: the diff removes the destructive window entirely rather than trading it for recoverable duplicates. A real transaction boundary for client-Java handlers remains the open follow-up the issue mentions; this handler needs none.

Tests

  • GlueExpansionsTest (new) — the descriptor carries the reconciliation coordinates, childPk included: a missing one would emit child. with no member and take the whole client-Java batch down with it.
  • IntentEngineIT.expansion_generates_the_span_handlers_and_the_status_badge_stack — the generated source keeps the surviving rows, deletes only what fell out of the span, re-spreads through updateDerived, and no longer contains the unconditional wipe.
  • IntentEmissionCoverageIT — a Stay / StayNight expansion is now part of the published-app fixture, so the handler is compiled by the real javac batch and the runtime promise is asserted: a three-day span expands into three rows, the span is edited to move one day out and two days in, and the two surviving days must answer with the identifiers they had — plus shares re-spread to 75.00 over four nights and a count write-back of 4. (Run against the pre-fix template, this fails with a new identifier for a surviving day, which is the bug.)

Both ITs verified locally (H2, headless), the changed modules' unit suites are green (692 tests in engine-intent), formatter:validate passes with a cold cache, and -P release javadoc builds clean on both changed Java modules.

Docs: dirigible-io/dirigible-io.github.io#206 (merged); spec proposal IntentFile/intent-specification#45 and IntentFile/intentfile.github.io#36 are open. The in-repo intent-assistant-guide.md, module README and ExpansionIntent javadoc are updated here.

🤖 Generated with Claude Code

@delchev
delchev force-pushed the fix/expansion-diff-not-rebuild branch from c41b539 to 30a537a Compare August 19, 2026 13:07
…ing it

Closes #6817.

The generated expansions handler regenerated a master's child rows by
deleting every existing row and then recreating the set. A client-Java
handler has no transaction boundary - each delete and each save commits on
its own - so a failure partway through the recreation left the deletes
committed with only some of the inserts applied: rows gone for good, a stale
count column, and roll-ups that had consumed a shrink which never really
happened. Nothing read as inconsistent, because no individual write failed.

The handler now diffs: a period with no row gains one, a row whose period
fell out of the span is deleted, and a row the span still covers is kept -
same row, same identifier, with whatever was edited on it. A second row on
an already kept period is deleted, which is what makes a set left
inconsistent by an earlier partial run converge. A failure can now leave the
set incomplete, never destroy a row the span still wants; and it is cheaper,
since most span edits move an edge.

With spread, a kept row's share is derived from the master's total and the
row count, so it is re-spread through updateDerived - the targeted write that
still publishes "-updated", so a roll-up over these rows recomputes exactly
as it did when the row was deleted and recreated.

Asserted on both layers: IntentEngineIT on the generated source, and
IntentEmissionCoverageIT at runtime - an edited span must answer with the
same row identifiers for the days it still covers, re-spread shares and a
count that followed the new row count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev force-pushed the fix/expansion-diff-not-rebuild branch from 30a537a to 6d7741b Compare August 19, 2026 13:30
@delchev
delchev merged commit e455bb6 into master Aug 19, 2026
10 checks passed
@delchev
delchev deleted the fix/expansion-diff-not-rebuild branch August 19, 2026 17:43
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.

expansions: deletes every child before recreating them, without a transaction - a mid-flight failure destroys committed rows

1 participant