Skip to content

Commit e64b46d

Browse files
committed
merge origin/main (os-regen artifacts taken from main; regeneration follows)
1 parent 0ab640d commit e64b46d

2 files changed

Lines changed: 18 additions & 18 deletions

File tree

‎docs/protocol-upgrade-guide.md‎

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

‎packages/spec/spec-changes.json‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,6 +2206,13 @@
22062206
"toMajor": 18,
22072207
"rationale": "ADR-0032 Decision 3 fixes one template delimiter, double braces, and deletes the single brace: it collides with CEL map literals, and an author who meets both dialects in one flow mixes them. The 17.x interpolator and the template engine render the same text for a path holding a string, a number, a boolean, null, an absent key or variable, an ISO date string, an object or an array, but not for every value — a Date rendered JSON-quoted under the interpolator and as its ISO text under the engine, and a screen title, screen description or end message that was one token holding an object, an array or a Date rendered String(value) — so no conversion is lossless (ADR-0087 D2) and none is applied. Arithmetic, function calls, the date macros and the run-user paths have no hole spelling: a hole is a path with a formatter, never logic. A flow carrying a single-brace token in a text slot is refused at registration, by objectstack validate and by the node contract; a stored flow carrying one is skipped at boot with a warn naming it."
22082208
},
2209+
{
2210+
"surface": "flows[].nodes[].config of a notify node (title, message), a screen node (title, description) and an end node (message) — a string, or the source of a template envelope, carrying a double-brace hole whose root is a dollar-named variable the flow engine does not bind, such as {{ $User.Id }}",
2211+
"replacement": "a variable the run has, written as a hole. The run user is computed first, with an assignment node whose value slot still reads the run-user path (assignments: { by: '{$User.Id}' }), then written as {{ by }}. A variable the flow binds itself (a declared variable, an assignment target, an outputVariable, a try_catch errorVariable) is named without the dollar sign and written as {{ caught.message }}. The engine's own variables stay holes: {{ $error.message }}, {{ $record.name }}, {{ $runId }}, {{ $flowName }}, {{ $flowLabel }}, and a flat-graph loop's {{ $loopItems }} / {{ $loopIndex }}",
2212+
"migrationId": "flow-text-slot-unbound-dollar-root-refused",
2213+
"toMajor": 18,
2214+
"rationale": "The dollar-named variables are the flow engine's own: it binds $record, $runId, $flowName, $flowLabel and $error, and a flat-graph loop binds $loopItems and $loopIndex. A hole over any other dollar name answers to no variable — {{ $User.Id }} looks like the run user and is not one, since the run user has no hole spelling. In 17.x the slot was a plain string read by the single-brace interpolator, which substituted the inner token and left a literal brace on each side; the 18 text slots render holes through the template engine, where such a hole renders nothing and the run reports success. It is now refused by the node contract, at registration and by objectstack validate, with the remedy its single-brace spelling gets; a stored flow carrying one is skipped at boot with a warn naming it. No D2 conversion exists: what the author meant the hole to read is not in the flow, and the template engine binds no new variable to answer it."
2215+
},
22092216
{
22102217
"surface": "the record and previous roots a record-change flow receives — a password or secret field, and an internal field, of the triggering record, on every object",
22112218
"replacement": "read a credential through a privileged binder — the flow credential channel for an http node's signing secret, or a privileged server-side read such as the engine's resolveSecretField — never off `record` or `previous`; on those roots a set credential-class field now reads as the mask `SECRET_MASK`, an unset one as null, and an `internal: true` field is absent",
@@ -3508,13 +3515,6 @@
35083515
"toMajor": 18,
35093516
"rationale": "ADR-0049 enforce-or-remove, the enforce arm: the four keys stay declared (they are the selection of every non-joined report), and the one report type that never reads them now refuses them. A `joined` report selects nothing itself, and the refinement already said so for `order` alone — it refused a container `order` with a pointer onto `blocks[]` while the four selection keys beside it parsed green. Measured at this repo's `.objectui-sha` pin `f8a9d0fb0596f4521076628e2bbfe27e6ce67d52`: `DatasetReportRenderer`'s joined branch (`DatasetReportRenderer.tsx:1462`) reads `blocks`, plus the container `runtimeFilter` and `drilldown` resolved above it, and returns before the top-level reads of `columns` / `dataset` / `rows` / `values` begin (line 1529 onward) — so each was accepted by the metadata layer and dropped by the renderer without a word. The alias tables made it reachable: `fields` / `measures` / `metrics` route to `values`, `groupings` / `groupBy` / `dimensions` to `rows`, and `objectName` / `object` / `dataSet` / `source` to `dataset`, on a joined report as on any other. Studio's report inspector hides the top-level binding for a joined report (`ReportDefaultInspector.tsx:328`) but its type picker patches only `type`, so a report bound first and switched to `joined` second carries the keys invisibly. An empty list is NOT refused: it selects nothing, which is what a joined container selects — the container `order` refusal's own threshold. Ships at once, no deprecation window: there is no window in which a key the renderer never reads does anything."
35103517
},
3511-
{
3512-
"surface": "A list view whose type is chart and whose effective chart binding names no dataset: no chart block and no options.chart bag, or, on a flattened view overlay saved through the metadata write door, an options.chart bag missing its dataset or its values while no chart block replaces it. Judged at every list-view door: views[].list and views[].listViews, objects[].listViews, a view item config, and the flattened list overlay.",
3513-
"replacement": "Bind the chart: declare a top-level `chart` block naming the ADR-0021 `dataset` to plot and at least one of its measures in `values` (`dimensions`, the X / group axis, stays optional, and `chartType` defaults to `bar`). A view whose only binding is the legacy `options.chart` bag completes the bag with `dataset` and `values`, or, preferred, moves the binding to the top-level `chart` block, which replaces the bag whole. A view that is not meant to be a chart takes another `type`.",
3514-
"migrationId": "view-chart-binding-dataset-required",
3515-
"toMajor": 18,
3516-
"rationale": "ADR-0021 single form, enforced (ADR-0049 enforce-or-remove, the enforce arm; ADR-0078, a view that renders nothing is refused rather than warned). A chart list view plots only the dataset its effective binding names, and the renderer reads that binding as the `chart` block, else the `options.chart` bag, the block replacing the bag whole (objectui plugin-list `ListView`, `resolveListChartBinding`, at this repo's `.objectui-sha` pin and at objectui main alike). The authoring `chart` block already required `dataset` and `values`, but a view with no block at all, or with only the bag, never met that schema. Measured on `origin/main` at `e148ca98`: the flattened overlay member accepted `type: 'chart'` with no block and an `options.chart` bag holding only `chartType`, and both authoring doors accepted the block-less view. What such a view rendered was a dead screen: at the pin the renderer fabricated a binding nobody wrote (an aggregate over a field named name and a measure named value), and objectui has since retired that floor, after which the chart component refuses on screen. Now refused at the view's own path, `chart`, or at `options.chart.dataset` / `options.chart.values`, with the binding to declare. Ships at once, no grace window and no dual spelling (2026-08-27 maintainer ruling 「短期不考虑渐进」). Not convertible: only the author knows which dataset a chart was meant to show."
3517-
},
35183518
{
35193519
"surface": "ui.ViewFilterRule with NO value on an operator that takes one — the value key omitted, or present and undefined, on equals, not_equals, contains, not_contains, icontains, starts_with, ends_with, greater_than, less_than, greater_than_or_equal, less_than_or_equal, before or after (an alias spelling of any of them included), on every carrier of ViewFilterRuleSchema",
35203520
"replacement": "the value the rule compares against — value: \"open\" on equals, value: \"2026-01-01\" on after. A rule that meant \"the field has no value\" becomes one of the four operators that take none — is_empty / is_not_empty / is_null / is_not_null — which read their direction from their name and still parse with or without a value. A rule that was an unfinished row is deleted. The list operators (in / not_in) and the range operator (between) refused an absent value before this change and still do, in their own words",
@@ -5816,6 +5816,13 @@
58165816
"toMajor": 18,
58175817
"rationale": "ADR-0032 Decision 3 fixes one template delimiter, double braces, and deletes the single brace: it collides with CEL map literals, and an author who meets both dialects in one flow mixes them. The 17.x interpolator and the template engine render the same text for a path holding a string, a number, a boolean, null, an absent key or variable, an ISO date string, an object or an array, but not for every value — a Date rendered JSON-quoted under the interpolator and as its ISO text under the engine, and a screen title, screen description or end message that was one token holding an object, an array or a Date rendered String(value) — so no conversion is lossless (ADR-0087 D2) and none is applied. Arithmetic, function calls, the date macros and the run-user paths have no hole spelling: a hole is a path with a formatter, never logic. A flow carrying a single-brace token in a text slot is refused at registration, by objectstack validate and by the node contract; a stored flow carrying one is skipped at boot with a warn naming it."
58185818
},
5819+
{
5820+
"surface": "flows[].nodes[].config of a notify node (title, message), a screen node (title, description) and an end node (message) — a string, or the source of a template envelope, carrying a double-brace hole whose root is a dollar-named variable the flow engine does not bind, such as {{ $User.Id }}",
5821+
"replacement": "a variable the run has, written as a hole. The run user is computed first, with an assignment node whose value slot still reads the run-user path (assignments: { by: '{$User.Id}' }), then written as {{ by }}. A variable the flow binds itself (a declared variable, an assignment target, an outputVariable, a try_catch errorVariable) is named without the dollar sign and written as {{ caught.message }}. The engine's own variables stay holes: {{ $error.message }}, {{ $record.name }}, {{ $runId }}, {{ $flowName }}, {{ $flowLabel }}, and a flat-graph loop's {{ $loopItems }} / {{ $loopIndex }}",
5822+
"migrationId": "flow-text-slot-unbound-dollar-root-refused",
5823+
"toMajor": 18,
5824+
"rationale": "The dollar-named variables are the flow engine's own: it binds $record, $runId, $flowName, $flowLabel and $error, and a flat-graph loop binds $loopItems and $loopIndex. A hole over any other dollar name answers to no variable — {{ $User.Id }} looks like the run user and is not one, since the run user has no hole spelling. In 17.x the slot was a plain string read by the single-brace interpolator, which substituted the inner token and left a literal brace on each side; the 18 text slots render holes through the template engine, where such a hole renders nothing and the run reports success. It is now refused by the node contract, at registration and by objectstack validate, with the remedy its single-brace spelling gets; a stored flow carrying one is skipped at boot with a warn naming it. No D2 conversion exists: what the author meant the hole to read is not in the flow, and the template engine binds no new variable to answer it."
5825+
},
58195826
{
58205827
"surface": "the record and previous roots a record-change flow receives — a password or secret field, and an internal field, of the triggering record, on every object",
58215828
"replacement": "read a credential through a privileged binder — the flow credential channel for an http node's signing secret, or a privileged server-side read such as the engine's resolveSecretField — never off `record` or `previous`; on those roots a set credential-class field now reads as the mask `SECRET_MASK`, an unset one as null, and an `internal: true` field is absent",
@@ -7118,13 +7125,6 @@
71187125
"toMajor": 18,
71197126
"rationale": "ADR-0049 enforce-or-remove, the enforce arm: the four keys stay declared (they are the selection of every non-joined report), and the one report type that never reads them now refuses them. A `joined` report selects nothing itself, and the refinement already said so for `order` alone — it refused a container `order` with a pointer onto `blocks[]` while the four selection keys beside it parsed green. Measured at this repo's `.objectui-sha` pin `f8a9d0fb0596f4521076628e2bbfe27e6ce67d52`: `DatasetReportRenderer`'s joined branch (`DatasetReportRenderer.tsx:1462`) reads `blocks`, plus the container `runtimeFilter` and `drilldown` resolved above it, and returns before the top-level reads of `columns` / `dataset` / `rows` / `values` begin (line 1529 onward) — so each was accepted by the metadata layer and dropped by the renderer without a word. The alias tables made it reachable: `fields` / `measures` / `metrics` route to `values`, `groupings` / `groupBy` / `dimensions` to `rows`, and `objectName` / `object` / `dataSet` / `source` to `dataset`, on a joined report as on any other. Studio's report inspector hides the top-level binding for a joined report (`ReportDefaultInspector.tsx:328`) but its type picker patches only `type`, so a report bound first and switched to `joined` second carries the keys invisibly. An empty list is NOT refused: it selects nothing, which is what a joined container selects — the container `order` refusal's own threshold. Ships at once, no deprecation window: there is no window in which a key the renderer never reads does anything."
71207127
},
7121-
{
7122-
"surface": "A list view whose type is chart and whose effective chart binding names no dataset: no chart block and no options.chart bag, or, on a flattened view overlay saved through the metadata write door, an options.chart bag missing its dataset or its values while no chart block replaces it. Judged at every list-view door: views[].list and views[].listViews, objects[].listViews, a view item config, and the flattened list overlay.",
7123-
"replacement": "Bind the chart: declare a top-level `chart` block naming the ADR-0021 `dataset` to plot and at least one of its measures in `values` (`dimensions`, the X / group axis, stays optional, and `chartType` defaults to `bar`). A view whose only binding is the legacy `options.chart` bag completes the bag with `dataset` and `values`, or, preferred, moves the binding to the top-level `chart` block, which replaces the bag whole. A view that is not meant to be a chart takes another `type`.",
7124-
"migrationId": "view-chart-binding-dataset-required",
7125-
"toMajor": 18,
7126-
"rationale": "ADR-0021 single form, enforced (ADR-0049 enforce-or-remove, the enforce arm; ADR-0078, a view that renders nothing is refused rather than warned). A chart list view plots only the dataset its effective binding names, and the renderer reads that binding as the `chart` block, else the `options.chart` bag, the block replacing the bag whole (objectui plugin-list `ListView`, `resolveListChartBinding`, at this repo's `.objectui-sha` pin and at objectui main alike). The authoring `chart` block already required `dataset` and `values`, but a view with no block at all, or with only the bag, never met that schema. Measured on `origin/main` at `e148ca98`: the flattened overlay member accepted `type: 'chart'` with no block and an `options.chart` bag holding only `chartType`, and both authoring doors accepted the block-less view. What such a view rendered was a dead screen: at the pin the renderer fabricated a binding nobody wrote (an aggregate over a field named name and a measure named value), and objectui has since retired that floor, after which the chart component refuses on screen. Now refused at the view's own path, `chart`, or at `options.chart.dataset` / `options.chart.values`, with the binding to declare. Ships at once, no grace window and no dual spelling (2026-08-27 maintainer ruling 「短期不考虑渐进」). Not convertible: only the author knows which dataset a chart was meant to show."
7127-
},
71287128
{
71297129
"surface": "ui.ViewFilterRule with NO value on an operator that takes one — the value key omitted, or present and undefined, on equals, not_equals, contains, not_contains, icontains, starts_with, ends_with, greater_than, less_than, greater_than_or_equal, less_than_or_equal, before or after (an alias spelling of any of them included), on every carrier of ViewFilterRuleSchema",
71307130
"replacement": "the value the rule compares against — value: \"open\" on equals, value: \"2026-01-01\" on after. A rule that meant \"the field has no value\" becomes one of the four operators that take none — is_empty / is_not_empty / is_null / is_not_null — which read their direction from their name and still parse with or without a value. A rule that was an unfinished row is deleted. The list operators (in / not_in) and the range operator (between) refused an absent value before this change and still do, in their own words",

0 commit comments

Comments
 (0)