Skip to content

intent: a cross-model parent roll-up takes capacity / balance, overdraw guard included (#7410) - #7416

Merged
delchev merged 1 commit into
masterfrom
issue-7410-foreign-parent-rollup-capacity
Sep 17, 2026
Merged

delchev merged 1 commit into
masterfrom
issue-7410-foreign-parent-rollup-capacity

Conversation

@delchev

@delchev delchev commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

The gap

The two sides of an allocation are one sentence for the business - "an invoice cannot be paid past its payable" is "a payment cannot be applied past its amount" - and the DSL guarded one and forced hand-written Java for the other. A rollups: entry whose PARENT is owned by another model (via carrying its own model:) had capacity: / balance: / status: refused at parse, so an n:m allocation could guard only the side whose parent is local.

Reproduced live on 14.56.0: a CustomerPayment with Amount: 100 was allocated 100 to invoice A and 100 again to invoice B - both 200 OK, 200 allocated out of a 100 payment. The AP twin has the same hole.

The change

Nothing about that direction actually needed the owner's code:

  • the capacity is a READ of one numeric column of the parent,
  • the balance is a second column on the targeted updateDerived the sum already makes,
  • and the overdraw guard is emitted into the CHILD's repository - which on this direction is LOCAL, because the child owns the event the roll-up binds to.

So:

  • IntentParser refuses only status: on a cross-model parent now, with a message that says why (it moves the parent through the owner's status seeds and its generated displaced-status column, which is the owner's lifecycle to declare). It additionally refuses a balance: with no capacity: - the balance IS capacity minus the sum, so a lone one names a column nothing ever fills. New surface, so no model can already carry it; the local direction is deliberately left as it was.
  • capacity: / balance: join field: in firstUnresolvableParentProperty, checked against the owner's .model at Generate time, because the parser cannot see a foreign entity's fields. A miss drops the roll-up with an issue naming the property rather than emitting code that fails the whole client-Java batch.
  • EdmIntentGenerator.buildRollupGuards no longer requires the parent to be in the local byName: a via with a model: resolves the parent's perspective through CrossModelSupport and stamps the guard with the owner's parentGenFolder. The DAO then addresses that parent by its fully-qualified generated type instead of importing it - an import could collide with a local entity of the same name - while a local guard keeps its import and its plain name and renders byte-identically (parentGenFolder is empty, and a .model generated before this change has no such key at all).

The cross-model CHILD direction is unchanged: its guard really does belong to the owner module (the rows are written by the owner's repository), and Generate still reports that it is not installed.

Docs: the intent assistant guide's rollups section, plus the module's CLAUDE.md. The doc-site pages (dirigible.io help + the IntentFile spec) ship alongside.

Verified

  • mvn -pl components/engine/engine-intent,components/ide/ide-template test green - EdmRollupGuardTest (new: the guard's coordinates for a local and a foreign parent, and no guard without a capacity) and the reworked GlueRollupCrossModelTest (capacity + balance accepted, status still refused, a lone balance refused, and the owner-model checks for a misspelt capacity / balance).
  • RollupGuardCrossModelTemplateIT (new) renders Repository.java.template through the platform's Velocity engine for both parent directions: the foreign parent is loaded by its fully-qualified type on both write paths and never imported; the local parent keeps its import and plain name.
  • ModelGenerationIT green.
  • mvn formatter:validate green with the formatter cache wiped, and the -P release javadoc build of the changed modules green.
  • IntentEngineIT green (81 tests, 0 failures).
  • Not run: the rest of the Selenide UI suite - CI covers it.

Fixes #7410

🤖 Generated with Claude Code

…aw guard included (#7410)

The two sides of an allocation are one sentence for the business - "an invoice
cannot be paid past its payable" is "a payment cannot be applied past its
amount" - and the DSL guarded one and forced hand-written Java for the other: a
`rollups:` entry whose parent is owned by another model had `capacity:` /
`balance:` / `status:` refused at parse. So a CustomerPayment of 100 was
allocated 100 to invoice A and 100 again to invoice B, both 200 OK, with 200
allocated out of a 100 payment.

Nothing about that direction actually needed the owner's code. The capacity is a
READ of one numeric column of the parent; the balance is a second column on the
targeted `updateDerived` the sum already makes; and the overdraw guard is emitted
into the CHILD's repository - which on this direction is LOCAL, because the child
owns the event the roll-up binds to.

- the parser now refuses only `status:` there (it moves the parent through the
  owner's status seeds and its displaced-status column, which is the owner's
  lifecycle to declare), plus a `balance:` with no `capacity:` - the balance IS
  capacity minus the sum, so a lone one names a column nothing fills. New surface,
  so no model can already carry it; the local direction is left as it was.
- `capacity:` / `balance:` join `field:` in the generation-time check against the
  owner's `.model`, because the parser cannot see a foreign entity's fields; a
  miss drops the roll-up loudly rather than failing the client-Java batch.
- `buildRollupGuards` no longer requires the parent to be local: a `via` carrying
  a `model:` resolves the parent's perspective through CrossModelSupport and
  stamps the guard with the owner's `parentGenFolder`. The DAO addresses that
  parent by its fully-qualified generated type instead of importing it - an import
  could collide with a local entity of the same name - and a local guard keeps its
  import and its plain name, byte-identical.

The cross-model CHILD direction is unchanged: its guard really does belong to the
owner module, and Generate still reports that it is not installed.

Verified: engine-intent + ide-template unit suites green (EdmRollupGuardTest and
the reworked GlueRollupCrossModelTest cover the guard coordinates, the parse rules
and the owner-model checks); RollupGuardCrossModelTemplateIT renders the DAO
through Velocity for both parent directions; ModelGenerationIT green; formatter
validate and the javadoc release build green.

Fixes #7410

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.

intent: allow capacity:/balance: on a foreign-parent rollup - an allocation can guard only one of its two parents today

1 participant