|
| 1 | +--- |
| 2 | +"@objectstack/objectql": minor |
| 3 | +"@objectstack/cli": minor |
| 4 | +--- |
| 5 | + |
| 6 | +feat(objectql,cli): `backfillSummaryNulls` accepts `recomputeUndefinedOnEmpty` — a caller who KNOWS a `min`/`max`/`avg` roll-up column was just declared can have it filled; `os migrate summary-nulls --recompute-undefined-on-empty object.field` surfaces it (#15064) |
| 7 | + |
| 8 | +A roll-up value has three producers — the insert-time seed, the child-write |
| 9 | +recompute, and the one-off backfill — and **declaring a summary field on an |
| 10 | +object that already has rows reaches none of them**. For `count`/`sum` the |
| 11 | +backfill repairs that as a side effect (every `NULL` is a hole to it). For |
| 12 | +`min`/`max`/`avg` it could not: `summaryNullIsBackfillable` decides on the |
| 13 | +function alone, so "never computed" and "no child rows" were indistinguishable, |
| 14 | +the column stayed `NULL` on every pre-existing parent, and the report said |
| 15 | +`filled: 0` — a false all-clear that a timed flow built on the column then |
| 16 | +turned into "matches nothing" (the customer case behind cloud#1908). |
| 17 | + |
| 18 | +**What changes** — maintainer ruling on #15064, option A: the caller who holds |
| 19 | +the fact gets a way to say it; the predicate and the default run do not move. |
| 20 | + |
| 21 | +- `SummaryBackfillOptions.recomputeUndefinedOnEmpty?: string[]` — `object.field` |
| 22 | + roll-ups the caller knows were never computed. A named `min`/`max`/`avg` is |
| 23 | + walked like a `count`: every `NULL` parent is recomputed through the same |
| 24 | + `aggregateSummaryValue` the engine writes. A parent whose aggregate is the |
| 25 | + empty-set reading (`null` — no child rows) already holds the engine's own |
| 26 | + value, so it is neither counted as a hole nor written; the scoped run is |
| 27 | + therefore idempotent in the same "re-run until it reports zero" sense. |
| 28 | + Naming a `count`/`sum` is accepted and changes nothing, so a publish path can |
| 29 | + pass every column it just declared without knowing the empty-set list. |
| 30 | +- A name that resolves to no roll-up owned by an object the run walks — a typo, |
| 31 | + a plain field, or an object `objects` left out — is **refused before any row |
| 32 | + is read**, dry run or apply, with an ADR-0112 envelope (`code: |
| 33 | + 'INVALID_FIELD'`, `status: 400` — the code the projection and write axes |
| 34 | + that name a field already answer, while sorting keeps `INVALID_SORT`; |
| 35 | + `field` names the first unresolved entry, `fields` all of them). A silent |
| 36 | + no-op there would be the same false all-clear this option exists to end. |
| 37 | +- `SummaryBackfillReport.recomputedUndefinedOnEmpty: string[]` — the complement |
| 38 | + of `skippedUndefinedOnEmpty`, same `object.field (fn)` spelling; `[]` on an |
| 39 | + unscoped run. `SummaryBackfillFieldOutcome.fn` widens from `'count' | 'sum'` |
| 40 | + to every roll-up function, since a named `max` now appears in `fields`. |
| 41 | +- `os migrate summary-nulls --recompute-undefined-on-empty object.field` |
| 42 | + (repeatable) passes the scope through; the confirmation prompt names the |
| 43 | + columns; `formatSummaryBackfillReport` lists them under "Recomputed on |
| 44 | + request" and explains a `NULL` that remains. |
| 45 | + |
| 46 | +**What does not change:** without the option the walk, the writes, every |
| 47 | +counter and the human-readable report are byte-for-byte what they were (pinned |
| 48 | +against output captured on `main` before this change); `min`/`max`/`avg` stay |
| 49 | +out of scope and keep being reported under `skippedUndefinedOnEmpty`; the |
| 50 | +predicate `summaryNullIsBackfillable` is untouched, so `os migrate |
| 51 | +summary-nulls` keeps its meaning on every deployment. The only visible delta on |
| 52 | +an unscoped run is the one additive report key, `recomputedUndefinedOnEmpty: []`. |
| 53 | + |
| 54 | +`minor` for both packages: an optional parameter on a published exported |
| 55 | +function, a new report key, and a new CLI flag are each a purely additive |
| 56 | +widening of a published surface, which takes at least `minor` (bump-level rule, |
| 57 | +2026-09-04); the `fix`-shaped motivation does not lower it. |
0 commit comments