Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/spec/glue.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,9 @@ Roll-ups are recompute-on-event (self-healing), so they are **eventually consist

Every roll-up recomputes on the child's **create, update and delete**, whatever its `op` reduces the children to - the event set belongs to the construct, not to the aggregation. The update pass is what a plain counter needs most: a child changes parents by an ordinary edit of its own parent relation. Moving a child to another parent - an edit of its `via` relation and nothing else - leaves BOTH parents right: the parent that received the child recomputes, and so does the parent it left, which does not go on counting a child that is no longer its own. That holds however the relation was written, including by a process step's field setter or a task form, which write one column and raise no ordinary change event.

The parent may be owned by **another model**. When the roll-up's `via` relation is a cross-model reference, the child stays local - it owns the event that drives the recompute - while the parent's coordinates come from the owner's model, so a time-tracking model can maintain an `actualHours` total on a project the projects model owns. The referenced model must be declared in `uses`, and the parent field is validated against the owner's model at generation time; an unresolvable roll-up is reported rather than dropped silently. The `capacity` / `balance` / `status` variants stay local-only, since they read the parent's own limit and status values.
The parent may be owned by **another model**. When the roll-up's `via` relation is a cross-model reference, the child stays local - it owns the event that drives the recompute - while the parent's coordinates come from the owner's model, so a time-tracking model can maintain an `actualHours` total on a project the projects model owns. The referenced model must be declared in `uses`, and the parent's `field`, `capacity` and `balance` are validated against the owner's model at generation time; an unresolvable roll-up is reported rather than dropped silently.

`capacity` and `balance` work on a cross-model parent, overdraw check included: the capacity is read off the parent, the balance is written alongside the sum, and the check that refuses a child row overdrawing the parent belongs to the child, which is local on this direction. That is what lets both sides of an n:m allocation be guarded from the module that owns the link rows - an invoice cannot be paid past its payable, and the payment it is paid from cannot be applied past its amount. A `balance` declared without a `capacity` is refused (the balance is `capacity - sum`). Only `status` stays local-only: it moves the parent through the owner's own status values.

## aggregates — keyed cross-entity totals

Expand Down