From da0058635ef01632d9a1a90b1c83928f1147e292 Mon Sep 17 00:00:00 2001 From: delchev Date: Thu, 17 Sep 2026 10:31:27 +0300 Subject: [PATCH] spec: a cross-model parent roll-up may declare capacity and balance Both are arithmetic on the parent's own numeric columns, and the overdraw check they install belongs to the child, which is local on that direction - so both sides of an n:m allocation can be guarded from the module that owns the link rows. `status` remains local-only, and a `balance` with no `capacity` is refused. Co-Authored-By: Claude Opus 5 --- versions/1.2.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/versions/1.2.md b/versions/1.2.md index f321557..fd1a117 100644 --- a/versions/1.2.md +++ b/versions/1.2.md @@ -1244,7 +1244,9 @@ A roll-up writes onto the child's own parent. For a total grouped by SEVERAL rel The parent MAY be owned by another model. A roll-up whose `via` relation is a cross-model reference maintains a field on an entity that model owns: 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 keep an `actualHours` total on a project the projects model owns. -**Normative.** For a cross-model parent, the `via` relation's model MUST be declared in [`uses`](#reuse-dont-redefine--uses), and the parent field is validated against the owner's model at generation time rather than against the local document. A conforming generator MUST report a roll-up it cannot resolve (an undeclared model, or a field the owner does not have) instead of dropping it silently. The `capacity` / `balance` / `status` variants remain local-only: they read the parent's own limit and status values, which belong to the model that owns the parent. +A cross-model parent MAY also declare `capacity` and `balance`. Both are arithmetic on the parent's own numeric columns — the capacity is read, the balance is written alongside the sum — and the overdraw check they install 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. + +**Normative.** For a cross-model parent, the `via` relation's model MUST be declared in [`uses`](#reuse-dont-redefine--uses), and the parent's `field`, `capacity` and `balance` are validated against the owner's model at generation time rather than against the local document. A conforming generator MUST report a roll-up it cannot resolve (an undeclared model, or a column the owner does not have) instead of dropping it silently, MUST refuse a cross-model `balance` declared without a `capacity` (the balance is defined as `capacity - sum`, so on its own it names a column nothing fills), and MUST enforce the capacity on the child's own write path with the same refusal a same-model roll-up emits. The `status` variant remains local-only: it moves the parent through the owner's own status values, which belong to the model that owns the parent. ### aggregates — keyed cross-entity totals