Skip to content

fix(intent): settlement allocation follows a corrected payment (#6818) - #6846

Merged
delchev merged 1 commit into
masterfrom
fix/settlement-payment-updated
Aug 19, 2026
Merged

fix(intent): settlement allocation follows a corrected payment (#6818)#6846
delchev merged 1 commit into
masterfrom
fix/settlement-payment-updated

Conversation

@delchev

@delchev delchev commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #6818.

The gap

settlements: bound the payment's bare create topic and nothing else. A payment is rarely final the moment it is typed in: one booked for the wrong amount is corrected the next day, one entered from a bank statement is completed once the counter-party is identified, one captured as a draft is amended before anyone treats it as money. In every one of those cases the allocation - and with it the invoice's settled figure - kept describing an amount the payment no longer carried, and nothing said so.

The fix

The spread handler is emitted once per bound payment event (create and update) from a new settlementListeners glue collection:

  • settlements still drives the one-per-settlement <Name>OnInvoice delegate. The two templates share the settlements collection today, so a second collection is what lets them fan out differently off one shared descriptor - rollupEntry copies it per class name + topic suffix, exactly as roll-ups and expansions already do.
  • The create handler keeps its established class name (<Name>OnPayment), so an existing project regenerates without churn; the correction one is <Name>OnPaymentUpdated.

The allocation was already a recompute of the payment's unallocated balance (pot less what is already allocated through the junction), which is what makes an extra delivery a no-op. It now converges in the other direction too: a payment corrected below what it already covers releases the excess through the junction repository - newest allocation first, reducing rather than removing the last row it touches - so the allocations sum to the payment again and the paid roll-up follows them down. Without that half, the fix would only have covered corrections that went up, which is not what "corrected the next day" means.

-transitioned is deliberately not bound here: that vocabulary gap is #6810, filed separately as the issue notes.

Verification

  • IntentEngineIT (50 tests) + IntentEmissionCoverageIT - 51/51 green. settlement_generates_on_payment_listener_and_on_invoice_delegate now also asserts the second listener, its -updated destination, the release call and that the release orders by the junction key descending through the repository.
  • The two generated listeners were rendered from the template and compiled with javac against the real api-modules-java + data-store-java jars - settlements have no compile-level coverage in any IT today (pre-existing), so the API use (delete/update/orderByDesc) was proven directly rather than assumed.
  • mvn formatter:validate clean; mvn -P release ... install (javadoc) clean on both touched Java modules.

Docs

🤖 Generated with Claude Code

`settlements:` bound only the payment's bare create topic, so a payment
booked for the wrong amount and corrected the next day - or created
incomplete and completed later - was never re-allocated: the invoice kept
the settled figure of an amount the payment no longer carried, or the
payment was never allocated at all.

The spread handler is now emitted once per bound payment event, from a new
`settlementListeners` glue collection. `settlements` still drives the
one-per-settlement `<Name>OnInvoice` delegate; a second collection is what
lets the two templates fan out differently off one shared descriptor
(`rollupEntry` copies it per class name + topic suffix, exactly as roll-ups
and expansions already do). The create handler keeps its established class
name, so an existing project regenerates without churn; the correction one
is `<Name>OnPaymentUpdated`.

The allocation was already written as a recompute of the payment's
UNALLOCATED balance, which is what makes an extra delivery a no-op. It now
converges in the other direction too: a payment corrected below what it
already covers releases the excess through the junction repository, newest
allocation first, so the allocations sum to the payment again and the paid
roll-up follows them down.

`-transitioned` is deliberately not bound here - that vocabulary gap is

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev force-pushed the fix/settlement-payment-updated branch from 1cf9fe8 to b3d3c15 Compare August 19, 2026 17:47
@delchev
delchev merged commit ae74a66 into master Aug 19, 2026
9 checks passed
@delchev
delchev deleted the fix/settlement-payment-updated branch August 19, 2026 17:48
delchev added a commit that referenced this pull request Aug 20, 2026
…reate event can no longer shrink it (#6864, #6865) (#6873)

Two residues of #6846, found in its review:

- A corrected MATCH column - the payment re-filed under another Customer -
  was invisible to the amount-based recompute (pot - allocated == 0), so
  the junction rows kept paying the OLD counterparty's invoices forever.
  The payment's match columns are now grouping keys, so its DAO publishes
  "-rekeyed" when one moves, and a third settlement listener releases
  everything and re-allocates from the STORE - both re-key notices run the
  same store-driven recompute, so their delivery order cannot matter and a
  redelivery converges. Emitted only for a LOCAL payment: a cross-model
  payment's DAO belongs to the owner model, and a projection has no
  repository to re-read - absent rather than silently dead. Closes #6864.

- The create handler no longer releases. A create event is the FIRST word
  about a payment - it has nothing to take back - so a negative pot there
  can only be a DELAYED create racing a correction the updated handler
  already allocated, and releasing on the stale payload transiently
  undid the correction. The updated handler owns every shrink.
  Closes #6865.

Also corrects the module guide's glue-collection note, which still cited
the generateUtils.js twin removed by #6707.

Covered by GlueSettlementRekeyTest (the third listener, and its deliberate
absence for a cross-model payment), EdmIntentGeneratorTest (the match
columns as the payment's grouping keys), and IntentEngineIT's settlement
emission block (the store-driven re-key recompute, the release-all, and
that the create handler carries no release).

Co-authored-by: Claude Fable 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.

settlements: binds only the payment's create topic - corrections and later confirmations never re-run allocation

1 participant