intent: a cross-model parent roll-up takes capacity / balance, overdraw guard included (#7410) - #7416
Merged
Merged
Conversation
…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>
This was referenced Sep 17, 2026
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.
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 (viacarrying its ownmodel:) hadcapacity:/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
CustomerPaymentwithAmount: 100was allocated100to invoice A and100again to invoice B - both200 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:
updateDerivedthe sum already makes,So:
IntentParserrefuses onlystatus: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 abalance:with nocapacity:- 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:joinfield:infirstUnresolvableParentProperty, checked against the owner's.modelat 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.buildRollupGuardsno longer requires the parent to be in the localbyName: aviawith amodel:resolves the parent's perspective throughCrossModelSupportand stamps the guard with the owner'sparentGenFolder. 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 (parentGenFolderis empty, and a.modelgenerated 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 testgreen -EdmRollupGuardTest(new: the guard's coordinates for a local and a foreign parent, and no guard without a capacity) and the reworkedGlueRollupCrossModelTest(capacity + balance accepted,statusstill refused, a lone balance refused, and the owner-model checks for a misspelt capacity / balance).RollupGuardCrossModelTemplateIT(new) rendersRepository.java.templatethrough 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.ModelGenerationITgreen.mvn formatter:validategreen with the formatter cache wiped, and the-P releasejavadoc build of the changed modules green.IntentEngineITgreen (81 tests, 0 failures).Fixes #7410
🤖 Generated with Claude Code