Skip to content

Commit d0a5ceb

Browse files
fix(spec): 内联形状摘要只展开一层,四条下钻路径共用同一条深度预算 (#6374) (#6572)
* fix(spec): 内联形状摘要只展开一层,四条下钻路径共用同一条深度预算 (#6374) `format-type.ts` 一直只展开一层 `{ … }` 形状,但这条预算写在键循环的三元 表达式里,于是只有直接对象子节点受它约束。数组元素、`Record` 的值、联合的 变体这三条路径都会重新进入对象分支而预算不在作用域内,单元格宽度于是等于 每层键数 × 变体数 × 每个形状宽度,一层一层乘上去。`ui/page.mdx` 的 `Page.slots` 因此是 1538 字符 —— 而且是在 #5340 的枚举省略已在该格生效 8 次之后的宽度。 新常量 `SHAPE_DEPTH_LIMIT` 把同一条预算移到对象分支本身,四条路径都要过它。 阈值 1 不是选出来的,是取回来的:它就是直接子节点路径上一直生效的那个值。 全语料实测(215 页 / 8499 个类型单元格),任何大于 1 的取值都比不改还差, 因为提高上限必然放松那条本来就是 1 的路径。 深度上限 | 改前 | 1 | 2 | 3 | 4 >200 字符 | 121 | 42 | 173 | 191 | 191 >400 字符 | 9 | 1 | 19 | 35 | 35 >900 字符 | 1 | 0 | 1 | 1 | 1 最宽 | 1538 | 656 | 1538 | 1538 | 1538 改后仅剩的 >400 单元格是 `PageComponent.type`(656),落在联合变体里的顶层 词表,#6225 有意不收,且不含任何嵌套形状 —— 形状深度带来的宽度已经从语料 里消失。 `content/docs/references/**` 55 页由 `gen:schema && gen:docs` 重生成, 无一处手改。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o * chore(spec): 在合并后的树上重生成参考文档 (#6374) `origin/main` 上有三个提交改了 schema 并各自重生成了 `content/docs/references/**`(#6512 i18n 标签契约、#6540 capability 注册、 #6526 ADR-0049 退役 sweep),与本分支的重生成在 13 个文件上相交。 `.gitattributes` 的 `merge=os-regen` 驱动按设计**没有做文本合并**,而是把这 13 个文件标记为「必须在合并后的树上重生成」—— 否则会落地 #6224 那种「零冲突 却陈旧」的组合。 本提交就是那次重生成:`gen:schema && gen:docs` 跑在合并后的树上,12 个文件 被修正,`check-regen-pending` 标记已清除。无一处手改 `.mdx`; `packages/spec/scripts/lib/format-type.ts` 与其测试**逐字未动**(main 上没有 任何提交碰过这两个文件)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 466c503 commit d0a5ceb

58 files changed

Lines changed: 565 additions & 182 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
fix(spec): 参考文档的内联形状摘要只展开一层,不再沿数组/Record/联合无预算下钻 (#6374)
6+
7+
`content/docs/references/**` 的类型单元格里,最宽的一格是 `ui/page.mdx`
8+
`Page.slots`,**1538 字符** —— 而且是在 #5340 的枚举省略已经在这一格生效 8 次
9+
之后的宽度。本次修完,这一格是 **122 字符**
10+
11+
## 机制
12+
13+
`format-type.ts` 一直只展开**一层** `{ … }` 形状:再往下的对象打印 `object`
14+
但这条预算写在键循环的三元表达式里,于是只有**直接对象子节点**受它约束。数组元素
15+
(`{ … }[]`)、`Record` 的值(`Record<string, { … }>`)、联合的变体
16+
(`{ … } | { … }[]`)这三条路径都会重新进入对象分支,而预算不在作用域内 ——
17+
单元格宽度于是等于「每层键数 × 变体数 × 每个形状的宽度」,一层一层乘上去。
18+
19+
同一个形状在同一个阅读深度上,**印全还是印 `object`,取决于作者有没有把它包在数组
20+
** —— 这是关于 Zod 写法的事实,不是关于读者怎么读的事实,和 #6225 拆掉的那种
21+
不对称完全同类。
22+
23+
新常量 `SHAPE_DEPTH_LIMIT` 把同一条预算移到对象分支本身,四条下钻路径都要过它。
24+
**阈值 1 不是选出来的,是取回来的** —— 它就是直接子节点路径上一直生效的那个值;
25+
全语料实测,任何大于 1 的取值都比不改还差(>200 字符的单元格 121 → 173+,因为
26+
提高上限必然放松那条本来就是 1 的路径)。
27+
28+
## 读者看到的变化(全语料 215 页 / 8499 个类型单元格)
29+
30+
| | 修改前 | 修改后 |
31+
|---|---|---|
32+
| >200 字符 | 121 | **42** |
33+
| >400 字符 | 9 | **1** |
34+
| >900 字符 | 1 | **0** |
35+
| p95 / p99 | 145 / 229 | **124 / 180** |
36+
| 最宽单元格 | 1538 | **656** |
37+
38+
修改后仅剩的那个 >400 单元格是 `ui/page.mdx``PageComponent.type`(656),
39+
一个落在联合变体里的顶层词表 —— #6225 有意不收它,它也**不含任何嵌套形状**
40+
也就是说:**由形状深度带来的宽度已经从语料里消失了**
41+
42+
## 省略掉的信息去哪了
43+
44+
`object` 不是截断:它对键**什么都不声称**,所以不像前缀那样会被误读成完整列表 ——
45+
这正是 #5340 定下的原则用在形状上而不是枚举成员上。它也不是这些表格里的新省略
46+
风格:嵌套形状本来就一直印 `object`。完整形状仍在原处 —— 生成器为它出页时是它
47+
自己的 `## Schema` 一节,任何情况下都在 `json-schema/` 里。
48+
49+
#5340 / #6226 的两个标记都还活着,只是有些出现位置被上游的深度预算吸收了:
50+
枚举标记 178 → 156,变体标记 16 → 9。#6226 的旗舰样本 `App.navigation` 在深度 0,
51+
逐字未变。
52+
53+
⛔ 所有 `.mdx` 均由 `gen:schema && gen:docs` 重生成,无一处手改。

content/docs/references/ai/conversation.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ const result = CodeContentSchema.parse(data);
118118
| **context** | `{ sessionId: string; userId?: string; agentId?: string; object?: string; … }` || |
119119
| **modelId** | `string` | optional | AI model ID |
120120
| **tokenBudget** | `{ maxTokens: integer; maxPromptTokens?: integer; maxCompletionTokens?: integer; reserveTokens: integer; … }` || |
121-
| **messages** | `{ id: string; timestamp: string; role: Enum<'system' \| 'user' \| 'assistant' \| 'function' \| 'tool'>; content: ({ type: 'text'; text: string; metadata?: Record<string, any> } \| { type: 'image'; imageUrl: string; detail: Enum<'low' \| 'high' \| 'auto'>; metadata?: Record<string, any> } \| { type: 'file'; fileUrl: string; mimeType: string; fileName?: string; … } \| { type: 'code'; text: string; language: string; metadata?: Record<string, any> })[]; … }[]` || |
121+
| **messages** | `{ id: string; timestamp: string; role: Enum<'system' \| 'user' \| 'assistant' \| 'function' \| 'tool'>; content: (object \| object \| object \| object)[]; … }[]` || |
122122
| **tokens** | `{ promptTokens: integer; completionTokens: integer; totalTokens: integer; budgetLimit: integer; … }` | optional | |
123123
| **totalTokens** | `{ promptTokens: integer; completionTokens: integer; totalTokens: integer }` | optional | Total tokens across all messages |
124124
| **totalCost** | `number` | optional | Total cost for this session in USD |

content/docs/references/ai/model-registry.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ const result = ModelCapabilitySchema.parse(data);
120120
| :--- | :--- | :--- | :--- |
121121
| **name** | `string` || Registry name |
122122
| **models** | `Record<string, { model: object; status?: Enum<'active' \| 'deprecated' \| 'experimental' \| 'disabled'>; priority?: integer; fallbackModels?: string[]; … }>` || Model entries by ID |
123-
| **promptTemplates** | `Record<string, { id: string; name: string; label: string; system?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }>` | optional | Prompt templates by name |
123+
| **promptTemplates** | `Record<string, { id: string; name: string; label: string; system?: string \| object; … }>` | optional | Prompt templates by name |
124124
| **defaultModel** | `string` | optional | Default model ID |
125125
| **enableAutoFallback** | `boolean` | optional | Auto-fallback on errors |
126126

content/docs/references/ai/solution-blueprint.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,10 @@ const result = BlueprintAppSchema.parse(data);
226226
| **summary** | `string` | optional | One-line description of the proposed solution |
227227
| **assumptions** | `string[]` || Design assumptions made from the underspecified goal |
228228
| **questions** | `string[]` | optional | At most 1-2 structure-deciding questions to confirm before building |
229-
| **objects** | `{ name: string; label?: string; description?: string; fields: { name: string; label?: string; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; required?: boolean; … }[]; … }[]` || Objects (tables) to create |
229+
| **objects** | `{ name: string; label?: string; description?: string; fields: object[]; … }[]` || Objects (tables) to create |
230230
| **views** | `{ object: string; name: string; label?: string; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'>; … }[]` | optional | Views to create |
231-
| **dashboards** | `{ name: string; label?: string; widgets?: { id: string; title?: string; object?: string; chart?: Enum<'metric' \| 'bar' \| 'line' \| 'pie' \| 'table'>; … }[] }[]` | optional | Dashboards to create |
232-
| **app** | `{ name: string; label?: string; icon?: string; nav?: { type: Enum<'object' \| 'dashboard'>; target: string; label?: string; icon?: string }[] }` | optional | The navigation shell (app) that surfaces the created objects/dashboards to end users |
231+
| **dashboards** | `{ name: string; label?: string; widgets?: object[] }[]` | optional | Dashboards to create |
232+
| **app** | `{ name: string; label?: string; icon?: string; nav?: object[] }` | optional | The navigation shell (app) that surfaces the created objects/dashboards to end users |
233233
| **seedData** | `{ object: string; records: Record<string, any>[] }[]` | optional | Suggested seed data (reported, not auto-applied in Phase C) |
234234

235235

@@ -244,10 +244,10 @@ const result = BlueprintAppSchema.parse(data);
244244
| **summary** | `string` || One-line description of the proposed solution |
245245
| **assumptions** | `string[]` || Design assumptions made from the underspecified goal |
246246
| **questions** | `string[] \| null` || At most 1-2 structure-deciding questions to confirm before building, or null |
247-
| **objects** | `{ name: string; label: string \| null; description: string \| null; fields: { name: string; label: string \| null; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; required: boolean \| null; … }[] }[]` || Objects (tables) to create |
247+
| **objects** | `{ name: string; label: string \| null; description: string \| null; fields: object[] }[]` || Objects (tables) to create |
248248
| **views** | `{ object: string; name: string; label: string \| null; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'> \| null; … }[] \| null` || Views to create, or null |
249-
| **dashboards** | `{ name: string; label: string \| null; widgets: { id: string; title: string \| null; object: string \| null; chart: Enum<'metric' \| 'bar' \| 'line' \| 'pie' \| 'table'> \| null; … }[] \| null }[] \| null` || Dashboards to create, or null |
250-
| **app** | `{ name: string; label: string \| null; icon: string \| null; nav: { type: Enum<'object' \| 'dashboard'>; target: string; label: string \| null; icon: string \| null }[] \| null } \| null` || The navigation shell (app) that surfaces the created objects/dashboards, or null |
249+
| **dashboards** | `{ name: string; label: string \| null; widgets: object[] \| null }[] \| null` || Dashboards to create, or null |
250+
| **app** | `{ name: string; label: string \| null; icon: string \| null; nav: object[] \| null } \| null` || The navigation shell (app) that surfaces the created objects/dashboards, or null |
251251

252252

253253
---

content/docs/references/api/analytics.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const result = AnalyticsEndpoint.parse(data);
4646
| **success** | `boolean` || Operation success status |
4747
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +257 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
4848
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
49-
| **data** | `{ cubes: { name: string; title?: string; description?: string; sql: string; … }[] }` || |
49+
| **data** | `{ cubes: object[] }` || |
5050

5151

5252
---
@@ -81,7 +81,7 @@ const result = AnalyticsEndpoint.parse(data);
8181
| **success** | `boolean` || Operation success status |
8282
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +257 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
8383
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
84-
| **data** | `{ rows: Record<string, any>[]; fields: { name: string; type: string }[]; sql?: string }` || |
84+
| **data** | `{ rows: Record<string, any>[]; fields: object[]; sql?: string }` || |
8585

8686

8787
---

content/docs/references/api/automation-api.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const result = AutomationApiErrorCode.parse(data);
9696
| **type** | `Enum<'autolaunched' \| 'record_change' \| 'schedule' \| 'screen' \| 'api'>` || Flow type |
9797
| **variables** | `{ name: string; type: string; isInput?: boolean; isOutput?: boolean }[]` | optional | Flow variables |
9898
| **nodes** | `{ id: string; type: string; label: string; config?: Record<string, any>; … }[]` || Flow nodes |
99-
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` || Flow connections |
99+
| **edges** | `{ id: string; source: string; target: string; condition?: string \| object; … }[]` || Flow connections |
100100
| **active** | `never` | optional | [REMOVED] `flow.active` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it never had an effect: the engine arms flows from `status`, and `active: false` did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use `status: 'obsolete'` (or 'invalid') to unbind and disable a flow, `status: 'active'` to arm it. |
101101
| **runAs** | `Enum<'system' \| 'user'>` | optional | Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user. |
102102
| **errorHandling** | `{ strategy?: Enum<'fail' \| 'retry' \| 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … }` | optional | Flow-level error handling configuration |
@@ -243,7 +243,7 @@ const result = AutomationApiErrorCode.parse(data);
243243
| **success** | `boolean` || Operation success status |
244244
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +257 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
245245
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
246-
| **data** | `{ flows: { name: string; label: string; type: string; status: string; … }[]; total?: integer; nextCursor?: string; hasMore: boolean }` || |
246+
| **data** | `{ flows: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` || |
247247

248248

249249
---
@@ -271,7 +271,7 @@ const result = AutomationApiErrorCode.parse(data);
271271
| **success** | `boolean` || Operation success status |
272272
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +257 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
273273
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
274-
| **data** | `{ runs: { id: string; flowName: string; flowVersion?: integer; status: Enum<'pending' \| 'running' \| 'paused' \| 'completed' \| 'failed' \| 'cancelled' \| … +2 more>; … }[]; total?: integer; nextCursor?: string; hasMore: boolean }` || |
274+
| **data** | `{ runs: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` || |
275275

276276

277277
---

content/docs/references/api/batch.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const result = BatchConfigSchema.parse(data);
128128
| **total** | `number` || Total number of records in the batch |
129129
| **succeeded** | `number` || Number of records that succeeded |
130130
| **failed** | `number` || Number of records that failed |
131-
| **results** | `{ id?: string; success: boolean; errors?: { code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +257 more>; message: string; category?: string; httpStatus?: integer; … }[]; data?: Record<string, any>; … }[]` || Detailed results for each record |
131+
| **results** | `{ id?: string; success: boolean; errors?: object[]; data?: Record<string, any>; … }[]` || Detailed results for each record |
132132

133133

134134
---

content/docs/references/api/contract.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ const result = ApiErrorSchema.parse(data);
348348
| **success** | `boolean` || Operation success status |
349349
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +257 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
350350
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
351-
| **data** | `{ id?: string; success: boolean; errors?: { code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +257 more>; message: string; category?: string; httpStatus?: integer; … }[]; index?: number; … }[]` || Results for each item in the batch |
351+
| **data** | `{ id?: string; success: boolean; errors?: object[]; index?: number; … }[]` || Results for each item in the batch |
352352

353353

354354
---
@@ -417,7 +417,7 @@ const result = ApiErrorSchema.parse(data);
417417
| **having** | `any` | optional | HAVING — filter over the AGGREGATED rows (aggregation aliases + groupBy projections); applied engine-side after aggregation |
418418
| **windowFunctions** | `never` | optional | [REMOVED] `query.windowFunctions` was removed in @objectstack/spec 17 (#4286, ADR-0049) — `find()` never applied it: no engine or driver read the key on the query path, so every OVER clause it declared was silently dropped. Delete the key. Window functions are a SQL-driver capability behind `SqlDriver.findWithWindowFunctions(object, query)` (embedder-level; not on the `IDataDriver` contract or the REST surface); request-level analytics are `aggregations` + `groupBy`. |
419419
| **distinct** | `never` | optional | [REMOVED] `query.distinct` was removed in @objectstack/spec 17 (#4286, ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded `total`/`hasMore` to a page-local estimate while still returning duplicate rows. Delete the key; `QueryBuilder.distinct()` was removed with it, and the count suppression is gone (`total` is truthful again). For unique values of one column use the SQL/memory drivers' `distinct(object, field)` door; for unique combinations, `groupBy`; for a deduplicated count, the `count_distinct` aggregation. |
420-
| **expand** | `Record<string, { object: string; fields?: string[]; where?: any; search?: string \| { query: string; fields?: string[]; fuzzy: boolean; operator: Enum<'and' \| 'or'>; … }; … }>` | optional | Recursive relation loading map. Keys are lookup/master_detail field names; values are nested QueryAST objects that control select (`fields`) and filter (`where`, AND-merged with the batch $in), plus further expansion on the related object. The engine resolves expand via batch $in queries (driver-agnostic) with a default max depth of 3; per-parent `limit`/`offset`/`orderBy` are NOT applied on this path. |
420+
| **expand** | `Record<string, { object: string; fields?: string[]; where?: any; search?: string \| object; … }>` | optional | Recursive relation loading map. Keys are lookup/master_detail field names; values are nested QueryAST objects that control select (`fields`) and filter (`where`, AND-merged with the batch $in), plus further expansion on the related object. The engine resolves expand via batch $in queries (driver-agnostic) with a default max depth of 3; per-parent `limit`/`offset`/`orderBy` are NOT applied on this path. |
421421

422422

423423
---

0 commit comments

Comments
 (0)