You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(spec): a solution blueprint can declare a roll-up, including a conditional one
`BlueprintFieldSchema` had no slot for `summaryOperations`, so a `summary`
field could only ever be proposed as a bare shell. Two consequences, both
observed live (cloud#970):
1. `z.object` STRIPS unknown keys, so a blueprint that correctly declared
`{ type:'summary', summaryOperations:{ object:'task', function:'count',
filter:{ status:'completed' } } }` silently lost that config at the parse
waist and materialized runtime-dead.
2. The strict structured-output mirror could not express it either, so the
design step — the one place the aggregation is actually known ("已完成任务数
counts only completed tasks") — had nowhere to put it.
This matters beyond tidiness: the engine recomputes roll-ups only when a CHILD
row is written, so a `summaryOperations` bolted on AFTER a build's sample data
loaded leaves the parent values empty until someone edits a child. The design
phase is the only place it can be declared in time.
Adds `BlueprintSummaryOperationsSchema` (object / function / field /
relationshipField, plus the predicate) to both the lenient schema and the strict
mirror. Strict mode cannot represent the canonical `filter` map (open-ended
`additionalProperties`), so the predicate is a flat `conditions` array of
`{field, op, value}` — the same shape a dashboard widget's `condition` already
uses; the lenient schema also accepts a real `filter` map for a hand-authored
blueprint. `BlueprintWidgetConditionSchema` becomes an alias of the now-shared
`BlueprintConditionSchema`.
Also makes the lenient schema's top-level `summary` OPTIONAL. It is a purely
descriptive one-liner with no structural role, but `apply_blueprint` parses the
model's re-emitted blueprint against this schema — so omitting it rejected the
whole build with `path: "summary"`, which the model read as "the summary FIELDS
are invalid" and repaired by DELETING the roll-up fields. The strict design
contract still requires it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(spec): record the new blueprint keys in the authorable surface + changeset
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(spec): regenerate the solution-blueprint reference page
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(spec): record the 4 added ai/ exports in the API-surface snapshot
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|**value**|`number \| string \| boolean`| ✅ | Comparison value — for a select field use its option VALUE, never its label (e.g. "completed", not "已完成") |
69
+
70
+
58
71
---
59
72
60
73
## BlueprintDashboard
@@ -82,6 +95,7 @@ const result = BlueprintApp.parse(data);
82
95
|**required**|`boolean`| optional | Whether the field is required |
83
96
|**reference**|`string`| optional | Target object name for lookup / master_detail relationship fields |
84
97
|**options**|`{ label: string; value: string }[]`| optional | Choices for select / multiselect / radio fields |
98
+
|**summaryOperations**|`{ object: string; function: Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max'>; field?: string; relationshipField?: string; … }`| optional | REQUIRED when `type` is "summary" (a roll-up of child records: 任务总数 / 报名人数 / 合计金额 / 已完成任务数). Names the child object, the aggregation, and — for a qualified count/sum — the condition. A "summary" field without it materializes runtime-dead. |
85
99
86
100
87
101
---
@@ -125,6 +139,22 @@ const result = BlueprintApp.parse(data);
125
139
|**records**|`Record<string, any>[]`| ✅ | Rows to seed |
126
140
127
141
142
+
---
143
+
144
+
## BlueprintSummaryOperations
145
+
146
+
### Properties
147
+
148
+
| Property | Type | Required | Description |
149
+
| :--- | :--- | :--- | :--- |
150
+
|**object**|`string`| ✅ | The CHILD object whose records are aggregated (snake_case). It must carry a lookup / master_detail field pointing back at this parent, or the roll-up never computes. |
|**field**|`string`| optional | Numeric field on the CHILD object to aggregate. Ignored for "count" (pass "id" or omit it). |
153
+
|**relationshipField**|`string`| optional | The child FK field pointing back at this parent. Auto-detected from the child's lookup / master_detail; set it only when the child has more than one reference to this parent. |
154
+
|**conditions**|`{ field: string; op: Enum<'lt' \| 'lte' \| 'gt' \| 'gte' \| 'eq' \| 'ne'>; value: number \| string \| boolean }[]`| optional | CONDITIONAL roll-up: aggregate only the child rows matching these comparisons (ANDed). REQUIRED whenever the field name carries a qualifier — "已完成任务数 / 已收货金额 / 待处理工单数", any 已X / 未X / `<某状态>`的 count-or-sum → e.g. [`{ field: "status", op: "eq", value: "completed" }`]. WITHOUT it the roll-up silently counts EVERY child and reports a plausible-looking WRONG number, which is worse than a visible 0. |
155
+
|**filter**|`any`| optional | The same predicate as a canonical query filter map (e.g. `{ status: "completed" }`, `{ status: { $in: ["received", "partial"] }` }). Use it when hand-authoring a blueprint; the structured design path uses `conditions` instead. Wins over `conditions` when both are given. |
156
+
157
+
128
158
---
129
159
130
160
## BlueprintView
@@ -149,9 +179,9 @@ const result = BlueprintApp.parse(data);
149
179
150
180
| Property | Type | Required | Description |
151
181
| :--- | :--- | :--- | :--- |
152
-
|**field**|`string`| ✅ | Field on the widget object to filter by (e.g. "stock_quantity", "status") |
182
+
|**field**|`string`| ✅ | Field on the target object to filter by (e.g. "stock_quantity", "status") |
|**value**|`number \| string \| boolean`| ✅ | Comparison value — for a select field use its option VALUE, never its label (e.g. "completed", not "已完成") |
155
185
156
186
157
187
---
@@ -162,7 +192,7 @@ const result = BlueprintApp.parse(data);
162
192
163
193
| Property | Type | Required | Description |
164
194
| :--- | :--- | :--- | :--- |
165
-
|**summary**|`string`|✅| One-line description of the proposed solution |
195
+
|**summary**|`string`|optional| One-line description of the proposed solution |
166
196
|**assumptions**|`string[]`| ✅ | Design assumptions made from the underspecified goal |
167
197
|**questions**|`string[]`| optional | At most 1-2 structure-deciding questions to confirm before building |
0 commit comments