Skip to content

feat(intent): aggregates: keyed cross-entity aggregate (model + glue + keyed-upsert handler)#6407

Merged
delchev merged 2 commits into
masterfrom
feat/intent-aggregates
Jul 25, 2026
Merged

feat(intent): aggregates: keyed cross-entity aggregate (model + glue + keyed-upsert handler)#6407
delchev merged 2 commits into
masterfrom
feat/intent-aggregates

Conversation

@delchev

@delchev delchev commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

aggregates: keyed cross-entity aggregate (COMPLETE - model + glue + handler, boot-verified)

A new intent primitive: a running sum/count of a source entity's field, grouped by one or more of
its to-one relations
, materialised into a separate target entity keyed by that group (one target
row per key-tuple). This is the two-key balance-rollup the ledger on-hand needs - a generalisation of
rollups: (which is single-key, child -> composition parent).

aggregates:
  - name: onHand
    of: StockMovement          # the source rows
    op: sum                    # sum (default) | count
    sum: quantity              # the signed field summed
    by: [Product, Store]       # the grouping keys (to-one relations of BOTH source and target)
    into: ProductAvailability  # the materialised target, keyed by the same relations
    field: onHand              # the target field holding the running value

What it emits

Each logical aggregate expands into three MessageHandlers (source create / -updated /
-deleted). Every one upserts the target row for the incoming row's key-tuple and recomputes the
aggregate from the store over all source rows sharing that tuple - idempotent and self-healing
(re-delivery / replay converge). A source row with any grouping key null is ignored.

  • model/AggregateIntent.java + IntentModel.aggregates (stage 1)
  • GlueIntentGenerator.buildAggregates - validates each by key is a to-one relation of BOTH source
    and target; emits the descriptor (source/target coordinates, keys, summed field, target field)
  • events/Aggregate.java.template - the keyed-upsert handler (shape-only; key expressions
    pre-rendered)
  • template.js + generateUtils.js case "aggregates" - the 3-handler expansion + expression rendering
  • Glue early-return guard fix: added posts + aggregates to the emptiness guard in
    GlueIntentGenerator.generate. Latent bug - an aggregates-only (or posts-only) intent skipped glue
    emission entirely; masked for posts only because those modules always carried other glue.

Verification

  • GlueAggregatesTest (glue descriptor + both keys resolved); full engine-intent suite 218/218.
  • Boot-verified end-to-end on a real instance (fresh registry, client-Java synchronizer):
    • all three handlers compile and connect as TOPIC listeners
      (agg-demo-StockMovement-StockMovement[-updated|-deleted]);
    • REST-posting StockMovement rows maintains ProductAvailability.onHand keyed by (Product,Store):
      10 + 5 - 3 = 12 for one tuple, 7 for another - two isolated materialised rows, signed sum.

Scope / follow-ups (v1)

  • Same-model source + target; uniform BigDecimal accumulation into a decimal target field.
  • A key CHANGE on update recomputes the NEW tuple only (append-only ledgers - the primary use - never
    edit keys; the old-tuple recompute is a documented follow-up, same limitation as rollups:).
  • count into an integer target and cross-model targets are follow-ups.

Downstream (KeyFolders): this is the linchpin under the aggregate guard checks (negative-stock,
credit-limit, vacation-capacity) and retires the hand-written costing listener + the on-hand
live-report workaround - materialising on-hand as a real FK target.

delchev and others added 2 commits July 25, 2026 09:40
… (stage 1)

First slice of aggregates: (the two-key balance rollup / materialised on-hand; CLAUDE.md §13 and the
prerequisite for the guard checks - kf-catalog PROPOSAL_AGGREGATE_CHECKS.md). Generalises rollups:
(single-key child->parent) to a running sum/count grouped by MULTIPLE to-one relations, materialised
into a SEPARATE target entity keyed by that group.

- AggregateIntent model (name/of/op/sum/by[]/into/field) + IntentModel.aggregates.
- GlueIntentGenerator.buildAggregates: resolves source + target coordinates, validates each `by` key
  is a to-one relation of BOTH source and target (the target is keyed by the same FKs), emits the
  descriptor (op, source/sum, keys, target/field). glue.put("aggregates").
- GlueAggregatesTest: onHand = sum(StockMovement.quantity) by [Product, Store] -> ProductAvailability
  .onHand; asserts the resolved descriptor + both keys. engine-intent 218/218.

Stage 2: the keyed-upsert handler template (maintain the target row per key-tuple on source
create/post/delete) + generateUtils wiring; then the guard checks on top, then inventory adoption.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the aggregates: feature: each logical aggregate expands into three
MessageHandlers (source create / -updated / -deleted) that upsert the target row
for the incoming row's key-tuple and recompute the sum/count from the store
(idempotent, self-healing). Same-model, BigDecimal into a decimal target.

- events/Aggregate.java.template: the keyed-upsert handler (shape-only; all key
  expressions pre-rendered by generateUtils).
- template.js: register the aggregates collection -> Aggregate.java.template.
- generateUtils.js case "aggregates": 3-handler expansion + expression rendering.
- GlueIntentGenerator early-return guard: add posts + aggregates (latent bug - an
  aggregates-only OR posts-only intent skipped glue emission entirely; masked for
  posts because those modules always carried other glue).

Boot-verified end-to-end on a real instance: handlers compile + connect as TOPIC
listeners; REST-posting StockMovements maintains ProductAvailability.onHand keyed
by (Product,Store) - 10+5-3=12 for one tuple, 7 for another, two isolated rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@delchev delchev changed the title feat(intent): aggregates: keyed cross-entity aggregate (stage 1 - model + glue) feat(intent): aggregates: keyed cross-entity aggregate (model + glue + keyed-upsert handler) Jul 25, 2026
@delchev
delchev merged commit 7606f54 into master Jul 25, 2026
10 checks passed
@delchev
delchev deleted the feat/intent-aggregates branch July 25, 2026 08:07
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.

1 participant