diff --git a/.changeset/inline-shape-depth-budget.md b/.changeset/inline-shape-depth-budget.md new file mode 100644 index 0000000000..468b815871 --- /dev/null +++ b/.changeset/inline-shape-depth-budget.md @@ -0,0 +1,53 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): 参考文档的内联形状摘要只展开一层,不再沿数组/Record/联合无预算下钻 (#6374) + +`content/docs/references/**` 的类型单元格里,最宽的一格是 `ui/page.mdx` 的 +`Page.slots`,**1538 字符** —— 而且是在 #5340 的枚举省略已经在这一格生效 8 次 +之后的宽度。本次修完,这一格是 **122 字符**。 + +## 机制 + +`format-type.ts` 一直只展开**一层** `{ … }` 形状:再往下的对象打印 `object`。 +但这条预算写在键循环的三元表达式里,于是只有**直接对象子节点**受它约束。数组元素 +(`{ … }[]`)、`Record` 的值(`Record`)、联合的变体 +(`{ … } | { … }[]`)这三条路径都会重新进入对象分支,而预算不在作用域内 —— +单元格宽度于是等于「每层键数 × 变体数 × 每个形状的宽度」,一层一层乘上去。 + +同一个形状在同一个阅读深度上,**印全还是印 `object`,取决于作者有没有把它包在数组 +里** —— 这是关于 Zod 写法的事实,不是关于读者怎么读的事实,和 #6225 拆掉的那种 +不对称完全同类。 + +新常量 `SHAPE_DEPTH_LIMIT` 把同一条预算移到对象分支本身,四条下钻路径都要过它。 +**阈值 1 不是选出来的,是取回来的** —— 它就是直接子节点路径上一直生效的那个值; +全语料实测,任何大于 1 的取值都比不改还差(>200 字符的单元格 121 → 173+,因为 +提高上限必然放松那条本来就是 1 的路径)。 + +## 读者看到的变化(全语料 215 页 / 8499 个类型单元格) + +| | 修改前 | 修改后 | +|---|---|---| +| >200 字符 | 121 | **42** | +| >400 字符 | 9 | **1** | +| >900 字符 | 1 | **0** | +| p95 / p99 | 145 / 229 | **124 / 180** | +| 最宽单元格 | 1538 | **656** | + +修改后仅剩的那个 >400 单元格是 `ui/page.mdx` 的 `PageComponent.type`(656), +一个落在联合变体里的顶层词表 —— #6225 有意不收它,它也**不含任何嵌套形状**。 +也就是说:**由形状深度带来的宽度已经从语料里消失了**。 + +## 省略掉的信息去哪了 + +`object` 不是截断:它对键**什么都不声称**,所以不像前缀那样会被误读成完整列表 —— +这正是 #5340 定下的原则用在形状上而不是枚举成员上。它也不是这些表格里的新省略 +风格:嵌套形状本来就一直印 `object`。完整形状仍在原处 —— 生成器为它出页时是它 +自己的 `## Schema` 一节,任何情况下都在 `json-schema/` 里。 + +#5340 / #6226 的两个标记都还活着,只是有些出现位置被上游的深度预算吸收了: +枚举标记 178 → 156,变体标记 16 → 9。#6226 的旗舰样本 `App.navigation` 在深度 0, +逐字未变。 + +⛔ 所有 `.mdx` 均由 `gen:schema && gen:docs` 重生成,无一处手改。 diff --git a/content/docs/references/ai/conversation.mdx b/content/docs/references/ai/conversation.mdx index aa4694814c..8c8cf30326 100644 --- a/content/docs/references/ai/conversation.mdx +++ b/content/docs/references/ai/conversation.mdx @@ -118,7 +118,7 @@ const result = CodeContentSchema.parse(data); | **context** | `{ sessionId: string; userId?: string; agentId?: string; object?: string; … }` | ✅ | | | **modelId** | `string` | optional | AI model ID | | **tokenBudget** | `{ maxTokens: integer; maxPromptTokens?: integer; maxCompletionTokens?: integer; reserveTokens: integer; … }` | ✅ | | -| **messages** | `{ id: string; timestamp: string; role: Enum<'system' \| 'user' \| 'assistant' \| 'function' \| 'tool'>; content: ({ type: 'text'; text: string; metadata?: Record } \| { type: 'image'; imageUrl: string; detail: Enum<'low' \| 'high' \| 'auto'>; metadata?: Record } \| { type: 'file'; fileUrl: string; mimeType: string; fileName?: string; … } \| { type: 'code'; text: string; language: string; metadata?: Record })[]; … }[]` | ✅ | | +| **messages** | `{ id: string; timestamp: string; role: Enum<'system' \| 'user' \| 'assistant' \| 'function' \| 'tool'>; content: (object \| object \| object \| object)[]; … }[]` | ✅ | | | **tokens** | `{ promptTokens: integer; completionTokens: integer; totalTokens: integer; budgetLimit: integer; … }` | optional | | | **totalTokens** | `{ promptTokens: integer; completionTokens: integer; totalTokens: integer }` | optional | Total tokens across all messages | | **totalCost** | `number` | optional | Total cost for this session in USD | diff --git a/content/docs/references/ai/model-registry.mdx b/content/docs/references/ai/model-registry.mdx index 69c1ae3703..c65d9bd738 100644 --- a/content/docs/references/ai/model-registry.mdx +++ b/content/docs/references/ai/model-registry.mdx @@ -120,7 +120,7 @@ const result = ModelCapabilitySchema.parse(data); | :--- | :--- | :--- | :--- | | **name** | `string` | ✅ | Registry name | | **models** | `Record; priority?: integer; fallbackModels?: string[]; … }>` | ✅ | Model entries by ID | -| **promptTemplates** | `Record; source?: string; ast?: any; meta?: object }; … }>` | optional | Prompt templates by name | +| **promptTemplates** | `Record` | optional | Prompt templates by name | | **defaultModel** | `string` | optional | Default model ID | | **enableAutoFallback** | `boolean` | optional | Auto-fallback on errors | diff --git a/content/docs/references/ai/solution-blueprint.mdx b/content/docs/references/ai/solution-blueprint.mdx index 215f659a71..d3f64b4aaf 100644 --- a/content/docs/references/ai/solution-blueprint.mdx +++ b/content/docs/references/ai/solution-blueprint.mdx @@ -226,10 +226,10 @@ const result = BlueprintAppSchema.parse(data); | **summary** | `string` | optional | One-line description of the proposed solution | | **assumptions** | `string[]` | ✅ | Design assumptions made from the underspecified goal | | **questions** | `string[]` | optional | At most 1-2 structure-deciding questions to confirm before building | -| **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 | +| **objects** | `{ name: string; label?: string; description?: string; fields: object[]; … }[]` | ✅ | Objects (tables) to create | | **views** | `{ object: string; name: string; label?: string; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'>; … }[]` | optional | Views to create | -| **dashboards** | `{ name: string; label?: string; widgets?: { id: string; title?: string; object?: string; chart?: Enum<'metric' \| 'bar' \| 'line' \| 'pie' \| 'table'>; … }[] }[]` | optional | Dashboards to create | -| **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 | +| **dashboards** | `{ name: string; label?: string; widgets?: object[] }[]` | optional | Dashboards to create | +| **app** | `{ name: string; label?: string; icon?: string; nav?: object[] }` | optional | The navigation shell (app) that surfaces the created objects/dashboards to end users | | **seedData** | `{ object: string; records: Record[] }[]` | optional | Suggested seed data (reported, not auto-applied in Phase C) | @@ -244,10 +244,10 @@ const result = BlueprintAppSchema.parse(data); | **summary** | `string` | ✅ | One-line description of the proposed solution | | **assumptions** | `string[]` | ✅ | Design assumptions made from the underspecified goal | | **questions** | `string[] \| null` | ✅ | At most 1-2 structure-deciding questions to confirm before building, or null | -| **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 | +| **objects** | `{ name: string; label: string \| null; description: string \| null; fields: object[] }[]` | ✅ | Objects (tables) to create | | **views** | `{ object: string; name: string; label: string \| null; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'> \| null; … }[] \| null` | ✅ | Views to create, or null | -| **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 | -| **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 | +| **dashboards** | `{ name: string; label: string \| null; widgets: object[] \| null }[] \| null` | ✅ | Dashboards to create, or null | +| **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 | --- diff --git a/content/docs/references/api/analytics.mdx b/content/docs/references/api/analytics.mdx index 11e2e4b55f..ad22e17a09 100644 --- a/content/docs/references/api/analytics.mdx +++ b/content/docs/references/api/analytics.mdx @@ -46,7 +46,7 @@ const result = AnalyticsEndpoint.parse(data); | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ cubes: { name: string; title?: string; description?: string; sql: string; … }[] }` | ✅ | | +| **data** | `{ cubes: object[] }` | ✅ | | --- @@ -81,7 +81,7 @@ const result = AnalyticsEndpoint.parse(data); | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ rows: Record[]; fields: { name: string; type: string }[]; sql?: string }` | ✅ | | +| **data** | `{ rows: Record[]; fields: object[]; sql?: string }` | ✅ | | --- diff --git a/content/docs/references/api/automation-api.mdx b/content/docs/references/api/automation-api.mdx index 65c930e9f8..e4e720a9e5 100644 --- a/content/docs/references/api/automation-api.mdx +++ b/content/docs/references/api/automation-api.mdx @@ -96,7 +96,7 @@ const result = AutomationApiErrorCode.parse(data); | **type** | `Enum<'autolaunched' \| 'record_change' \| 'schedule' \| 'screen' \| 'api'>` | ✅ | Flow type | | **variables** | `{ name: string; type: string; isInput?: boolean; isOutput?: boolean }[]` | optional | Flow variables | | **nodes** | `{ id: string; type: string; label: string; config?: Record; … }[]` | ✅ | Flow nodes | -| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | ✅ | Flow connections | +| **edges** | `{ id: string; source: string; target: string; condition?: string \| object; … }[]` | ✅ | Flow connections | | **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. | | **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. | | **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); | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ flows: { name: string; label: string; type: string; status: string; … }[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | | +| **data** | `{ flows: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | | --- @@ -271,7 +271,7 @@ const result = AutomationApiErrorCode.parse(data); | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ runs: { id: string; flowName: string; flowVersion?: integer; status: Enum<'pending' \| 'running' \| 'paused' \| 'completed' \| 'failed' \| 'cancelled' \| … +2 more>; … }[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | | +| **data** | `{ runs: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | | --- diff --git a/content/docs/references/api/batch.mdx b/content/docs/references/api/batch.mdx index 6118e517ee..0a50d71614 100644 --- a/content/docs/references/api/batch.mdx +++ b/content/docs/references/api/batch.mdx @@ -128,7 +128,7 @@ const result = BatchConfigSchema.parse(data); | **total** | `number` | ✅ | Total number of records in the batch | | **succeeded** | `number` | ✅ | Number of records that succeeded | | **failed** | `number` | ✅ | Number of records that failed | -| **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; … }[]` | ✅ | Detailed results for each record | +| **results** | `{ id?: string; success: boolean; errors?: object[]; data?: Record; … }[]` | ✅ | Detailed results for each record | --- diff --git a/content/docs/references/api/contract.mdx b/content/docs/references/api/contract.mdx index 144d9f6f0b..037aeaa013 100644 --- a/content/docs/references/api/contract.mdx +++ b/content/docs/references/api/contract.mdx @@ -348,7 +348,7 @@ const result = ApiErrorSchema.parse(data); | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **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 | +| **data** | `{ id?: string; success: boolean; errors?: object[]; index?: number; … }[]` | ✅ | Results for each item in the batch | --- @@ -417,7 +417,7 @@ const result = ApiErrorSchema.parse(data); | **having** | `any` | optional | HAVING — filter over the AGGREGATED rows (aggregation aliases + groupBy projections); applied engine-side after aggregation | | **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`. | | **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. | -| **expand** | `Record; … }; … }>` | 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. | +| **expand** | `Record` | 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. | --- diff --git a/content/docs/references/api/documentation.mdx b/content/docs/references/api/documentation.mdx index e16fcb07ad..11b2ca18c4 100644 --- a/content/docs/references/api/documentation.mdx +++ b/content/docs/references/api/documentation.mdx @@ -77,11 +77,11 @@ const result = ApiChangelogEntrySchema.parse(data); | **title** | `string` | ✅ | Documentation title | | **version** | `string` | ✅ | API version | | **description** | `string` | optional | API description | -| **servers** | `{ url: string; description?: string; variables?: Record }[]` | ✅ | API server URLs | +| **servers** | `{ url: string; description?: string; variables?: Record }[]` | ✅ | API server URLs | | **ui** | `{ type: Enum<'swagger-ui' \| 'redoc' \| 'rapidoc' \| 'stoplight' \| 'scalar' \| 'graphiql' \| 'postman' \| 'custom'>; path: string; theme: Enum<'light' \| 'dark' \| 'auto'>; enableTryItOut: boolean; … }` | optional | Testing UI configuration | | **generateOpenApi** | `boolean` | ✅ | Generate OpenAPI 3.0 specification | | **generateTestCollections** | `boolean` | ✅ | Generate API test collections | -| **testCollections** | `{ name: string; description?: string; variables: Record; requests: { name: string; description?: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' \| 'HEAD' \| 'OPTIONS'>; url: string; … }[]; … }[]` | ✅ | Predefined test collections | +| **testCollections** | `{ name: string; description?: string; variables: Record; requests: object[]; … }[]` | ✅ | Predefined test collections | | **changelog** | `{ version: string; date: string; changes: object; migrationGuide?: string }[]` | ✅ | API version changelog | | **codeTemplates** | `{ language: string; name: string; template: string; variables?: string[] }[]` | ✅ | Code generation templates | | **termsOfService** | `string` | optional | Terms of service URL | @@ -104,7 +104,7 @@ const result = ApiChangelogEntrySchema.parse(data); | **description** | `string` | optional | Collection description | | **variables** | `Record` | ✅ | Shared variables | | **requests** | `{ name: string; description?: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' \| 'HEAD' \| 'OPTIONS'>; url: string; … }[]` | ✅ | Test requests in this collection | -| **folders** | `{ name: string; description?: string; requests: { name: string; description?: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' \| 'HEAD' \| 'OPTIONS'>; url: string; … }[] }[]` | optional | Request folders for organization | +| **folders** | `{ name: string; description?: string; requests: object[] }[]` | optional | Request folders for organization | --- @@ -186,8 +186,8 @@ const result = ApiChangelogEntrySchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **openApiSpec** | `{ openapi: string; info: object; servers: { url: string; description?: string; variables?: Record }[]; paths: Record; … }` | optional | Generated OpenAPI specification | -| **testCollections** | `{ name: string; description?: string; variables: Record; requests: { name: string; description?: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' \| 'HEAD' \| 'OPTIONS'>; url: string; … }[]; … }[]` | optional | Generated test collections | +| **openApiSpec** | `{ openapi: string; info: object; servers: object[]; paths: Record; … }` | optional | Generated OpenAPI specification | +| **testCollections** | `{ name: string; description?: string; variables: Record; requests: object[]; … }[]` | optional | Generated test collections | | **markdown** | `string` | optional | Generated markdown documentation | | **html** | `string` | optional | Generated HTML documentation | | **generatedAt** | `string` | ✅ | Generation timestamp | @@ -235,7 +235,7 @@ const result = ApiChangelogEntrySchema.parse(data); | :--- | :--- | :--- | :--- | | **openapi** | `string` | ✅ | OpenAPI specification version | | **info** | `{ title: string; version: string; description?: string; termsOfService?: string; … }` | ✅ | API metadata | -| **servers** | `{ url: string; description?: string; variables?: Record }[]` | ✅ | API servers | +| **servers** | `{ url: string; description?: string; variables?: Record }[]` | ✅ | API servers | | **paths** | `Record` | ✅ | API paths and operations | | **components** | `{ schemas?: Record; responses?: Record; parameters?: Record; examples?: Record; … }` | optional | Reusable components | | **security** | `Record[]` | optional | Global security requirements | diff --git a/content/docs/references/api/export.mdx b/content/docs/references/api/export.mdx index 7f5e8f41cb..6575db0e5b 100644 --- a/content/docs/references/api/export.mdx +++ b/content/docs/references/api/export.mdx @@ -490,7 +490,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ jobs: { jobId: string; object: string; status: Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>; format: Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>; … }[]; nextCursor?: string; hasMore: boolean }` | ✅ | | +| **data** | `{ jobs: object[]; nextCursor?: string; hasMore: boolean }` | ✅ | | --- @@ -533,7 +533,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | **fields** | `string[]` | optional | Fields to include | | **filter** | `Record` | optional | Record filter criteria | | **templateId** | `string` | optional | Export template ID for field mappings | -| **schedule** | `{ cronExpression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; timezone?: string }` | ✅ | Schedule timing configuration | +| **schedule** | `{ cronExpression: string \| object; timezone?: string }` | ✅ | Schedule timing configuration | | **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration | @@ -567,7 +567,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | **fields** | `string[]` | optional | Fields to include | | **filter** | `Record` | optional | Record filter criteria | | **templateId** | `string` | optional | Export template ID for field mappings | -| **schedule** | `{ cronExpression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; timezone?: string }` | ✅ | Schedule timing configuration | +| **schedule** | `{ cronExpression: string \| object; timezone?: string }` | ✅ | Schedule timing configuration | | **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration | | **enabled** | `boolean` | optional | Whether the scheduled export is active | | **lastRunAt** | `string` | optional | Last execution timestamp | diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index cae83fbc6e..6c61eff280 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -94,7 +94,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ total: integer; succeeded: integer; failed: integer; errors?: { type: string; name: string; error: string }[] }` | ✅ | Bulk operation result | +| **data** | `{ total: integer; succeeded: integer; failed: integer; errors?: object[] }` | ✅ | Bulk operation result | --- @@ -350,7 +350,7 @@ Metadata query with filtering, sorting, and pagination | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ items: { type: string; name: string; namespace?: string; label?: string; … }[]; total: integer; page: integer; pageSize: integer }` | ✅ | Paginated query result | +| **data** | `{ items: object[]; total: integer; page: integer; pageSize: integer }` | ✅ | Paginated query result | --- @@ -448,7 +448,7 @@ Metadata query with filtering, sorting, and pagination | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ valid: boolean; errors?: { path: string; message: string; code?: string }[]; warnings?: { path: string; message: string }[] }` | ✅ | Validation result | +| **data** | `{ valid: boolean; errors?: object[]; warnings?: object[] }` | ✅ | Validation result | --- diff --git a/content/docs/references/api/odata.mdx b/content/docs/references/api/odata.mdx index 6ec9d90812..dacda928ad 100644 --- a/content/docs/references/api/odata.mdx +++ b/content/docs/references/api/odata.mdx @@ -91,7 +91,7 @@ const result = ODataConfigSchema.parse(data); | :--- | :--- | :--- | :--- | | **enabled** | `boolean` | ✅ | Enable OData API | | **path** | `string` | ✅ | OData endpoint path | -| **metadata** | `{ namespace: string; entityTypes: { name: string; key: string[]; properties: { name: string; type: string; nullable: boolean }[]; navigationProperties?: { name: string; type: string; partner?: string }[] }[]; entitySets: { name: string; entityType: string }[] }` | optional | OData metadata configuration | +| **metadata** | `{ namespace: string; entityTypes: object[]; entitySets: object[] }` | optional | OData metadata configuration | --- @@ -102,7 +102,7 @@ const result = ODataConfigSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **error** | `{ code: string; message: string; target?: string; details?: { code: string; message: string; target?: string }[]; … }` | ✅ | | +| **error** | `{ code: string; message: string; target?: string; details?: object[]; … }` | ✅ | | --- @@ -150,7 +150,7 @@ const result = ODataConfigSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **namespace** | `string` | ✅ | Service namespace | -| **entityTypes** | `{ name: string; key: string[]; properties: { name: string; type: string; nullable: boolean }[]; navigationProperties?: { name: string; type: string; partner?: string }[] }[]` | ✅ | Entity types | +| **entityTypes** | `{ name: string; key: string[]; properties: object[]; navigationProperties?: object[] }[]` | ✅ | Entity types | | **entitySets** | `{ name: string; entityType: string }[]` | ✅ | Entity sets | diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index 869cc6cfbb..3c76c77bf2 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -91,7 +91,7 @@ List installed packages response | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ packages: { manifest: object; status?: Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>; enabled?: boolean; installedAt?: string; … }[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | | +| **data** | `{ packages: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | | --- @@ -145,7 +145,7 @@ Install package response | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ package: object; dependencyResolution?: object; namespaceConflicts?: { type: 'namespace_conflict'; requestedNamespace: string; conflictingPackageId: string; conflictingPackageName: string; … }[]; message?: string }` | ✅ | | +| **data** | `{ package: object; dependencyResolution?: object; namespaceConflicts?: object[]; message?: string }` | ✅ | | --- @@ -252,7 +252,7 @@ Resolve dependencies response | **success** | `boolean` | ✅ | Operation success status | | **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 | | **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ dependencies: { packageId: string; requiredRange: string; resolvedVersion?: string; installedVersion?: string; … }[]; canProceed: boolean; requiredActions: { type: Enum<'install' \| 'upgrade' \| 'confirm_conflict'>; packageId: string; description: string }[]; installOrder: string[]; … }` | ✅ | Dependency resolution result with topological sort | +| **data** | `{ dependencies: object[]; canProceed: boolean; requiredActions: object[]; installOrder: string[]; … }` | ✅ | Dependency resolution result with topological sort | --- diff --git a/content/docs/references/api/protocol.mdx b/content/docs/references/api/protocol.mdx index 129f9966b6..eecf05b587 100644 --- a/content/docs/references/api/protocol.mdx +++ b/content/docs/references/api/protocol.mdx @@ -268,7 +268,7 @@ const result = AiAgentCapabilitiesSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **object** | `string` | ✅ | Object name | -| **request** | `{ operation: Enum<'create' \| 'update' \| 'upsert' \| 'delete'>; records: { id?: string; data?: Record; externalId?: string }[]; options?: object }` | ✅ | Batch operation request | +| **request** | `{ operation: Enum<'create' \| 'update' \| 'upsert' \| 'delete'>; records: object[]; options?: object }` | ✅ | Batch operation request | --- @@ -286,7 +286,7 @@ const result = AiAgentCapabilitiesSchema.parse(data); | **total** | `number` | ✅ | Total number of records in the batch | | **succeeded** | `number` | ✅ | Number of records that succeeded | | **failed** | `number` | ✅ | Number of records that failed | -| **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; … }[]` | ✅ | Detailed results for each record | +| **results** | `{ id?: string; success: boolean; errors?: object[]; data?: Record; … }[]` | ✅ | Detailed results for each record | --- @@ -434,7 +434,7 @@ const result = AiAgentCapabilitiesSchema.parse(data); | **total** | `number` | ✅ | Total number of records in the batch | | **succeeded** | `number` | ✅ | Number of records that succeeded | | **failed** | `number` | ✅ | Number of records that failed | -| **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; … }[]` | ✅ | Detailed results for each record | +| **results** | `{ id?: string; success: boolean; errors?: object[]; data?: Record; … }[]` | ✅ | Detailed results for each record | --- @@ -525,7 +525,7 @@ Enable package response | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **object** | `string` | ✅ | The unique machine name of the object to query (e.g. "account"). | -| **query** | `{ object: string; fields?: string[]; where?: any; search?: string \| { query: string; fields?: string[]; fuzzy: boolean; operator: Enum<'and' \| 'or'>; … }; … }` | optional | Structured query definition (filter, sort, select, pagination). | +| **query** | `{ object: string; fields?: string[]; where?: any; search?: string \| object; … }` | optional | Structured query definition (filter, sort, select, pagination). | --- @@ -885,7 +885,7 @@ Get package response | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **locale** | `string` | ✅ | Locale code | -| **translations** | `{ objects?: Record }>; … }>; apps?: Record }>; messages?: Record; globalActions?: Record }>; … }>; … }` | ✅ | Translation data | +| **translations** | `{ objects?: Record; apps?: Record; messages?: Record; globalActions?: Record; … }` | ✅ | Translation data | --- @@ -911,9 +911,9 @@ Get package response | **name** | `string` | optional | Item name — supplied by the metadata door; for an object-scoped container it is the object name. | | **label** | `string \| Record` | optional | Human-readable label shown in metadata lists. | | **object** | `string` | optional | Object this container binds to — how a stack-level `views: [...]` entry says which object its views belong to; read by `getViewsByObject()` / `GET /meta/view?object=`. | -| **list** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: { provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }; … }` | optional | | +| **list** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }` | optional | | | **form** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }` | optional | | -| **listViews** | `Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: { provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) | +| **listViews** | `Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) | | **formViews** | `Record; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }>` | optional | Additional named form views | | **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. | | **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). | @@ -974,7 +974,7 @@ Install package response | :--- | :--- | :--- | :--- | | **package** | `{ manifest: object; status?: Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>; enabled?: boolean; installedAt?: string; … }` | ✅ | Installed package details | | **message** | `string` | optional | Installation status message | -| **dependencyResolution** | `{ dependencies: { packageId: string; requiredRange: string; resolvedVersion?: string; installedVersion?: string; … }[]; canProceed: boolean; requiredActions: { type: Enum<'install' \| 'upgrade' \| 'confirm_conflict'>; packageId: string; description: string }[]; installOrder: string[]; … }` | optional | Dependency resolution result from install analysis | +| **dependencyResolution** | `{ dependencies: object[]; canProceed: boolean; requiredActions: object[]; installOrder: string[]; … }` | optional | Dependency resolution result from install analysis | --- @@ -1463,7 +1463,7 @@ Uninstall package response | **total** | `number` | ✅ | Total number of records in the batch | | **succeeded** | `number` | ✅ | Number of records that succeeded | | **failed** | `number` | ✅ | Number of records that failed | -| **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; … }[]` | ✅ | Detailed results for each record | +| **results** | `{ id?: string; success: boolean; errors?: object[]; data?: Record; … }[]` | ✅ | Detailed results for each record | --- @@ -1525,7 +1525,7 @@ Uninstall package response | **object** | `string` | ✅ | The object name. | | **mode** | `Enum<'insert' \| 'update'>` | ✅ | The write mode the verdict was reached for. | | **valid** | `boolean` | ✅ | True when EVERY row is valid — the whole-set answer. | -| **results** | `{ valid: boolean; errors: { field: string; code: string; message: string }[]; warnings: { field: string; code: string; message: string }[] }[]` | ✅ | Per-row verdicts, in submission order. | +| **results** | `{ valid: boolean; errors: object[]; warnings: object[] }[]` | ✅ | Per-row verdicts, in submission order. | | **posture** | `{ valueShapeStrict: boolean; mediaValueShapeStrict: boolean }` | ✅ | The ADR-0104 posture the verdict was reached under — reported because it is the difference between "this row is fine" and "this row is fine HERE". The same row can be an error on a self-certified deployment and an admitted warning on an un-migrated one, and a caller explaining a verdict needs to know which it got. An unconditionally-strict preview was considered and rejected (#4633 option B): it would fail rows on every un-migrated deployment that the write would have accepted. | diff --git a/content/docs/references/api/realtime.mdx b/content/docs/references/api/realtime.mdx index 66ac1e4f17..11ab0fa9e2 100644 --- a/content/docs/references/api/realtime.mdx +++ b/content/docs/references/api/realtime.mdx @@ -29,7 +29,7 @@ const result = RealtimeConfigSchema.parse(data); | :--- | :--- | :--- | :--- | | **enabled** | `boolean` | ✅ | Enable realtime synchronization | | **transport** | `Enum<'websocket' \| 'sse' \| 'polling'>` | ✅ | Transport protocol | -| **subscriptions** | `{ id: string; events: { type: Enum<'record.created' \| 'record.updated' \| 'record.deleted' \| 'field.changed'>; object?: string; filters?: any }[]; transport: Enum<'websocket' \| 'sse' \| 'polling'>; channel?: string }[]` | optional | Default subscriptions | +| **subscriptions** | `{ id: string; events: object[]; transport: Enum<'websocket' \| 'sse' \| 'polling'>; channel?: string }[]` | optional | Default subscriptions | --- diff --git a/content/docs/references/api/rest-server.mdx b/content/docs/references/api/rest-server.mdx index 159e03e37a..4f6fb4fea3 100644 --- a/content/docs/references/api/rest-server.mdx +++ b/content/docs/references/api/rest-server.mdx @@ -169,10 +169,10 @@ const result = BatchEndpointsConfigSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **api** | `{ version: string; basePath: string; apiPath?: string; enableCrud: boolean; … }` | optional | REST API configuration | -| **crud** | `{ operations?: object; patterns?: Record; path: string; summary?: string; description?: string }>; dataPrefix: string; objectParamStyle: Enum<'path' \| 'query'> }` | optional | CRUD endpoints configuration | +| **crud** | `{ operations?: object; patterns?: Record; dataPrefix: string; objectParamStyle: Enum<'path' \| 'query'> }` | optional | CRUD endpoints configuration | | **metadata** | `{ prefix: string; enableCache: boolean; cacheTtl: integer; endpoints?: object }` | optional | Metadata endpoints configuration | | **batch** | `{ maxBatchSize: integer; enableBatchEndpoint: boolean; operations?: object; defaultAtomic: boolean }` | optional | Batch endpoints configuration | -| **routes** | `{ includeObjects?: string[]; excludeObjects?: string[]; nameTransform: Enum<'none' \| 'plural' \| 'kebab-case' \| 'camelCase'>; overrides?: Record }> }` | optional | Route generation configuration | +| **routes** | `{ includeObjects?: string[]; excludeObjects?: string[]; nameTransform: Enum<'none' \| 'plural' \| 'kebab-case' \| 'camelCase'>; overrides?: Record }` | optional | Route generation configuration | | **openApi31** | `never` | optional | [REMOVED] `RestServerConfig.openApi31` was removed in @objectstack/spec 17 (#4579, ADR-0049) — no runtime ever read it: the REST server forwards only `api`/`crud`/`metadata`/`batch`/`routes`, and the served /openapi.json is the pre-generated contract enriched with the live server URL and the registered objects, so webhook/callback definitions declared here never appeared in it. Delete the key. Config-driven OpenAPI 3.1 webhooks/callbacks documentation is a new capability and must arrive via the enforce route of ADR-0049 (a new ADR), not by re-declaring the key; for a real outbound webhook use `Webhook` from `@objectstack/spec/automation`. | diff --git a/content/docs/references/automation/control-flow.mdx b/content/docs/references/automation/control-flow.mdx index 7a839bca46..c3331ccfb4 100644 --- a/content/docs/references/automation/control-flow.mdx +++ b/content/docs/references/automation/control-flow.mdx @@ -94,7 +94,7 @@ const result = FlowRegionSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **nodes** | `{ id: string; type: string; label: string; config?: Record; … }[]` | ✅ | Region body nodes (single-entry/single-exit sub-graph) | -| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | optional | Region body edges | +| **edges** | `{ id: string; source: string; target: string; condition?: string \| object; … }[]` | optional | Region body edges | --- @@ -109,7 +109,7 @@ const result = FlowRegionSchema.parse(data); | **iteratorVariable** | `string` | optional | Loop variable holding the current item | | **indexVariable** | `string` | optional | Optional loop variable holding the current index | | **maxIterations** | `integer` | optional | Hard cap on iterations (clamped to the engine ceiling) | -| **body** | `{ nodes: { id: string; type: string; label: string; config?: Record; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | optional | Loop body region (omit for legacy flat-graph loops) | +| **body** | `{ nodes: object[]; edges?: object[] }` | optional | Loop body region (omit for legacy flat-graph loops) | --- @@ -122,7 +122,7 @@ const result = FlowRegionSchema.parse(data); | :--- | :--- | :--- | :--- | | **name** | `string` | optional | Branch label | | **nodes** | `{ id: string; type: string; label: string; config?: Record; … }[]` | ✅ | Branch body nodes | -| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | optional | Branch body edges | +| **edges** | `{ id: string; source: string; target: string; condition?: string \| object; … }[]` | optional | Branch body edges | --- @@ -133,7 +133,7 @@ const result = FlowRegionSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **branches** | `{ name?: string; nodes: { id: string; type: string; label: string; config?: Record; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }[]` | ✅ | Branch regions executed concurrently; implicit join at block end | +| **branches** | `{ name?: string; nodes: object[]; edges?: object[] }[]` | ✅ | Branch regions executed concurrently; implicit join at block end | --- @@ -160,8 +160,8 @@ const result = FlowRegionSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **try** | `{ nodes: { id: string; type: string; label: string; config?: Record; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | ✅ | Protected region | -| **catch** | `{ nodes: { id: string; type: string; label: string; config?: Record; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | optional | Handler region run when the try region fails | +| **try** | `{ nodes: object[]; edges?: object[] }` | ✅ | Protected region | +| **catch** | `{ nodes: object[]; edges?: object[] }` | optional | Handler region run when the try region fails | | **errorVariable** | `string` | optional | Variable holding the caught error in the catch region | | **retry** | `{ maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; maxRetryDelayMs?: integer; … }` | optional | Optional retry policy for the try region | diff --git a/content/docs/references/automation/flow.mdx b/content/docs/references/automation/flow.mdx index b1a8c783c1..70c3538c94 100644 --- a/content/docs/references/automation/flow.mdx +++ b/content/docs/references/automation/flow.mdx @@ -50,7 +50,7 @@ const result = FlowSchema.parse(data); | **type** | `Enum<'autolaunched' \| 'record_change' \| 'schedule' \| 'screen' \| 'api'>` | ✅ | Flow type | | **variables** | `{ name: string; type: string; isInput?: boolean; isOutput?: boolean }[]` | optional | Flow variables | | **nodes** | `{ id: string; type: string; label: string; config?: Record; … }[]` | ✅ | Flow nodes | -| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | ✅ | Flow connections | +| **edges** | `{ id: string; source: string; target: string; condition?: string \| object; … }[]` | ✅ | Flow connections | | **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. | | **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. | | **errorHandling** | `{ strategy?: Enum<'fail' \| 'retry' \| 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … }` | optional | Flow-level error handling configuration | diff --git a/content/docs/references/automation/state-machine.mdx b/content/docs/references/automation/state-machine.mdx index cd7019fd24..e6c9356019 100644 --- a/content/docs/references/automation/state-machine.mdx +++ b/content/docs/references/automation/state-machine.mdx @@ -143,8 +143,8 @@ Type: `string` | **description** | `string` | optional | | | **contextSchema** | `Record` | optional | Zod Schema for the machine context/memory | | **initial** | `string` | ✅ | Initial State ID | -| **states** | `Record; entry?: (string \| { type: string; params?: Record })[]; exit?: (string \| { type: string; params?: Record })[]; on?: Record }; actions?: (string \| { type: string; params?: Record })[]; description?: string } \| { target?: string; cond?: string \| { type: string; params?: Record }; actions?: (string \| { type: string; params?: Record })[]; description?: string }[]>; … }>` | ✅ | State Nodes | -| **on** | `Record }; actions?: (string \| { type: string; params?: Record })[]; description?: string } \| { target?: string; cond?: string \| { type: string; params?: Record }; actions?: (string \| { type: string; params?: Record })[]; description?: string }[]>` | optional | | +| **states** | `Record; entry?: (string \| object)[]; exit?: (string \| object)[]; on?: Record; … }>` | ✅ | State Nodes | +| **on** | `Record` | optional | | --- @@ -158,8 +158,8 @@ Type: `string` | **type** | `Enum<'atomic' \| 'compound' \| 'parallel' \| 'final' \| 'history'>` | ✅ | | | **entry** | `(string \| { type: string; params?: Record })[]` | optional | Actions to run when entering this state | | **exit** | `(string \| { type: string; params?: Record })[]` | optional | Actions to run when leaving this state | -| **on** | `Record }; actions?: (string \| { type: string; params?: Record })[]; description?: string } \| { target?: string; cond?: string \| { type: string; params?: Record }; actions?: (string \| { type: string; params?: Record })[]; description?: string }[]>` | optional | Map of Event Type -> Transition Definition | -| **always** | `{ target?: string; cond?: string \| { type: string; params?: Record }; actions?: (string \| { type: string; params?: Record })[]; description?: string }[]` | optional | | +| **on** | `Record` | optional | Map of Event Type -> Transition Definition | +| **always** | `{ target?: string; cond?: string \| object; actions?: (string \| object)[]; description?: string }[]` | optional | | | **initial** | `string` | optional | Initial child state (if compound) | | **states** | `Record` | optional | | | **meta** | `{ label?: string; description?: string; color?: string; aiInstructions?: string }` | optional | | diff --git a/content/docs/references/cloud/marketplace.mdx b/content/docs/references/cloud/marketplace.mdx index f0a006ea65..a0749ca250 100644 --- a/content/docs/references/cloud/marketplace.mdx +++ b/content/docs/references/cloud/marketplace.mdx @@ -238,7 +238,7 @@ Marketplace search response | **total** | `integer` | ✅ | Total matching results | | **page** | `integer` | ✅ | Current page number | | **pageSize** | `integer` | ✅ | Items per page | -| **facets** | `{ categories?: { category: Enum<'crm' \| 'erp' \| 'hr' \| 'finance' \| 'project' \| 'collaboration' \| 'analytics' \| … +8 more>; count: integer }[]; pricing?: { model: Enum<'free' \| 'freemium' \| 'paid' \| 'subscription' \| 'usage-based' \| 'contact-sales'>; count: integer }[] }` | optional | Aggregation facets for refining search | +| **facets** | `{ categories?: object[]; pricing?: object[] }` | optional | Aggregation facets for refining search | --- @@ -259,7 +259,7 @@ Developer submission of a package version for review | **artifactUrl** | `string` | ✅ | Package artifact URL for review | | **releaseNotes** | `string` | optional | Release notes for this version | | **isNewListing** | `boolean` | ✅ | Whether this is a new listing submission | -| **scanResults** | `{ passed: boolean; securityScore?: number; compatibilityCheck?: boolean; issues?: { severity: Enum<'critical' \| 'high' \| 'medium' \| 'low' \| 'info'>; message: string; file?: string; line?: number }[] }` | optional | Automated scan results | +| **scanResults** | `{ passed: boolean; securityScore?: number; compatibilityCheck?: boolean; issues?: object[] }` | optional | Automated scan results | | **reviewerNotes** | `string` | optional | Notes from the platform reviewer | | **submittedAt** | `string` | optional | Submission timestamp | | **reviewedAt** | `string` | optional | Review completion timestamp | diff --git a/content/docs/references/data/data-engine.mdx b/content/docs/references/data/data-engine.mdx index 07ae177fa0..8f1a753238 100644 --- a/content/docs/references/data/data-engine.mdx +++ b/content/docs/references/data/data-engine.mdx @@ -65,7 +65,7 @@ Options for DataEngine.aggregate operations | :--- | :--- | :--- | :--- | | **method** | `'aggregate'` | ✅ | | | **object** | `string` | ✅ | | -| **query** | `{ context?: object; where?: Record \| any; groupBy?: string[]; aggregations?: { function: Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct'>; field?: string; alias: string; distinct?: boolean; … }[]; … }` | ✅ | | +| **query** | `{ context?: object; where?: Record \| any; groupBy?: string[]; aggregations?: object[]; … }` | ✅ | | --- @@ -170,7 +170,7 @@ Reference: any | :--- | :--- | :--- | :--- | | **method** | `'findOne'` | ✅ | | | **object** | `string` | ✅ | | -| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: { field: string; order?: Enum<'asc' \| 'desc'> }[]; … }` | optional | | +| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: object[]; … }` | optional | | --- @@ -183,7 +183,7 @@ Reference: any | :--- | :--- | :--- | :--- | | **method** | `'find'` | ✅ | | | **object** | `string` | ✅ | | -| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: { field: string; order?: Enum<'asc' \| 'desc'> }[]; … }` | optional | | +| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: object[]; … }` | optional | | --- @@ -252,7 +252,7 @@ This schema accepts one of the following structures: | :--- | :--- | :--- | :--- | | **method** | `'find'` | ✅ | | | **object** | `string` | ✅ | | -| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: { field: string; order?: Enum<'asc' \| 'desc'> }[]; … }` | optional | | +| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: object[]; … }` | optional | | --- @@ -264,7 +264,7 @@ This schema accepts one of the following structures: | :--- | :--- | :--- | :--- | | **method** | `'findOne'` | ✅ | | | **object** | `string` | ✅ | | -| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: { field: string; order?: Enum<'asc' \| 'desc'> }[]; … }` | optional | | +| **query** | `{ context?: object; where?: Record \| any; fields?: string[]; orderBy?: object[]; … }` | optional | | --- @@ -328,7 +328,7 @@ This schema accepts one of the following structures: | :--- | :--- | :--- | :--- | | **method** | `'aggregate'` | ✅ | | | **object** | `string` | ✅ | | -| **query** | `{ context?: object; where?: Record \| any; groupBy?: string[]; aggregations?: { function: Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct'>; field?: string; alias: string; distinct?: boolean; … }[]; … }` | ✅ | | +| **query** | `{ context?: object; where?: Record \| any; groupBy?: string[]; aggregations?: object[]; … }` | ✅ | | --- @@ -521,7 +521,7 @@ QueryAST-aligned query options for IDataEngine.find() operations | **cursor** | `never` | optional | [REMOVED] `query.cursor` was removed in @objectstack/spec 17 (#4286, ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; `QueryBuilder.cursor()` was removed with it. Express the keyset as an ordinary `where` predicate on your sort key — `where: { created_at: { $gt: last.created_at } }` with the matching `orderBy` — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record. | | **search** | `{ query: string; fields?: string[]; fuzzy: boolean; operator: Enum<'and' \| 'or'>; … }` | optional | | | **searchFields** | `string[]` | optional | | -| **expand** | `Record; … }; … }>` | optional | | +| **expand** | `Record` | optional | | | **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. | diff --git a/content/docs/references/data/document.mdx b/content/docs/references/data/document.mdx index d78f2561c2..6be292d5b4 100644 --- a/content/docs/references/data/document.mdx +++ b/content/docs/references/data/document.mdx @@ -52,9 +52,9 @@ const result = DocumentSchema.parse(data); | **fileSize** | `number` | ✅ | File size in bytes | | **category** | `string` | optional | Document category | | **tags** | `string[]` | optional | Document tags | -| **versioning** | `{ enabled: boolean; versions: { versionNumber: number; createdAt: number; createdBy: string; size: number; … }[]; majorVersion: number; minorVersion: number }` | optional | Version control | +| **versioning** | `{ enabled: boolean; versions: object[]; majorVersion: number; minorVersion: number }` | optional | Version control | | **template** | `{ id: string; name: string; description?: string; fileUrl: string; … }` | optional | Document template | -| **eSignature** | `{ provider: Enum<'docusign' \| 'adobe-sign' \| 'hellosign' \| 'custom'>; enabled: boolean; signers: { email: string; name: string; role: string; order: number }[]; expirationDays: number; … }` | optional | E-signature config | +| **eSignature** | `{ provider: Enum<'docusign' \| 'adobe-sign' \| 'hellosign' \| 'custom'>; enabled: boolean; signers: object[]; expirationDays: number; … }` | optional | E-signature config | | **access** | `{ isPublic: boolean; sharedWith?: string[]; expiresAt?: number }` | optional | Access control | | **metadata** | `Record` | optional | Custom metadata | diff --git a/content/docs/references/data/external-catalog.mdx b/content/docs/references/data/external-catalog.mdx index 72017be763..dc57749550 100644 --- a/content/docs/references/data/external-catalog.mdx +++ b/content/docs/references/data/external-catalog.mdx @@ -40,7 +40,7 @@ const result = ExternalCatalogSchema.parse(data); | **datasource** | `string` | ✅ | Datasource.name this catalog snapshots. | | **snapshotAt** | `string` | ✅ | When the snapshot was taken (ISO 8601). | | **dialect** | `string` | optional | Remote SQL dialect, when known. | -| **tables** | `{ remoteSchema?: string; remoteName: string; columns: { name: string; sqlType: string; nullable: boolean; primaryKey: boolean; … }[]; indexes?: { name: string; columns: string[]; unique: boolean }[]; … }[]` | ✅ | Snapshotted remote tables. | +| **tables** | `{ remoteSchema?: string; remoteName: string; columns: object[]; indexes?: object[]; … }[]` | ✅ | Snapshotted remote tables. | --- diff --git a/content/docs/references/data/object.mdx b/content/docs/references/data/object.mdx index ef7b995515..c49c98c332 100644 --- a/content/docs/references/data/object.mdx +++ b/content/docs/references/data/object.mdx @@ -81,7 +81,7 @@ const result = ApiMethod.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **class** | `Enum<'record' \| 'audit' \| 'telemetry' \| 'transient' \| 'event'>` | ✅ | Persistence contract: record (business truth, permanent) \| audit (compliance ledger) \| telemetry (high-freq log) \| transient (ephemeral state) \| event (bus messages). | -| **retention** | `{ maxAge: string; onlyWhen?: Record }` | optional | Age-based retention window enforced by the LifecycleService Reaper. | +| **retention** | `{ maxAge: string; onlyWhen?: Record }` | optional | Age-based retention window enforced by the LifecycleService Reaper. | | **ttl** | `{ field: string; expireAfter: string }` | optional | Per-row TTL auto-expiry (transient/event classes). | | **storage** | `{ strategy: 'rotation'; shards: integer; unit: Enum<'day' \| 'week' \| 'month'> }` | optional | Physical storage strategy for high-frequency telemetry (LifecycleService Rotator). | | **archive** | `{ after: string; to: string; keep?: string }` | optional | Cold-store archival (LifecycleService Archiver) — audit-class hot→cold hand-off. | @@ -117,7 +117,7 @@ const result = ApiMethod.parse(data); | **isSystem** | `boolean` | optional | Is system object (protected from deletion; defaults its org-wide sharing to public when no sharingModel is set — plugin-sharing) | | **managedBy** | `Enum<'platform' \| 'config' \| 'system-data' \| 'engine-owned' \| 'append-only' \| 'better-auth'>` | optional | Lifecycle bucket — platform (user CRUD) \| config (admin authored) \| system-data (platform-defined schema, admin/user-writable data) \| engine-owned (engine owns the lifecycle, no user writes) \| append-only (audit) \| better-auth (identity). UI clients honour the resolved affordance matrix. | | **ownership** | `Enum<'user' \| 'org' \| 'none'>` | optional | Record-ownership model: user (default — injects reassignable owner_id) \| org \| none (no per-record owner, skips owner_id). Distinct from the package own/extend contribution kind. | -| **userActions** | `{ create?: boolean; import?: boolean; edit?: boolean \| { enabled?: boolean; visibleWhen?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; disabledWhen?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }; delete?: boolean \| { enabled?: boolean; visibleWhen?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; disabledWhen?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }; … }` | optional | Per-object override of the resolved CRUD affordance matrix. | +| **userActions** | `{ create?: boolean; import?: boolean; edit?: boolean \| object; delete?: boolean \| object; … }` | optional | Per-object override of the resolved CRUD affordance matrix. | | **systemFields** | `false \| { tenant?: boolean; audit?: boolean }` | optional | Opt out of, or selectively disable, registry-level system-field auto-injection. | | **datasource** | `string` | optional | Target Datasource ID. "default" is the primary DB. | | **external** | `{ remoteName?: string; remoteSchema?: string; writable?: boolean; columnMap?: Record; … }` | optional | Remote table binding for federated (external) objects. | @@ -136,7 +136,7 @@ const result = ApiMethod.parse(data); | **titleFormat** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField. | | **highlightFields** | `string[]` | optional | [ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. | | **stageField** | `string \| false` | optional | [ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. | -| **listViews** | `Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: { provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }; … }>` | optional | Built-in named list views (segmented tabs) shipped with the object schema — "views" mode, dropdown userFilters allowed, no page-only tabs (ADR-0047) | +| **listViews** | `Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }>` | optional | Built-in named list views (segmented tabs) shipped with the object schema — "views" mode, dropdown userFilters allowed, no page-only tabs (ADR-0047) | | **searchableFields** | `string[]` | optional | Fields the `$search` query matches against (ADR-0061). Canonical default for the record picker, list quick-search and global search; views may narrow it. When unset, search auto-defaults to the name/title field plus short-text fields. | | **enable** | `{ trackHistory?: boolean; searchable?: boolean; apiEnabled?: boolean; apiMethods?: Enum<'get' \| 'list' \| 'create' \| 'update' \| 'delete' \| 'bulk'>[]; … }` | optional | Enabled system features modules | | **sharingModel** | `Enum<'private' \| 'public_read' \| 'public_read_write' \| 'controlled_by_parent'>` | optional | Org-Wide Default record visibility (OWD) for INTERNAL users. Canonical four only (legacy aliases removed, ADR-0090 D4): private (owner-only) \| public_read (everyone reads, owner writes) \| public_read_write (everyone reads+writes) \| controlled_by_parent (derived from the master record). A CUSTOM object that omits this resolves to private at runtime (ADR-0090 D1). | diff --git a/content/docs/references/data/seed-loader.mdx b/content/docs/references/data/seed-loader.mdx index 0189cf7d33..1203ceffc5 100644 --- a/content/docs/references/data/seed-loader.mdx +++ b/content/docs/references/data/seed-loader.mdx @@ -49,7 +49,7 @@ Complete object dependency graph for seed data loading | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **nodes** | `{ object: string; dependsOn: string[]; references: { field: string; targetObject: string; targetField: string; fieldType: Enum<'lookup' \| 'master_detail' \| 'user'>; … }[] }[]` | ✅ | All objects in the dependency graph | +| **nodes** | `{ object: string; dependsOn: string[]; references: object[] }[]` | ✅ | All objects in the dependency graph | | **insertOrder** | `string[]` | ✅ | Topologically sorted insert order | | **circularDependencies** | `string[][]` | ✅ | Circular dependency chains (e.g., [["a", "b", "a"]]) | @@ -190,7 +190,7 @@ Complete seed loader result | :--- | :--- | :--- | :--- | | **success** | `boolean` | ✅ | Overall success status | | **dryRun** | `boolean` | ✅ | Whether this was a dry-run | -| **dependencyGraph** | `{ nodes: { object: string; dependsOn: string[]; references: { field: string; targetObject: string; targetField: string; fieldType: Enum<'lookup' \| 'master_detail' \| 'user'>; … }[] }[]; insertOrder: string[]; circularDependencies: string[][] }` | ✅ | Object dependency graph | +| **dependencyGraph** | `{ nodes: object[]; insertOrder: string[]; circularDependencies: string[][] }` | ✅ | Object dependency graph | | **results** | `{ object: string; mode: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; inserted: integer; updated: integer; … }[]` | ✅ | Per-object load results | | **errors** | `{ sourceObject: string; field: string; targetObject: string; targetField: string; … }[]` | ✅ | All reference resolution errors | | **summary** | `{ objectsProcessed: integer; totalRecords: integer; totalInserted: integer; totalUpdated: integer; … }` | ✅ | Summary statistics | diff --git a/content/docs/references/integration/connector.mdx b/content/docs/references/integration/connector.mdx index 405501c354..2b2ccf5cea 100644 --- a/content/docs/references/integration/connector.mdx +++ b/content/docs/references/integration/connector.mdx @@ -171,7 +171,7 @@ Circuit breaker configuration | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | | **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | -| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | +| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| object; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; defaultValue?: any; dataType?: Enum<'string' \| 'number' \| 'boolean' \| 'date' \| 'datetime' \| 'json' \| 'array'>; … }[]` | optional | Field mapping rules | | **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete' \| 'bulk_update' \| 'bulk_delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `never` | optional | [REMOVED] `connector.rateLimitConfig` was removed in @objectstack/spec 17.0.0 (#4911, ADR-0049 D2) — the entire shape is gone, not just this key: `ConnectorRateLimitConfig` and its `RateLimitStrategy` enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime `security/rate-limit.ts`) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute `shared` `RateLimitConfig` — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. Run `os migrate meta --from 16` to rewrite it automatically. | @@ -180,7 +180,7 @@ Circuit breaker configuration | **requestTimeoutMs** | `number` | optional | Request timeout in ms | | **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status | | **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). | -| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| … +3 more>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| … +3 more>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration | +| **errorMapping** | `{ rules: object[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| … +3 more>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration | | **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration | | **metadata** | `Record` | optional | Custom connector metadata | @@ -486,7 +486,7 @@ Connector type | **auth** | `{ type: 'none' } \| { type: 'bearer'; credentialRef: string } \| { type: 'api-key'; credentialRef: string; headerName?: string; paramName?: string } \| { type: 'basic'; username: string; credentialRef: string }` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). | | **actions** | `{ key: string; label: string; description?: string; inputSchema?: Record; … }[]` | optional | | | **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | Trigger definitions (not yet enforced — never read at registration; see #3197) | -| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration | +| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| object; realtimeSync?: boolean; … }` | optional | Data sync configuration | | **fieldMappings** | `{ source: string; target: string; defaultValue?: any; dataType?: Enum<'string' \| 'number' \| 'boolean' \| 'date' \| 'datetime' \| 'json' \| 'array'>; … }[]` | optional | Field mapping rules | | **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete' \| 'bulk_update' \| 'bulk_delete'>[]; … }[]` | optional | Webhook configurations (not yet enforced — never read at registration; see #3197) | | **rateLimitConfig** | `never` | optional | [REMOVED] `connector.rateLimitConfig` was removed in @objectstack/spec 17.0.0 (#4911, ADR-0049 D2) — the entire shape is gone, not just this key: `ConnectorRateLimitConfig` and its `RateLimitStrategy` enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime `security/rate-limit.ts`) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute `shared` `RateLimitConfig` — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. Run `os migrate meta --from 16` to rewrite it automatically. | @@ -495,7 +495,7 @@ Connector type | **requestTimeoutMs** | `number` | optional | Request timeout in ms | | **status** | `Enum<'active' \| 'inactive' \| 'error' \| 'configuring'>` | optional | Connector status | | **enabled** | `boolean` | optional | Enable connector. On declarative stack entries, false marks a deliberate catalog-only descriptor (#2612). | -| **errorMapping** | `{ rules: { sourceCode: string \| number; sourceMessage?: string; targetCode: string; targetCategory: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| … +3 more>; … }[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| … +3 more>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration | +| **errorMapping** | `{ rules: object[]; defaultCategory?: Enum<'validation' \| 'authorization' \| 'not_found' \| 'conflict' \| 'rate_limit' \| … +3 more>; unmappedBehavior: Enum<'passthrough' \| 'generic_error' \| 'throw'>; logUnmapped?: boolean }` | optional | Error mapping configuration | | **health** | `{ healthCheck?: object; circuitBreaker?: object }` | optional | Health and resilience configuration | | **metadata** | `Record` | optional | Custom connector metadata | diff --git a/content/docs/references/kernel/manifest.mdx b/content/docs/references/kernel/manifest.mdx index 6baf1e215b..f0f3ca8e3f 100644 --- a/content/docs/references/kernel/manifest.mdx +++ b/content/docs/references/kernel/manifest.mdx @@ -39,12 +39,12 @@ const result = ManifestSchema.parse(data); | **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files | | **datasources** | `string[]` | optional | Glob patterns for Datasource definitions | | **dependencies** | `Record` | optional | Package dependencies | -| **configuration** | `{ title?: string; properties: Record; default?: any; description?: string; required?: boolean; … }> }` | optional | Plugin configuration settings | -| **contributes** | `{ kinds?: { id: string; globs: string[]; description?: string }[]; events?: string[]; menus?: Record; themes?: { id: string; label: string; path: string }[]; … }` | optional | Platform contributions | +| **configuration** | `{ title?: string; properties: Record }` | optional | Plugin configuration settings | +| **contributes** | `{ kinds?: object[]; events?: string[]; menus?: Record; themes?: object[]; … }` | optional | Platform contributions | | **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) | -| **capabilities** | `{ implements?: { protocol: object; conformance?: Enum<'full' \| 'partial' \| 'experimental' \| 'deprecated'>; implementedFeatures?: string[]; features?: { name: string; enabled?: boolean; description?: string; sinceVersion?: string; … }[]; … }[]; provides?: { id: string; name: string; description?: string; version: object; … }[]; requires?: { pluginId: string; version: string; optional?: boolean; reason?: string; … }[]; extensionPoints?: { id: string; name: string; description?: string; type: Enum<'action' \| 'hook' \| 'widget' \| 'provider' \| 'transformer' \| 'validator' \| 'decorator'>; … }[]; … }` | optional | Plugin capability declarations for interoperability | +| **capabilities** | `{ implements?: object[]; provides?: object[]; requires?: object[]; extensionPoints?: object[]; … }` | optional | Plugin capability declarations for interoperability | | **extensions** | `Record` | optional | Extension points and contributions | -| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: ({ id: string; label: string \| Record; icon?: string; order?: number; … } \| { id: string; label: string \| Record; icon?: string; order?: number; … } \| { id: string; label: string \| Record; icon?: string; order?: number; … } \| { id: string; label: string \| Record; icon?: string; order?: number; … } \| … +5 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages | +| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| object \| object \| object \| … +5 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages | | **loading** | `{ strategy?: Enum<'eager' \| 'lazy' \| 'parallel' \| 'deferred' \| 'on-demand'>; preload?: object; codeSplitting?: object; dynamicImport?: object; … }` | optional | Plugin loading and runtime behavior configuration | | **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) | | **engines** | `{ platform?: string; protocol?: string }` | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes `engine`) | diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index 4a21c43664..fd6c36030e 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -117,7 +117,7 @@ const result = MetadataBulkResultSchema.parse(data); | **type** | `'standard'` | ✅ | Plugin type | | **description** | `string` | optional | Plugin description | | **capabilities** | `{ crud?: boolean; query?: boolean; overlay?: boolean; watch?: boolean; … }` | ✅ | Plugin capabilities | -| **config** | `{ storage: object; customizationPolicies?: { metadataType: string; allowCustomization?: boolean; lockedFields?: string[]; customizableFields?: string[]; … }[]; mergeStrategy?: object; additionalTypes?: { label: string; description?: string; filePatterns: string[]; supportsOverlay?: boolean; … }[]; … }` | optional | Plugin configuration | +| **config** | `{ storage: object; customizationPolicies?: object[]; mergeStrategy?: object; additionalTypes?: object[]; … }` | optional | Plugin configuration | --- diff --git a/content/docs/references/kernel/package-registry.mdx b/content/docs/references/kernel/package-registry.mdx index ec093169db..19aabb879f 100644 --- a/content/docs/references/kernel/package-registry.mdx +++ b/content/docs/references/kernel/package-registry.mdx @@ -149,7 +149,7 @@ Install package response | :--- | :--- | :--- | :--- | | **package** | `{ manifest: object; status?: Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>; enabled?: boolean; installedAt?: string; … }` | ✅ | Installed package details | | **message** | `string` | optional | Installation status message | -| **dependencyResolution** | `{ dependencies: { packageId: string; requiredRange: string; resolvedVersion?: string; installedVersion?: string; … }[]; canProceed: boolean; requiredActions: { type: Enum<'install' \| 'upgrade' \| 'confirm_conflict'>; packageId: string; description: string }[]; installOrder: string[]; … }` | optional | Dependency resolution result from install analysis | +| **dependencyResolution** | `{ dependencies: object[]; canProceed: boolean; requiredActions: object[]; installOrder: string[]; … }` | optional | Dependency resolution result from install analysis | --- diff --git a/content/docs/references/kernel/plugin-capability.mdx b/content/docs/references/kernel/plugin-capability.mdx index 76f5477750..b7b24a6383 100644 --- a/content/docs/references/kernel/plugin-capability.mdx +++ b/content/docs/references/kernel/plugin-capability.mdx @@ -84,7 +84,7 @@ Level of protocol conformance | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **implements** | `{ protocol: object; conformance: Enum<'full' \| 'partial' \| 'experimental' \| 'deprecated'>; implementedFeatures?: string[]; features?: { name: string; enabled: boolean; description?: string; sinceVersion?: string; … }[]; … }[]` | optional | List of protocols this plugin conforms to | +| **implements** | `{ protocol: object; conformance: Enum<'full' \| 'partial' \| 'experimental' \| 'deprecated'>; implementedFeatures?: string[]; features?: object[]; … }[]` | optional | List of protocols this plugin conforms to | | **provides** | `{ id: string; name: string; description?: string; version: object; … }[]` | optional | Services/APIs this plugin offers to others | | **requires** | `{ pluginId: string; version: string; optional: boolean; reason?: string; … }[]` | optional | Required plugins and their capabilities | | **extensionPoints** | `{ id: string; name: string; description?: string; type: Enum<'action' \| 'hook' \| 'widget' \| 'provider' \| 'transformer' \| 'validator' \| 'decorator'>; … }[]` | optional | Points where other plugins can extend this plugin | @@ -118,7 +118,7 @@ Level of protocol conformance | **name** | `string` | ✅ | | | **description** | `string` | optional | | | **version** | `{ major: integer; minor: integer; patch: integer }` | ✅ | Semantic version of the protocol | -| **methods** | `{ name: string; description?: string; parameters?: { name: string; type: string; required: boolean; description?: string }[]; returnType?: string; … }[]` | ✅ | | +| **methods** | `{ name: string; description?: string; parameters?: object[]; returnType?: string; … }[]` | ✅ | | | **events** | `{ name: string; description?: string; payload?: string }[]` | optional | | | **stability** | `Enum<'stable' \| 'beta' \| 'alpha' \| 'experimental'>` | ✅ | | diff --git a/content/docs/references/kernel/plugin-registry.mdx b/content/docs/references/kernel/plugin-registry.mdx index 3a43b1b1c5..40d6dfa5e8 100644 --- a/content/docs/references/kernel/plugin-registry.mdx +++ b/content/docs/references/kernel/plugin-registry.mdx @@ -71,7 +71,7 @@ const result = PluginInstallConfigSchema.parse(data); | **category** | `Enum<'data' \| 'integration' \| 'ui' \| 'analytics' \| 'security' \| 'automation' \| 'ai' \| 'utility' \| 'driver' \| 'gateway' \| 'adapter'>` | optional | | | **tags** | `string[]` | optional | | | **vendor** | `{ id: string; name: string; website?: string; email?: string; … }` | ✅ | | -| **capabilities** | `{ implements?: { protocol: object; conformance: Enum<'full' \| 'partial' \| 'experimental' \| 'deprecated'>; implementedFeatures?: string[]; features?: { name: string; enabled: boolean; description?: string; sinceVersion?: string; … }[]; … }[]; provides?: { id: string; name: string; description?: string; version: object; … }[]; requires?: { pluginId: string; version: string; optional: boolean; reason?: string; … }[]; extensionPoints?: { id: string; name: string; description?: string; type: Enum<'action' \| 'hook' \| 'widget' \| 'provider' \| 'transformer' \| 'validator' \| 'decorator'>; … }[]; … }` | optional | | +| **capabilities** | `{ implements?: object[]; provides?: object[]; requires?: object[]; extensionPoints?: object[]; … }` | optional | | | **compatibility** | `{ minObjectStackVersion?: string; maxObjectStackVersion?: string; nodeVersion?: string; platforms?: Enum<'linux' \| 'darwin' \| 'win32' \| 'browser'>[] }` | optional | | | **links** | `{ homepage?: string; repository?: string; documentation?: string; bugs?: string; … }` | optional | | | **media** | `{ icon?: string; logo?: string; screenshots?: string[]; video?: string }` | optional | | diff --git a/content/docs/references/kernel/plugin-security-advanced.mdx b/content/docs/references/kernel/plugin-security-advanced.mdx index 9864a98b63..620ca607ef 100644 --- a/content/docs/references/kernel/plugin-security-advanced.mdx +++ b/content/docs/references/kernel/plugin-security-advanced.mdx @@ -61,7 +61,7 @@ const result = KernelSecurityPolicySchema.parse(data); | **vulnerabilities** | `{ cve?: string; id: string; severity: Enum<'critical' \| 'high' \| 'medium' \| 'low' \| 'info'>; category?: string; … }[]` | optional | | | **codeIssues** | `{ severity: Enum<'error' \| 'warning' \| 'info'>; type: string; file: string; line?: integer; … }[]` | optional | | | **dependencyVulnerabilities** | `{ package: string; version: string; vulnerability: object }[]` | optional | | -| **licenseCompliance** | `{ status: Enum<'compliant' \| 'non-compliant' \| 'unknown'>; issues?: { package: string; license: string; reason: string }[] }` | optional | | +| **licenseCompliance** | `{ status: Enum<'compliant' \| 'non-compliant' \| 'unknown'>; issues?: object[] }` | optional | | | **summary** | `{ totalVulnerabilities: integer; criticalCount: integer; highCount: integer; mediumCount: integer; … }` | ✅ | | @@ -140,7 +140,7 @@ Scope of permission application | **resource** | `Enum<'data.object' \| 'data.record' \| 'data.field' \| 'ui.view' \| 'ui.dashboard' \| 'ui.report' \| 'system.config' \| 'system.plugin' \| 'system.api' \| 'system.service' \| … +6 more>` | ✅ | Type of resource being accessed | | **actions** | `Enum<'create' \| 'read' \| 'update' \| 'delete' \| 'execute' \| 'manage' \| 'configure' \| 'share' \| 'export' \| 'import' \| 'admin'>[]` | ✅ | | | **scope** | `Enum<'global' \| 'tenant' \| 'user' \| 'resource' \| 'plugin'>` | optional | Scope of permission application | -| **filter** | `{ resourceIds?: string[]; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; fields?: string[] }` | optional | | +| **filter** | `{ resourceIds?: string[]; condition?: string \| object; fields?: string[] }` | optional | | | **description** | `string` | ✅ | | | **required** | `boolean` | optional | | | **justification** | `string` | optional | Why this permission is needed | @@ -188,10 +188,10 @@ Scope of permission application | :--- | :--- | :--- | :--- | | **pluginId** | `string` | ✅ | | | **trustLevel** | `Enum<'verified' \| 'trusted' \| 'community' \| 'untrusted' \| 'blocked'>` | ✅ | Trust level of the plugin | -| **permissions** | `{ permissions: { id: string; resource: Enum<'data.object' \| 'data.record' \| 'data.field' \| 'ui.view' \| 'ui.dashboard' \| … +11 more>; actions: Enum<'create' \| 'read' \| 'update' \| 'delete' \| 'execute' \| 'manage' \| 'configure' \| … +4 more>[]; scope?: Enum<'global' \| 'tenant' \| 'user' \| 'resource' \| 'plugin'>; … }[]; groups?: { name: string; description: string; permissions: string[] }[]; defaultGrant?: Enum<'prompt' \| 'allow' \| 'deny' \| 'inherit'> }` | ✅ | | +| **permissions** | `{ permissions: object[]; groups?: object[]; defaultGrant?: Enum<'prompt' \| 'allow' \| 'deny' \| 'inherit'> }` | ✅ | | | **sandbox** | `{ enabled?: boolean; level?: Enum<'none' \| 'minimal' \| 'standard' \| 'strict' \| 'paranoid'>; runtime?: object; filesystem?: object; … }` | ✅ | | | **policy** | `{ csp?: object; cors?: object; rateLimit?: object; authentication?: object; … }` | optional | | -| **scanResults** | `{ timestamp: string; scanner: object; status: Enum<'passed' \| 'failed' \| 'warning'>; vulnerabilities?: { cve?: string; id: string; severity: Enum<'critical' \| 'high' \| 'medium' \| 'low' \| 'info'>; category?: string; … }[]; … }[]` | optional | | +| **scanResults** | `{ timestamp: string; scanner: object; status: Enum<'passed' \| 'failed' \| 'warning'>; vulnerabilities?: object[]; … }[]` | optional | | | **vulnerabilities** | `{ cve?: string; id: string; severity: Enum<'critical' \| 'high' \| 'medium' \| 'low' \| 'info'>; category?: string; … }[]` | optional | | | **codeSigning** | `{ signed: boolean; signature?: string; certificate?: string; algorithm?: string; … }` | optional | | | **certifications** | `{ name: string; issuer: string; issuedDate: string; expiryDate?: string; … }[]` | optional | | diff --git a/content/docs/references/kernel/plugin-security.mdx b/content/docs/references/kernel/plugin-security.mdx index 273d787132..94cc1a9c2a 100644 --- a/content/docs/references/kernel/plugin-security.mdx +++ b/content/docs/references/kernel/plugin-security.mdx @@ -43,7 +43,7 @@ Complete dependency graph for a package and its transitive dependencies | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **root** | `{ id: string; version: string }` | ✅ | Root package of the dependency graph | -| **nodes** | `{ id: string; version: string; dependencies: { name: string; versionConstraint: string; type: Enum<'required' \| 'optional' \| 'peer' \| 'dev'>; resolvedVersion?: string }[]; depth: integer; … }[]` | ✅ | All resolved package nodes in the dependency graph | +| **nodes** | `{ id: string; version: string; dependencies: object[]; depth: integer; … }[]` | ✅ | All resolved package nodes in the dependency graph | | **edges** | `{ from: string; to: string; constraint: string }[]` | ✅ | Directed edges representing dependency relationships | | **stats** | `{ totalDependencies: integer; directDependencies: integer; maxDepth: integer }` | ✅ | Summary statistics for the dependency graph | @@ -93,8 +93,8 @@ Result of a dependency resolution process | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **status** | `Enum<'success' \| 'conflict' \| 'error'>` | ✅ | Overall status of the dependency resolution | -| **graph** | `{ root: object; nodes: { id: string; version: string; dependencies: { name: string; versionConstraint: string; type: Enum<'required' \| 'optional' \| 'peer' \| 'dev'>; resolvedVersion?: string }[]; depth: integer; … }[]; edges: { from: string; to: string; constraint: string }[]; stats: object }` | optional | Resolved dependency graph if resolution succeeded | -| **conflicts** | `{ package: string; conflicts: { version: string; requestedBy: string[]; constraint: string }[]; resolution?: object; severity: Enum<'error' \| 'warning' \| 'info'> }[]` | ✅ | List of dependency conflicts detected during resolution | +| **graph** | `{ root: object; nodes: object[]; edges: object[]; stats: object }` | optional | Resolved dependency graph if resolution succeeded | +| **conflicts** | `{ package: string; conflicts: object[]; resolution?: object; severity: Enum<'error' \| 'warning' \| 'info'> }[]` | ✅ | List of dependency conflicts detected during resolution | | **errors** | `{ package: string; error: string }[]` | ✅ | Errors encountered during dependency resolution | | **installOrder** | `string[]` | ✅ | Topologically sorted list of package IDs for installation | | **resolvedIn** | `integer` | optional | Time taken to resolve dependencies in milliseconds | @@ -227,7 +227,7 @@ Result of a security scan performed on a plugin | **vulnerabilities** | `{ cve?: string; id: string; title: string; description: string; … }[]` | ✅ | List of vulnerabilities discovered during the scan | | **summary** | `{ critical: integer; high: integer; medium: integer; low: integer; … }` | ✅ | Summary counts of vulnerabilities by severity | | **licenseIssues** | `{ package: string; license: string; reason: string; severity: Enum<'error' \| 'warning' \| 'info'> }[]` | ✅ | License compliance issues found during the scan | -| **codeQuality** | `{ score?: number; issues: { type: Enum<'security' \| 'quality' \| 'style'>; severity: Enum<'error' \| 'warning' \| 'info'>; message: string; file?: string; … }[] }` | optional | Code quality analysis results | +| **codeQuality** | `{ score?: number; issues: object[] }` | optional | Code quality analysis results | | **nextScanAt** | `string` | optional | ISO 8601 timestamp for the next scheduled scan | diff --git a/content/docs/references/kernel/plugin-versioning.mdx b/content/docs/references/kernel/plugin-versioning.mdx index 0f4d31be83..1ce70ca14d 100644 --- a/content/docs/references/kernel/plugin-versioning.mdx +++ b/content/docs/references/kernel/plugin-versioning.mdx @@ -124,7 +124,7 @@ Compatibility level between versions | **enabled** | `boolean` | optional | | | **maxConcurrentVersions** | `integer` | optional | How many versions can run at the same time | | **selectionStrategy** | `Enum<'latest' \| 'stable' \| 'compatible' \| 'pinned' \| 'canary' \| 'custom'>` | optional | | -| **routing** | `{ condition: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; version: string; priority?: integer }[]` | optional | | +| **routing** | `{ condition: string \| object; version: string; priority?: integer }[]` | optional | | | **rollout** | `{ enabled?: boolean; strategy: Enum<'percentage' \| 'blue-green' \| 'canary'>; percentage?: number; duration?: integer }` | optional | | @@ -138,7 +138,7 @@ Compatibility level between versions | :--- | :--- | :--- | :--- | | **pluginId** | `string` | ✅ | | | **currentVersion** | `string` | ✅ | | -| **compatibilityMatrix** | `{ from: string; to: string; compatibility: Enum<'fully-compatible' \| 'backward-compatible' \| 'deprecated-compatible' \| … +2 more>; breakingChanges?: { introducedIn: string; type: Enum<'api-removed' \| 'api-renamed' \| 'api-signature-changed' \| 'behavior-changed' \| … +3 more>; description: string; migrationGuide?: string; … }[]; … }[]` | ✅ | | +| **compatibilityMatrix** | `{ from: string; to: string; compatibility: Enum<'fully-compatible' \| 'backward-compatible' \| 'deprecated-compatible' \| … +2 more>; breakingChanges?: object[]; … }[]` | ✅ | | | **supportedVersions** | `{ version: string; supported: boolean; endOfLife?: string; securitySupport: boolean }[]` | ✅ | | | **minimumCompatibleVersion** | `string` | optional | Oldest version that can be directly upgraded | @@ -153,7 +153,7 @@ Compatibility level between versions | :--- | :--- | :--- | :--- | | **success** | `boolean` | ✅ | | | **resolved** | `{ pluginId: string; version: string; resolvedVersion: string }[]` | optional | | -| **conflicts** | `{ type: Enum<'version-mismatch' \| 'missing-dependency' \| 'circular-dependency' \| … +2 more>; plugins: { pluginId: string; version: string; requirement?: string }[]; description: string; resolutions?: { strategy: Enum<'upgrade' \| 'downgrade' \| 'replace' \| 'disable' \| 'manual'>; description: string; automaticResolution: boolean; riskLevel: Enum<'low' \| 'medium' \| 'high'> }[]; … }[]` | optional | | +| **conflicts** | `{ type: Enum<'version-mismatch' \| 'missing-dependency' \| 'circular-dependency' \| … +2 more>; plugins: object[]; description: string; resolutions?: object[]; … }[]` | optional | | | **warnings** | `string[]` | optional | | | **installationOrder** | `string[]` | optional | Plugin IDs in order they should be installed | | **dependencyGraph** | `Record` | optional | Map of plugin ID to its dependencies | @@ -174,7 +174,7 @@ Compatibility level between versions | **releaseNotes** | `string` | optional | | | **breakingChanges** | `{ introducedIn: string; type: Enum<'api-removed' \| 'api-renamed' \| 'api-signature-changed' \| 'behavior-changed' \| … +3 more>; description: string; migrationGuide?: string; … }[]` | optional | | | **deprecations** | `{ feature: string; deprecatedIn: string; removeIn?: string; reason: string; … }[]` | optional | | -| **compatibilityMatrix** | `{ from: string; to: string; compatibility: Enum<'fully-compatible' \| 'backward-compatible' \| 'deprecated-compatible' \| … +2 more>; breakingChanges?: { introducedIn: string; type: Enum<'api-removed' \| 'api-renamed' \| 'api-signature-changed' \| 'behavior-changed' \| … +3 more>; description: string; migrationGuide?: string; … }[]; … }[]` | optional | | +| **compatibilityMatrix** | `{ from: string; to: string; compatibility: Enum<'fully-compatible' \| 'backward-compatible' \| 'deprecated-compatible' \| … +2 more>; breakingChanges?: object[]; … }[]` | optional | | | **securityFixes** | `{ cve?: string; severity: Enum<'critical' \| 'high' \| 'medium' \| 'low'>; description: string; fixedIn: string }[]` | optional | | | **statistics** | `{ downloads?: integer; installations?: integer; ratings?: number }` | optional | | | **support** | `{ status: Enum<'active' \| 'maintenance' \| 'deprecated' \| 'eol'>; endOfLife?: string; securitySupport: boolean }` | ✅ | | diff --git a/content/docs/references/qa/testing.mdx b/content/docs/references/qa/testing.mdx index 08f31aca25..a5606a5e62 100644 --- a/content/docs/references/qa/testing.mdx +++ b/content/docs/references/qa/testing.mdx @@ -106,9 +106,9 @@ A complete test scenario with setup, execution steps, and teardown | **name** | `string` | ✅ | Scenario name for test reports | | **description** | `string` | optional | Detailed description of the test scenario | | **tags** | `string[]` | optional | Tags for filtering and categorization (e.g. "critical", "regression", "crm") | -| **setup** | `{ name: string; description?: string; action: object; assertions?: { field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'is_null' \| 'not_null' \| … +5 more>; expectedValue: any }[]; … }[]` | optional | Steps to run before main test (preconditions) | -| **steps** | `{ name: string; description?: string; action: object; assertions?: { field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'is_null' \| 'not_null' \| … +5 more>; expectedValue: any }[]; … }[]` | ✅ | Main test sequence to execute | -| **teardown** | `{ name: string; description?: string; action: object; assertions?: { field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'is_null' \| 'not_null' \| … +5 more>; expectedValue: any }[]; … }[]` | optional | Steps to cleanup after test execution | +| **setup** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to run before main test (preconditions) | +| **steps** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | ✅ | Main test sequence to execute | +| **teardown** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to cleanup after test execution | | **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario | diff --git a/content/docs/references/security/explain.mdx b/content/docs/references/security/explain.mdx index cf20bd3926..9d1b548d2e 100644 --- a/content/docs/references/security/explain.mdx +++ b/content/docs/references/security/explain.mdx @@ -123,7 +123,7 @@ ADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object | **verdict** | `Enum<'grants' \| 'denies' \| 'narrows' \| 'widens' \| 'neutral' \| 'not_applicable'>` | ✅ | | | **detail** | `string` | ✅ | | | **contributors** | `{ kind: Enum<'permission_set' \| 'position' \| 'system'>; name: string; via?: string; state?: Enum<'active' \| 'expired'> }[]` | ✅ | | -| **record** | `{ outcome: Enum<'admitted' \| 'excluded' \| 'not_evaluated'>; rowFilter?: any; matchesRecord?: boolean; rules: { kind: Enum<'tenant_filter' \| 'owd_baseline' \| 'ownership' \| 'record_share' \| 'sharing_rule' \| … +3 more>; name: string; grants?: Enum<'read' \| 'edit' \| 'full'>; via?: string; … }[]; … }` | optional | Row-level determination for the specific record under explanation; set only for record-grained requests. | +| **record** | `{ outcome: Enum<'admitted' \| 'excluded' \| 'not_evaluated'>; rowFilter?: any; matchesRecord?: boolean; rules: object[]; … }` | optional | Row-level determination for the specific record under explanation; set only for record-grained requests. | --- diff --git a/content/docs/references/studio/object-designer.mdx b/content/docs/references/studio/object-designer.mdx index e99b9c8b05..48e162fcdf 100644 --- a/content/docs/references/studio/object-designer.mdx +++ b/content/docs/references/studio/object-designer.mdx @@ -190,10 +190,10 @@ ER diagram layout algorithm | :--- | :--- | :--- | :--- | | **defaultView** | `Enum<'field-editor' \| 'relationship-mapper' \| 'er-diagram' \| 'object-manager'>` | ✅ | Default view | | **fieldEditor** | `{ inlineEditing: boolean; dragReorder: boolean; showFieldGroups: boolean; showPropertyPanel: boolean; … }` | ✅ | Field editor configuration | -| **relationshipMapper** | `{ visualCreation: boolean; showReverseRelationships: boolean; showCascadeWarnings: boolean; displayConfig: { type: Enum<'lookup' \| 'master_detail' \| 'tree'>; lineStyle: Enum<'solid' \| 'dashed' \| 'dotted'>; color: string; highlightColor: string; … }[] }` | ✅ | Relationship mapper configuration | +| **relationshipMapper** | `{ visualCreation: boolean; showReverseRelationships: boolean; showCascadeWarnings: boolean; displayConfig: object[] }` | ✅ | Relationship mapper configuration | | **erDiagram** | `{ enabled: boolean; layout: Enum<'force' \| 'hierarchy' \| 'grid' \| 'circular'>; nodeDisplay: object; showMinimap: boolean; … }` | ✅ | ER diagram configuration | | **objectManager** | `{ defaultDisplayMode: Enum<'table' \| 'cards' \| 'tree'>; defaultSortField: Enum<'name' \| 'label' \| 'fieldCount' \| 'updatedAt'>; defaultSortDirection: Enum<'asc' \| 'desc'>; defaultFilter: object; … }` | ✅ | Object manager configuration | -| **objectPreview** | `{ tabs: { key: string; label: string; icon?: string; enabled: boolean; … }[]; defaultTab: string; showHeader: boolean; showBreadcrumbs: boolean }` | ✅ | Object preview configuration | +| **objectPreview** | `{ tabs: object[]; defaultTab: string; showHeader: boolean; showBreadcrumbs: boolean }` | ✅ | Object preview configuration | --- diff --git a/content/docs/references/studio/plugin.mdx b/content/docs/references/studio/plugin.mdx index 2e05ce00bc..a56e85d4f9 100644 --- a/content/docs/references/studio/plugin.mdx +++ b/content/docs/references/studio/plugin.mdx @@ -208,7 +208,7 @@ const result = ActionContributionSchema.parse(data); | **version** | `string` | ✅ | Plugin version | | **description** | `string` | optional | Plugin description | | **author** | `string` | optional | Author | -| **contributes** | `{ metadataViewers: { id: string; metadataTypes: string[]; label: string; priority: number; … }[]; sidebarGroups: { key: string; label: string; icon?: string; metadataTypes: string[]; … }[]; actions: { id: string; label: string; icon?: string; location: Enum<'toolbar' \| 'contextMenu' \| 'commandPalette'>; … }[]; metadataIcons: { metadataType: string; label: string; icon: string }[]; … }` | ✅ | | +| **contributes** | `{ metadataViewers: object[]; sidebarGroups: object[]; actions: object[]; metadataIcons: object[]; … }` | ✅ | | --- diff --git a/content/docs/references/system/book.mdx b/content/docs/references/system/book.mdx index 93b96c83e1..f8f0bb8e1c 100644 --- a/content/docs/references/system/book.mdx +++ b/content/docs/references/system/book.mdx @@ -52,7 +52,7 @@ const result = BookSchema.parse(data); | **icon** | `string` | optional | | | **order** | `number` | optional | Orders books within the portal | | **audience** | `'org' \| 'public' \| { permissionSet: string }` | optional | Access audience; defaults to 'org' (inherits package grant) | -| **groups** | `{ key: string; label: string; order?: number; include?: string \| { tag: string }; … }[]` | ✅ | The spine: ordered sections. Two levels total. | +| **groups** | `{ key: string; label: string; order?: number; include?: string \| object; … }[]` | ✅ | The spine: ordered sections. Two levels total. | | **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). | | **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. | | **_lockSource** | `Enum<'artifact' \| 'package' \| 'env-forced'>` | optional | Layer that set _lock (artifact \| package \| env-forced). | diff --git a/content/docs/references/system/cache.mdx b/content/docs/references/system/cache.mdx index 5639ae575f..813c09590a 100644 --- a/content/docs/references/system/cache.mdx +++ b/content/docs/references/system/cache.mdx @@ -172,7 +172,7 @@ Distributed cache configuration with consistency and avalanche prevention | **encryption** | `boolean` | optional | Enable encryption for cached data | | **consistency** | `Enum<'write_through' \| 'write_behind' \| 'write_around' \| 'refresh_ahead'>` | optional | Distributed cache consistency strategy | | **avalanchePrevention** | `{ jitterTtl?: object; circuitBreaker?: object; lockout?: object }` | optional | Cache avalanche and stampede prevention | -| **warmup** | `{ enabled?: boolean; strategy?: Enum<'eager' \| 'lazy' \| 'scheduled'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; patterns?: string[]; … }` | optional | Cache warmup strategy | +| **warmup** | `{ enabled?: boolean; strategy?: Enum<'eager' \| 'lazy' \| 'scheduled'>; schedule?: string \| object; patterns?: string[]; … }` | optional | Cache warmup strategy | --- diff --git a/content/docs/references/system/change-management.mdx b/content/docs/references/system/change-management.mdx index bb764ce3e3..077932c2b6 100644 --- a/content/docs/references/system/change-management.mdx +++ b/content/docs/references/system/change-management.mdx @@ -67,11 +67,11 @@ const result = ChangeImpactSchema.parse(data); | **requestedBy** | `string` | ✅ | Requester user ID | | **requestedAt** | `number` | ✅ | Request timestamp | | **impact** | `{ level: Enum<'low' \| 'medium' \| 'high' \| 'critical'>; affectedSystems: string[]; affectedUsers?: number; downtime?: object }` | ✅ | Impact assessment | -| **implementation** | `{ description: string; steps: { order: number; description: string; estimatedMinutes: number }[]; testing?: string }` | ✅ | Implementation plan | -| **rollbackPlan** | `{ description: string; steps: { order: number; description: string; estimatedMinutes: number }[]; testProcedure?: string }` | ✅ | Rollback plan | +| **implementation** | `{ description: string; steps: object[]; testing?: string }` | ✅ | Implementation plan | +| **rollbackPlan** | `{ description: string; steps: object[]; testProcedure?: string }` | ✅ | Rollback plan | | **schedule** | `{ plannedStart: number; plannedEnd: number; actualStart?: number; actualEnd?: number }` | optional | Schedule | | **securityImpact** | `{ assessed: boolean; riskLevel?: Enum<'none' \| 'low' \| 'medium' \| 'high' \| 'critical'>; affectedDataClassifications?: Enum<'pii' \| 'phi' \| 'pci' \| 'financial' \| 'confidential' \| 'internal' \| 'public'>[]; requiresSecurityApproval: boolean; … }` | optional | Security impact assessment per ISO 27001:2022 A.8.32 | -| **approval** | `{ required: boolean; approvers: { userId: string; approvedAt?: number; comments?: string }[] }` | optional | Approval workflow | +| **approval** | `{ required: boolean; approvers: object[] }` | optional | Approval workflow | | **attachments** | `{ name: string; url: string }[]` | optional | Attachments | | **metadata** | `Record` | optional | Custom metadata key-value pairs for extensibility | diff --git a/content/docs/references/system/collaboration.mdx b/content/docs/references/system/collaboration.mdx index 494bf360be..7ba7593e14 100644 --- a/content/docs/references/system/collaboration.mdx +++ b/content/docs/references/system/collaboration.mdx @@ -102,7 +102,7 @@ const result = AwarenessEventSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **state** | `{ type: 'lww-register'; value: any; timestamp: string; replicaId: string; … } \| { type: 'g-counter'; counts: Record } \| { type: 'pn-counter'; positive: Record; negative: Record } \| { type: 'or-set'; elements: { value: any; timestamp: string; replicaId: string; uid: string; … }[] } \| … +1 more` | ✅ | Merged CRDT state | +| **state** | `{ type: 'lww-register'; value: any; timestamp: string; replicaId: string; … } \| { type: 'g-counter'; counts: Record } \| { type: 'pn-counter'; positive: Record; negative: Record } \| { type: 'or-set'; elements: object[] } \| … +1 more` | ✅ | Merged CRDT state | | **conflicts** | `{ type: string; description: string; resolved: boolean }[]` | optional | Conflicts encountered during merge | diff --git a/content/docs/references/system/disaster-recovery.mdx b/content/docs/references/system/disaster-recovery.mdx index 1144635220..2c7e42dbed 100644 --- a/content/docs/references/system/disaster-recovery.mdx +++ b/content/docs/references/system/disaster-recovery.mdx @@ -97,10 +97,10 @@ Complete disaster recovery plan configuration | **enabled** | `boolean` | optional | Enable disaster recovery plan | | **rpo** | `{ value: number; unit?: Enum<'seconds' \| 'minutes' \| 'hours'> }` | ✅ | Recovery Point Objective | | **rto** | `{ value: number; unit?: Enum<'seconds' \| 'minutes' \| 'hours'> }` | ✅ | Recovery Time Objective | -| **backup** | `{ strategy?: Enum<'full' \| 'incremental' \| 'differential'>; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; retention: object; destination: object; … }` | ✅ | Backup configuration | +| **backup** | `{ strategy?: Enum<'full' \| 'incremental' \| 'differential'>; schedule?: string \| object; retention: object; destination: object; … }` | ✅ | Backup configuration | | **failover** | `{ mode?: Enum<'active_passive' \| 'active_active' \| 'pilot_light' \| 'warm_standby'>; autoFailover?: boolean; healthCheckInterval?: number; failureThreshold?: number; … }` | optional | Multi-region failover configuration | | **replication** | `{ mode?: Enum<'synchronous' \| 'asynchronous' \| 'semi_synchronous'>; maxLagSeconds?: number; includeObjects?: string[]; excludeObjects?: string[] }` | optional | Data replication settings | -| **testing** | `{ enabled?: boolean; schedule?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; notificationChannel?: string }` | optional | Automated disaster recovery testing | +| **testing** | `{ enabled?: boolean; schedule?: string \| object; notificationChannel?: string }` | optional | Automated disaster recovery testing | | **runbookUrl** | `string` | optional | URL to disaster recovery runbook/playbook | | **contacts** | `{ name: string; role: string; email?: string; phone?: string }[]` | optional | Emergency contact list for DR incidents | diff --git a/content/docs/references/system/incident-response.mdx b/content/docs/references/system/incident-response.mdx index d9ed052ee1..9c6499c074 100644 --- a/content/docs/references/system/incident-response.mdx +++ b/content/docs/references/system/incident-response.mdx @@ -154,7 +154,7 @@ Organization-level incident response policy per ISO 27001:2022 | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **enabled** | `boolean` | ✅ | Enable incident response management | -| **notificationMatrix** | `{ rules: { severity: Enum<'critical' \| 'high' \| 'medium' \| 'low'>; channels: Enum<'email' \| 'sms' \| 'slack' \| 'pagerduty' \| 'webhook'>[]; recipients: string[]; withinMinutes: number; … }[]; escalationTimeoutMinutes: number; escalationChain: string[] }` | ✅ | Notification and escalation matrix | +| **notificationMatrix** | `{ rules: object[]; escalationTimeoutMinutes: number; escalationChain: string[] }` | ✅ | Notification and escalation matrix | | **defaultResponseTeam** | `string` | ✅ | Default incident response team or role | | **triageDeadlineHours** | `number` | ✅ | Maximum hours to begin triage after detection | | **requirePostIncidentReview** | `boolean` | ✅ | Require post-incident review for all incidents | diff --git a/content/docs/references/system/job.mdx b/content/docs/references/system/job.mdx index 99afb5395d..0986570879 100644 --- a/content/docs/references/system/job.mdx +++ b/content/docs/references/system/job.mdx @@ -58,7 +58,7 @@ const result = CronScheduleSchema.parse(data); | **name** | `string` | ✅ | Job name (snake_case) | | **label** | `string` | optional | Human-readable label | | **description** | `string` | optional | Job description / purpose | -| **schedule** | `{ type: 'cron'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; timezone?: string } \| { type: 'interval'; intervalMs: integer } \| { type: 'once'; at: string }` | ✅ | Job schedule configuration | +| **schedule** | `{ type: 'cron'; expression: string \| object; timezone?: string } \| { type: 'interval'; intervalMs: integer } \| { type: 'once'; at: string }` | ✅ | Job schedule configuration | | **handler** | `string` | ✅ | Handler function name (must match a key in `defineStack({ functions })`) | | **retryPolicy** | `{ maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; maxRetryDelayMs?: integer; … }` | optional | Retry policy: failed runs (including timeouts) are retried with exponential backoff (delay = min(backoffMs * backoffMultiplier^(retry-1), maxRetryDelayMs), optionally jittered) up to maxRetries retries after the initial attempt (#3494). Omit the block for a single attempt; declaring it without `maxRetries` also means no retry since 17.0.0 (#4661) — state a count to opt in. | | **timeout** | `integer` | optional | Per-attempt time limit in milliseconds; an over-limit run is recorded with execution status "timeout" (#3494). The in-flight handler is abandoned, not forcibly cancelled. Omit for no time limit. | diff --git a/content/docs/references/system/metrics.mdx b/content/docs/references/system/metrics.mdx index ce0e0337e3..c1975e9bde 100644 --- a/content/docs/references/system/metrics.mdx +++ b/content/docs/references/system/metrics.mdx @@ -98,8 +98,8 @@ Metric data point | **timestamp** | `string` | ✅ | Observation timestamp | | **value** | `number` | optional | Metric value | | **labels** | `Record` | optional | Metric labels | -| **histogram** | `{ count: integer; sum: number; buckets: { upperBound: number; count: integer }[] }` | optional | | -| **summary** | `{ count: integer; sum: number; quantiles: { quantile: number; value: number }[] }` | optional | | +| **histogram** | `{ count: integer; sum: number; buckets: object[] }` | optional | | +| **summary** | `{ count: integer; sum: number; quantiles: object[] }` | optional | | --- @@ -231,7 +231,7 @@ Metrics configuration | **slos** | `{ name: string; label: string; description?: string; sli: string; … }[]` | optional | | | **exports** | `{ type: Enum<'prometheus' \| 'openmetrics' \| 'graphite' \| 'statsd' \| 'influxdb' \| 'datadog' \| … +5 more>; endpoint?: string; interval?: integer; batch?: object; … }[]` | optional | | | **collectionInterval** | `integer` | optional | | -| **retention** | `{ period?: integer; downsampling?: { afterSeconds: integer; resolution: integer }[] }` | optional | | +| **retention** | `{ period?: integer; downsampling?: object[] }` | optional | | | **cardinalityLimits** | `{ maxLabelCombinations?: integer; onLimitExceeded?: Enum<'drop' \| 'sample' \| 'alert'> }` | optional | | @@ -271,7 +271,7 @@ Service Level Objective | **sli** | `string` | ✅ | SLI name | | **target** | `number` | ✅ | Target percentage | | **period** | `{ type: Enum<'rolling' \| 'calendar'>; duration?: integer; calendar?: Enum<'daily' \| 'weekly' \| 'monthly' \| 'quarterly' \| 'yearly'> }` | ✅ | Time period | -| **errorBudget** | `{ enabled: boolean; alertThreshold: number; burnRateWindows?: { window: integer; threshold: number }[] }` | optional | | +| **errorBudget** | `{ enabled: boolean; alertThreshold: number; burnRateWindows?: object[] }` | optional | | | **alerts** | `{ name: string; severity: Enum<'info' \| 'warning' \| 'critical'>; condition: object }[]` | ✅ | | | **enabled** | `boolean` | ✅ | | diff --git a/content/docs/references/system/object-storage.mdx b/content/docs/references/system/object-storage.mdx index 09598c58f5..4ee5db46cd 100644 --- a/content/docs/references/system/object-storage.mdx +++ b/content/docs/references/system/object-storage.mdx @@ -69,7 +69,7 @@ const result = AccessControlConfigSchema.parse(data); | **versioning** | `boolean` | ✅ | Enable object versioning | | **encryption** | `{ enabled: boolean; algorithm: Enum<'AES256' \| 'aws:kms' \| 'azure:kms' \| 'gcp:kms'>; kmsKeyId?: string }` | optional | Server-side encryption configuration | | **accessControl** | `{ acl: Enum<'private' \| 'public_read' \| 'public_read_write' \| 'authenticated_read' \| … +2 more>; allowedOrigins?: string[]; allowedMethods?: Enum<'GET' \| 'PUT' \| 'POST' \| 'DELETE' \| 'HEAD'>[]; allowedHeaders?: string[]; … }` | optional | Access control configuration | -| **lifecyclePolicy** | `{ enabled: boolean; rules: { id: string; enabled: boolean; action: Enum<'transition' \| 'delete' \| 'abort'>; prefix?: string; … }[] }` | optional | Lifecycle policy configuration | +| **lifecyclePolicy** | `{ enabled: boolean; rules: object[] }` | optional | Lifecycle policy configuration | | **multipartConfig** | `{ enabled: boolean; partSize: number; maxParts: number; threshold: number; … }` | optional | Multipart upload configuration | | **tags** | `Record` | optional | Bucket tags for organization | | **description** | `string` | optional | Bucket description | diff --git a/content/docs/references/system/search-engine.mdx b/content/docs/references/system/search-engine.mdx index feaba3219d..c6731fb110 100644 --- a/content/docs/references/system/search-engine.mdx +++ b/content/docs/references/system/search-engine.mdx @@ -64,7 +64,7 @@ Top-level full-text search engine configuration | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **provider** | `Enum<'elasticsearch' \| 'algolia' \| 'meilisearch' \| 'typesense' \| 'opensearch'>` | ✅ | Search engine backend provider | -| **indexes** | `{ indexName: string; objectName: string; fields: { name: string; type: Enum<'text' \| 'keyword' \| 'number' \| 'date' \| 'boolean' \| 'geo'>; analyzer?: string; searchable: boolean; … }[]; replicas: number; … }[]` | ✅ | Search index definitions | +| **indexes** | `{ indexName: string; objectName: string; fields: object[]; replicas: number; … }[]` | ✅ | Search index definitions | | **analyzers** | `Record; language?: string; stopwords?: string[]; customFilters?: string[] }>` | optional | Named text analyzer configurations | | **facets** | `{ field: string; maxValues: number; sort: Enum<'count' \| 'alpha'> }[]` | optional | Faceted search configurations | | **typoTolerance** | `boolean` | ✅ | Enable typo-tolerant search | diff --git a/content/docs/references/system/tracing.mdx b/content/docs/references/system/tracing.mdx index 06ea56e116..d2523cbc00 100644 --- a/content/docs/references/system/tracing.mdx +++ b/content/docs/references/system/tracing.mdx @@ -297,7 +297,7 @@ Trace sampling configuration | **ratio** | `number` | optional | Sample ratio (0-1) | | **rateLimit** | `number` | optional | Traces per second | | **parentBased** | `{ whenParentSampled?: Enum<'always_on' \| 'always_off' \| 'trace_id_ratio' \| 'rate_limiting' \| 'parent_based' \| … +3 more>; whenParentNotSampled?: Enum<'always_on' \| 'always_off' \| 'trace_id_ratio' \| 'rate_limiting' \| 'parent_based' \| … +3 more>; root?: Enum<'always_on' \| 'always_off' \| 'trace_id_ratio' \| 'rate_limiting' \| 'parent_based' \| … +3 more>; rootRatio?: number }` | optional | | -| **composite** | `{ strategy: Enum<'always_on' \| 'always_off' \| 'trace_id_ratio' \| 'rate_limiting' \| 'parent_based' \| … +3 more>; ratio?: number; condition?: Record \| string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }[]` | optional | | +| **composite** | `{ strategy: Enum<'always_on' \| 'always_off' \| 'trace_id_ratio' \| 'rate_limiting' \| 'parent_based' \| … +3 more>; ratio?: number; condition?: Record \| string \| object }[]` | optional | | | **rules** | `{ name: string; match?: object; decision: Enum<'drop' \| 'record_only' \| 'record_and_sample'>; rate?: number }[]` | optional | | | **customSamplerId** | `string` | optional | Custom sampler identifier | diff --git a/content/docs/references/system/translation.mdx b/content/docs/references/system/translation.mdx index ecae7950d9..553e8e7841 100644 --- a/content/docs/references/system/translation.mdx +++ b/content/docs/references/system/translation.mdx @@ -85,7 +85,7 @@ Translation data for a single object | **description** | `string` | optional | Translated object description | | **fields** | `Record }>` | optional | Field-level translations | | **_views** | `Record` | optional | View translations keyed by view name | -| **_actions** | `Record }>; … }>` | optional | Action translations keyed by action name | +| **_actions** | `Record; … }>` | optional | Action translations keyed by action name | | **_sections** | `Record` | optional | Section translations keyed by section name | | **_tabs** | `Record` | optional | Filter-preset tab translations keyed by tab name | @@ -140,14 +140,14 @@ Translation data for objects, apps, and UI messages | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **objects** | `Record }>; … }>` | optional | Object translations keyed by object name | -| **apps** | `Record }>` | optional | App translations keyed by app name | +| **objects** | `Record; … }>` | optional | Object translations keyed by object name | +| **apps** | `Record }>` | optional | App translations keyed by app name | | **messages** | `Record` | optional | UI message translations keyed by message ID | -| **globalActions** | `Record }>; … }>` | optional | Global action translations keyed by action name | -| **dashboards** | `Record; widgets?: Record }>` | optional | Dashboard translations keyed by dashboard name | +| **globalActions** | `Record; … }>` | optional | Global action translations keyed by action name | +| **dashboards** | `Record; widgets?: Record }>` | optional | Dashboard translations keyed by dashboard name | | **pages** | `Record` | optional | Page translations keyed by page name | -| **settings** | `Record; keys?: Record }>; … }>` | optional | Settings manifest translations keyed by namespace | -| **metadataForms** | `Record; fields?: Record }>` | optional | Translations for metadata-type configuration forms keyed by metadata type | +| **settings** | `Record; keys?: Record; … }>` | optional | Settings manifest translations keyed by namespace | +| **metadataForms** | `Record; fields?: Record }>` | optional | Translations for metadata-type configuration forms keyed by metadata type | | **settingsCommon** | `{ sourceLabels?: object }` | optional | Cross-namespace Settings UI strings | @@ -193,14 +193,14 @@ One locale of translations — the `translation` metadata type | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **objects** | `Record }>; … }>` | optional | Object translations keyed by object name | -| **apps** | `Record }>` | optional | App translations keyed by app name | +| **objects** | `Record; … }>` | optional | Object translations keyed by object name | +| **apps** | `Record }>` | optional | App translations keyed by app name | | **messages** | `Record` | optional | UI message translations keyed by message ID | -| **globalActions** | `Record }>; … }>` | optional | Global action translations keyed by action name | -| **dashboards** | `Record; widgets?: Record }>` | optional | Dashboard translations keyed by dashboard name | +| **globalActions** | `Record; … }>` | optional | Global action translations keyed by action name | +| **dashboards** | `Record; widgets?: Record }>` | optional | Dashboard translations keyed by dashboard name | | **pages** | `Record` | optional | Page translations keyed by page name | -| **settings** | `Record; keys?: Record }>; … }>` | optional | Settings manifest translations keyed by namespace | -| **metadataForms** | `Record; fields?: Record }>` | optional | Translations for metadata-type configuration forms keyed by metadata type | +| **settings** | `Record; keys?: Record; … }>` | optional | Settings manifest translations keyed by namespace | +| **metadataForms** | `Record; fields?: Record }>` | optional | Translations for metadata-type configuration forms keyed by metadata type | | **settingsCommon** | `{ sourceLabels?: object }` | optional | Cross-namespace Settings UI strings | | **locale** | `string` | ✅ | BCP-47 locale this item translates (e.g. "zh-CN") | | **name** | `string` | optional | Item name — conventionally the locale code (`zh-CN`); the runtime sync falls back to it when `locale` is absent | diff --git a/content/docs/references/ui/action.mdx b/content/docs/references/ui/action.mdx index 49fc476b94..3bac9f7988 100644 --- a/content/docs/references/ui/action.mdx +++ b/content/docs/references/ui/action.mdx @@ -86,7 +86,7 @@ const result = ActionSchema.parse(data); | **requiredPermissions** | `string[]` | optional | [ADR-0066 D4] Capabilities required to invoke this action. Enforced with 403 on the platform action route (script/flow/modal + MCP) and mirrored as a UI hide; a `type: api` action pointed at a custom endpoint must re-check it there. | | **shortcut** | `never` | optional | [REMOVED] `action.shortcut` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it never triggered anything: no keydown listener feeds ActionEngine.getShortcuts(), and objectui's keyboard stack (useKeyboardShortcuts) is hand-registered and never consults action metadata. Delete the key. For a real shortcut, register the key in the Console keyboard stack and have its handler invoke the action by name. | | **bulkEnabled** | `never` | optional | [REMOVED] `action.bulkEnabled` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — the multi-select toolbar is driven by the LIST VIEW's `bulkActions` / `bulkActionDefs`, never by this flag, so setting it changed nothing. Delete the key and declare the action in the view's `bulkActions` instead. | -| **ai** | `{ exposed?: boolean; description?: string; category?: Enum<'data' \| 'action' \| 'flow' \| 'integration' \| 'vector_search' \| 'analytics' \| 'utility'>; paramHints?: Record; … }` | optional | AI exposure (opt-in). Set ai.exposed=true + ai.description to make this callable by agents. | +| **ai** | `{ exposed?: boolean; description?: string; category?: Enum<'data' \| 'action' \| 'flow' \| 'integration' \| 'vector_search' \| 'analytics' \| 'utility'>; paramHints?: Record; … }` | optional | AI exposure (opt-in). Set ai.exposed=true + ai.description to make this callable by agents. | | **recordIdParam** | `string` | optional | Body key to inject the row id into when running from a list_item context. | | **recordIdField** | `string` | optional | Row field whose value seeds recordIdParam. Defaults to "id". | | **bodyShape** | `'flat' \| { wrap: string }` | optional | Body wrapping: flat (default) or `{ wrap: key }` to nest user-collected params under a key. | @@ -166,7 +166,7 @@ const result = ActionSchema.parse(data); | **label** | `string \| Record` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time | | **type** | `Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| 'markdown' \| 'html' \| 'richtext' \| 'number' \| 'currency' \| 'percent' \| 'date' \| … +35 more>` | optional | | | **required** | `boolean` | optional | | -| **options** | `{ label: string \| Record; value: string; visibleWhen?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }[]` | optional | | +| **options** | `{ label: string \| Record; value: string; visibleWhen?: string \| object }[]` | optional | | | **placeholder** | `string` | optional | | | **helpText** | `string` | optional | | | **defaultValue** | `any` | optional | | diff --git a/content/docs/references/ui/app.mdx b/content/docs/references/ui/app.mdx index 8fe4c05220..c5b94429ad 100644 --- a/content/docs/references/ui/app.mdx +++ b/content/docs/references/ui/app.mdx @@ -120,7 +120,7 @@ const result = ActionNavItemSchema.parse(data); | **id** | `string` | ✅ | Selector id; selected value is exposed as the nav template var `{}` | | **label** | `string \| Record` | ✅ | Dropdown label | | **icon** | `string` | optional | Icon name | -| **optionsSource** | `{ endpoint: string; valueKey: string; labelKey: string; filter?: { key: string; op: Enum<'eq' \| 'ne' \| 'in' \| 'nin'>; value: string \| string[] }[] }` | ✅ | Option data source | +| **optionsSource** | `{ endpoint: string; valueKey: string; labelKey: string; filter?: object[] }` | ✅ | Option data source | | **allValue** | `string` | ✅ | Sentinel value meaning "no concrete selection yet" (empty string is almost always right) | | **persist** | `Enum<'query' \| 'session' \| 'none'>` | ✅ | Persist selection via URL query, sessionStorage, or not at all | diff --git a/content/docs/references/ui/component.mdx b/content/docs/references/ui/component.mdx index 35ac7b6c99..04ca6e1ef5 100644 --- a/content/docs/references/ui/component.mdx +++ b/content/docs/references/ui/component.mdx @@ -256,7 +256,7 @@ const result = AIChatWindowProps.parse(data); | :--- | :--- | :--- | :--- | | **type** | `Enum<'line' \| 'card' \| 'pill'>` | optional | | | **position** | `Enum<'top' \| 'left'>` | optional | | -| **items** | `{ label: string \| Record; icon?: string; visibleWhen?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; value?: string; … }[]` | ✅ | | +| **items** | `{ label: string \| Record; icon?: string; visibleWhen?: string \| object; value?: string; … }[]` | ✅ | | | **aria** | `{ ariaLabel?: string \| Record; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes | diff --git a/content/docs/references/ui/dashboard.mdx b/content/docs/references/ui/dashboard.mdx index b7430a97d9..0844861eb8 100644 --- a/content/docs/references/ui/dashboard.mdx +++ b/content/docs/references/ui/dashboard.mdx @@ -32,7 +32,7 @@ const result = DashboardSchema.parse(data); | **name** | `string` | ✅ | Dashboard unique name | | **label** | `string \| Record` | ✅ | Dashboard label | | **description** | `string \| Record` | optional | Dashboard description | -| **header** | `{ showTitle: boolean; showDescription: boolean; actions?: { label: string \| Record; actionUrl: string; actionType?: Enum<'script' \| 'url' \| 'modal' \| 'flow' \| 'api' \| 'form'>; icon?: string }[] }` | optional | Dashboard header configuration | +| **header** | `{ showTitle: boolean; showDescription: boolean; actions?: object[] }` | optional | Dashboard header configuration | | **widgets** | `{ id: string; title?: string \| Record; description?: string \| Record; type: Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| … +13 more>; … }[]` | ✅ | Widgets to display | | **columns** | `integer` | optional | Number of grid columns (default 12) | | **gap** | `integer` | optional | Grid gap in Tailwind spacing units | diff --git a/content/docs/references/ui/page.mdx b/content/docs/references/ui/page.mdx index d9985bac89..a41db28292 100644 --- a/content/docs/references/ui/page.mdx +++ b/content/docs/references/ui/page.mdx @@ -54,7 +54,7 @@ Interface-level page configuration (Airtable parity) | **levels** | `integer` | optional | Number of hierarchy levels to display | | **sourceView** | `string` | optional | @deprecated Legacy named-view inheritance. Define columns/sort/filterBy on the page instead. | | **appearance** | `{ showDescription: boolean; allowedVisualizations?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>[] }` | optional | Appearance and visualization configuration | -| **userFilters** | `{ element: Enum<'dropdown' \| 'tabs' \| 'toggle'>; fields?: { field: string; label?: string \| Record; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: { value: string \| number \| boolean; label: string \| Record; color?: string }[]; … }[]; tabs?: { name: string; label?: string \| Record; icon?: string; view?: string; … }[]; showAllRecords?: boolean; … }` | optional | End-user quick-filter bar for this page (overrides the source view's userFilters) | +| **userFilters** | `{ element: Enum<'dropdown' \| 'tabs' \| 'toggle'>; fields?: object[]; tabs?: object[]; showAllRecords?: boolean; … }` | optional | End-user quick-filter bar for this page (overrides the source view's userFilters) | | **userActions** | `{ sort: boolean; search: boolean; filter: boolean; refresh: boolean; … }` | optional | User action toggles | | **addRecord** | `{ enabled: boolean; position: Enum<'top' \| 'bottom' \| 'both'>; mode: Enum<'inline' \| 'form' \| 'modal'>; formView?: string }` | optional | Add record entry point configuration | | **buttons** | `string[]` | optional | Toolbar buttons — names of the source object's actions to surface in the page toolbar | @@ -79,13 +79,13 @@ Interface-level page configuration (Airtable parity) | **variables** | `{ name: string; type?: Enum<'string' \| 'number' \| 'boolean' \| 'object' \| 'array' \| 'record_id'>; defaultValue?: any; source?: string }[]` | optional | Local page state, exposed to expressions as `page.` and writable by interactive elements via `source` (master/detail, filtered dashboards). | | **object** | `string` | optional | Bound object (for Record pages) | | **template** | `string` | optional | Layout template name (e.g. "header-sidebar-main") | -| **regions** | `{ name: string; width?: Enum<'small' \| 'medium' \| 'large' \| 'full'>; components: { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … }[] }[]` | optional | Layout regions (header, main, sidebar, footer) with their components. Optional — list pages use interfaceConfig, slotted pages use slots, and an empty full page falls back to the synthesized default layout. | +| **regions** | `{ name: string; width?: Enum<'small' \| 'medium' \| 'large' \| 'full'>; components: object[] }[]` | optional | Layout regions (header, main, sidebar, footer) with their components. Optional — list pages use interfaceConfig, slotted pages use slots, and an empty full page falls back to the synthesized default layout. | | **isDefault** | `boolean` | optional | | | **assignedProfiles** | `string[]` | optional | | -| **interfaceConfig** | `{ source?: string; columns?: string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]; sort?: { field: string; order: Enum<'asc' \| 'desc'> }[]; filterBy?: { field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'starts_with' \| … +14 more>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]; … }` | optional | Interface-level page configuration (for Airtable-style interface pages) | +| **interfaceConfig** | `{ source?: string; columns?: string[] \| object[]; sort?: object[]; filterBy?: object[]; … }` | optional | Interface-level page configuration (for Airtable-style interface pages) | | **aria** | `{ ariaLabel?: string \| Record; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes | | **kind** | `Enum<'full' \| 'slotted' \| 'html' \| 'react' \| 'jsx'>` | optional | Page override mode. full \| slotted = structured authoring; html = author-written constrained JSX/HTML+Tailwind compiled (parsed, never executed) to the tree (ADR-0080; the legacy value 'jsx' is a deprecated alias); react = real-React source executed at render by the runtime (ADR-0081); it runs author JS, so it is gated by a host capability that defaults ON and is disabled server-side via the OS_PAGE_REACT=off env toggle. | -| **slots** | `{ header?: { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … } \| { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … }[]; actions?: { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … } \| { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … }[]; alerts?: { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … } \| { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … }[]; highlights?: { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … } \| { type: Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| … +29 more> \| string; id?: string; label?: string \| Record; properties?: Record; … }[]; … }` | optional | Slot override map for slotted pages | +| **slots** | `{ header?: object \| object[]; actions?: object \| object[]; alerts?: object \| object[]; highlights?: object \| object[]; … }` | optional | Slot override map for slotted pages | | **source** | `string` | optional | Page source text. For kind==='html' (alias 'jsx') it is constrained JSX/HTML+Tailwind compiled to the tree by @objectstack/sdui-parser at save time (parse, never execute). For kind==='react' it is real React/JSX executed at render by @object-ui/react-runtime (trusted tier). Authoritative over `regions` in both. | | **requires** | `string[]` | optional | Plugin namespaces the JSX source references (validated at save and load) | | **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). | @@ -115,7 +115,7 @@ Interface-level page configuration (Airtable parity) | **responsiveStyles** | `{ large?: Record; medium?: Record; small?: Record; xsmall?: Record }` | optional | Per-breakpoint scoped style maps (ADR-0065) | | **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL) — component rendered only when TRUE. Binds `record`, `current_user`, `page.`. e.g. "page.selectedProjectId != ''" | | **visibility** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → `visibleWhen`] Visibility predicate (CEL). Normalized to `visibleWhen` at parse. | -| **dataSource** | `{ object: string; view?: string; filter?: any; sort?: { field: string; order: Enum<'asc' \| 'desc'> }[]; … }` | optional | Per-element data binding for multi-object pages | +| **dataSource** | `{ object: string; view?: string; filter?: any; sort?: object[]; … }` | optional | Per-element data binding for multi-object pages | | **responsive** | `{ breakpoint?: Enum<'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'>; hiddenOn?: Enum<'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'>[]; columns?: object; order?: object }` | optional | Responsive layout configuration | | **aria** | `{ ariaLabel?: string \| Record; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes | diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index 50b60fa926..59b1c39d14 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -234,7 +234,7 @@ Column footer summary configuration | **visibleOn** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → `visibleWhen`] Visibility predicate (CEL). Hides the whole section when false. Normalized to `visibleWhen` at parse. | | **columns** | `Enum<'1' \| '2' \| '3' \| '4'> \| 1 \| 2 \| 3 \| 4` | optional | | | **pane** | `Enum<'primary' \| 'secondary'>` | optional | Split pane this section renders in (split forms only; a parse error elsewhere). Omitted → first section 'primary', others 'secondary'. | -| **fields** | `(string \| { field: string; type?: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; options?: { label: string; value: string; color?: string; default?: boolean; … }[]; reference?: string; … })[]` | ✅ | | +| **fields** | `(string \| { field: string; type?: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; options?: object[]; reference?: string; … })[]` | ✅ | | --- @@ -313,7 +313,7 @@ Gallery/card view configuration | **effortField** | `string` | optional | Per-task load units (resource view; default 1) | | **capacity** | `number` | optional | Per-resource capacity ceiling; loads above this flag overload | | **tooltipFields** | `(string \| { field: string; label?: string })[]` | optional | Fields to surface in the hover tooltip, in display order | -| **quickFilters** | `{ field: string; label?: string; options?: (string \| { value: string \| number; label?: string })[] }[]` | optional | Multi-select filter dropdowns rendered above the chart | +| **quickFilters** | `{ field: string; label?: string; options?: (string \| object)[] }[]` | optional | Multi-select filter dropdowns rendered above the chart | | **autoZoomToFilter** | `boolean` | optional | When true (default), filtering zooms the range to the filtered tasks | @@ -456,7 +456,7 @@ List chart view configuration | **sort** | `string \| { field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | | | **searchableFields** | `string[]` | optional | Fields enabled for search | | **filterableFields** | `string[]` | optional | Legacy shorthand for userFilters.fields — bare field names enabled for end-user filtering. Prefer userFilters | -| **userFilters** | `{ element?: Enum<'dropdown' \| 'tabs' \| 'toggle'>; fields?: { field: string; label?: string \| Record; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: { value: string \| number \| boolean; label: string \| Record; color?: string }[]; … }[]; tabs?: { name: string; label?: string \| Record; icon?: string; view?: string; … }[]; showAllRecords?: boolean; … }` | optional | End-user quick-filter bar: dropdown/toggle fields or tab presets. Omit to let the renderer derive filters from select/boolean fields | +| **userFilters** | `{ element?: Enum<'dropdown' \| 'tabs' \| 'toggle'>; fields?: object[]; tabs?: object[]; showAllRecords?: boolean; … }` | optional | End-user quick-filter bar: dropdown/toggle fields or tab presets. Omit to let the renderer derive filters from select/boolean fields | | **resizable** | `boolean` | optional | Enable column resizing | | **striped** | `boolean` | optional | Striped row styling | | **bordered** | `boolean` | optional | Show borders | @@ -474,7 +474,7 @@ List chart view configuration | **description** | `string \| Record` | optional | View description for documentation/tooltips | | **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration | | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | -| **grouping** | `{ fields: { field: string; order?: Enum<'asc' \| 'desc'>; collapsed?: boolean }[] }` | optional | Group records by one or more fields | +| **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | | **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | | **fieldOrder** | `string[]` | optional | Explicit field display order for this view | @@ -482,7 +482,7 @@ List chart view configuration | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry (objectui#3139) that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time (#4457). Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. | | **virtualScroll** | `boolean` | optional | Enable virtual scrolling for large datasets | -| **conditionalFormatting** | `{ condition: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; style: Record }[]` | optional | Conditional formatting rules for list rows | +| **conditionalFormatting** | `{ condition: string \| object; style: Record }[]` | optional | Conditional formatting rules for list rows | | **inlineEdit** | `boolean` | optional | Allow inline editing of records directly in the list view | | **exportOptions** | `Enum<'csv' \| 'xlsx' \| 'pdf' \| 'json'>[]` | optional | Available export format options | | **userActions** | `{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }` | optional | User action toggles for the view toolbar | @@ -562,7 +562,7 @@ List chart view configuration | **description** | `string \| Record` | optional | View description for documentation/tooltips | | **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration | | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | -| **grouping** | `{ fields: { field: string; order?: Enum<'asc' \| 'desc'>; collapsed?: boolean }[] }` | optional | Group records by one or more fields | +| **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | | **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | | **fieldOrder** | `string[]` | optional | Explicit field display order for this view | @@ -570,7 +570,7 @@ List chart view configuration | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry (objectui#3139) that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time (#4457). Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. | | **virtualScroll** | `boolean` | optional | Enable virtual scrolling for large datasets | -| **conditionalFormatting** | `{ condition: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; style: Record }[]` | optional | Conditional formatting rules for list rows | +| **conditionalFormatting** | `{ condition: string \| object; style: Record }[]` | optional | Conditional formatting rules for list rows | | **inlineEdit** | `boolean` | optional | Allow inline editing of records directly in the list view | | **exportOptions** | `Enum<'csv' \| 'xlsx' \| 'pdf' \| 'json'>[]` | optional | Available export format options | | **userActions** | `{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }` | optional | User action toggles for the view toolbar | @@ -583,7 +583,7 @@ List chart view configuration | **aria** | `{ ariaLabel?: string \| Record; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes for the list view | | **responsive** | `never` | optional | [REMOVED] `view.responsive` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — no renderer ever read it; the grid is responsive by its own layout rules. Delete the key. | | **performance** | `never` | optional | [REMOVED] `view.performance` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — no renderer or runtime read it; list-view performance tuning was never implemented. Delete the key. | -| **userFilters** | `{ element?: Enum<'dropdown' \| 'toggle'>; fields?: { field: string; label?: string \| Record; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: { value: string \| number \| boolean; label: string \| Record; color?: string }[]; … }[] }` | optional | | +| **userFilters** | `{ element?: Enum<'dropdown' \| 'toggle'>; fields?: object[] }` | optional | | --- @@ -595,7 +595,7 @@ List chart view configuration | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **element** | `Enum<'dropdown' \| 'toggle'>` | ✅ | Filter control style on object views: "dropdown" (per-field value chips). "toggle" is deprecated. "tabs" is page-only — use `listViews` for named presets. | -| **fields** | `{ field: string; label?: string \| Record; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: { value: string \| number \| boolean; label: string \| Record; color?: string }[]; … }[]` | optional | Fields exposed as quick filters (dropdown/toggle elements) | +| **fields** | `{ field: string; label?: string \| Record; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: object[]; … }[]` | optional | Fields exposed as quick filters (dropdown/toggle elements) | --- @@ -731,7 +731,7 @@ End-user quick-filter configuration (Airtable "User filters" parity) | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **element** | `Enum<'dropdown' \| 'tabs' \| 'toggle'>` | ✅ | Filter control style: "dropdown" (per-field value selectors) or "tabs" (named presets). "toggle" is deprecated. | -| **fields** | `{ field: string; label?: string \| Record; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: { value: string \| number \| boolean; label: string \| Record; color?: string }[]; … }[]` | optional | Fields exposed as quick filters (dropdown/toggle elements) | +| **fields** | `{ field: string; label?: string \| Record; type?: Enum<'select' \| 'multi-select' \| 'boolean' \| 'date-range' \| 'text'>; options?: object[]; … }[]` | optional | Fields exposed as quick filters (dropdown/toggle elements) | | **tabs** | `{ name: string; label?: string \| Record; icon?: string; view?: string; … }[]` | optional | Named filter presets rendered as tabs (tabs element). Reuses ViewTabSchema | | **showAllRecords** | `boolean` | optional | Show an "All records" tab before the presets (tabs element) | | **allowAddTab** | `boolean` | optional | Render an "add tab" affordance after the presets (tabs element). Page lists only — object views use `listViews` for named presets | @@ -748,9 +748,9 @@ End-user quick-filter configuration (Airtable "User filters" parity) | **name** | `string` | optional | Item name — supplied by the metadata door; for an object-scoped container it is the object name. | | **label** | `string \| Record` | optional | Human-readable label shown in metadata lists. | | **object** | `string` | optional | Object this container binds to — how a stack-level `views: [...]` entry says which object its views belong to; read by `getViewsByObject()` / `GET /meta/view?object=`. | -| **list** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: { provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }; … }` | optional | | +| **list** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }` | optional | | | **form** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }` | optional | | -| **listViews** | `Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: { provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) | +| **listViews** | `Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) | | **formViews** | `Record; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }>` | optional | Additional named form views | | **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. | | **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). | @@ -869,7 +869,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **viewKind** | `'list'` | ✅ | | -| **config** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: { provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }; … }` | ✅ | List-family view configuration. | +| **config** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }` | ✅ | List-family view configuration. | | **name** | `string` | ✅ | Globally-unique view id, `.`. | | **object** | `string` | ✅ | Bound object name — the foreign key used to aggregate views. | | **label** | `string \| Record` | optional | Display label (supports i18n). | @@ -935,7 +935,7 @@ This schema accepts one of the following structures: | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **viewKind** | `'list'` | ✅ | | -| **config** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: { provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }; … }` | ✅ | List-family view configuration. | +| **config** | `{ name?: string; label?: string \| Record; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }` | ✅ | List-family view configuration. | | **name** | `string` | ✅ | Globally-unique view id, `.`. | | **object** | `string` | ✅ | Bound object name — the foreign key used to aggregate views. | | **label** | `string \| Record` | optional | Display label (supports i18n). | diff --git a/packages/spec/scripts/format-type.test.ts b/packages/spec/scripts/format-type.test.ts index aa241395c9..baa14e7264 100644 --- a/packages/spec/scripts/format-type.test.ts +++ b/packages/spec/scripts/format-type.test.ts @@ -784,15 +784,25 @@ describe('formatType — over-wide enums inside a shape summary are elided with expect(rendered.length).toBe(174); }); - it('elides through array-of-object nesting — the `api/*.mdx` `error` shape', () => { - // A direct object child is forced opaque, but an ARRAY of objects recurses, - // which is how a 261-member enum reached a cell two levels down. - const rendered = formatType(ERROR_RESPONSE, ctx()); - expect(rendered).toBe( - "{ success: boolean; error?: { code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | " + - "'MISSING_REQUIRED_FIELD' | … +258 more>; message: string }[] }", + it('elides through a WRAPPER reached from a summary — array of enum', () => { + // REPLACED FIXTURE (#6374). This case used to run on `ERROR_RESPONSE`, whose + // enum sat two SHAPE levels down (`{ success; error?: { code: Enum<…> }[] }`) + // — a route the shape budget closes: that inner shape now prints `object`, + // so there is no enum there left to elide and the old assertion could only + // have been kept by asserting an emptiness. What #5340 actually claims is + // that the elision composes through the wrappers between a summary and a + // vocabulary, and that is still true and still worth pinning — the budget + // stops the renderer re-entering the OBJECT branch, it does not sever + // `inShapeSummary` from arrays and records. `ERROR_RESPONSE` keeps its job + // one block down, where it now pins the budget itself. + const rendered = formatType( + { type: 'object', properties: { codes: { type: 'array', items: { type: 'string', enum: ERROR_CODES } } } }, + ctx(), ); + expect(rendered).toContain('… +258 more'); expect(3 + 258).toBe(ERROR_CODES.length); + // Still an array OF the elided vocabulary, not an elided array. + expect(rendered.endsWith('>[] }')).toBe(true); // Was 5000+ characters in one table cell. expect(rendered.length).toBeLessThan(150); }); @@ -1199,17 +1209,266 @@ describe('formatType — a union spells four variants and counts the rest (#6226 expect(rendered).toContain('… +5 more'); }); - it('caps a union nested inside a shape summary too, alongside the enum elision', () => { - // The two elisions compose: `Page.slots`-shaped cells carry both. + it('caps a union nested inside a shape summary too — the `Manifest.navigationContributions` cell', () => { + // REPLACED FIXTURE (#6374). This case used to build six OBJECT variants + // under a key and assert `… +2 more`. Below a summary those six now all + // render `object`, and six six-character spellings are narrower than the + // marker that would replace two of them, so the shared pay-for-your-marker + // guard refuses — correctly, and the case would have been asserting the + // guard rather than the cap. The corpus keeps exactly one cell where a + // nested union is still wide enough for the cap to pay, and this is it. + const rendered = formatType( + { + type: 'object', + properties: { + app: { type: 'string' }, + items: { type: 'array', items: { anyOf: Array.from({ length: 9 }, (_, i) => variant(`k${i}`)) } }, + }, + required: ['app', 'items'], + }, + ctx(), + ); + expect(rendered).toBe('{ app: string; items: (object | object | object | object | … +5 more)[] }'); + // 4 shown + 5 hidden = the 9 the schema declares, below a summary exactly as + // above one: the budget changes what a variant SPELLS, never the arity the + // cap reports. + expect(4 + 5).toBe(9); + }); +}); + +/** + * The real `ui/page.mdx` `Page.slots` node, as `gen:schema` emits it — seven + * slot keys, each a union of `PageComponent` or an array of it, and + * `PageComponent` inlined by value (not `$ref`d) at all fourteen positions. + * + * This is the corpus maximum #6374 was filed on: 1538 characters in one table + * cell, WITH `INLINE_ENUM_WIDTH_LIMIT` already firing eight times inside it. + */ +const PAGE_COMPONENT = { + type: 'object', + properties: { + type: { + anyOf: [ + { + type: 'string', + enum: [ + 'page:header', 'page:footer', 'page:sidebar', 'page:tabs', 'page:accordion', + 'page:card', 'page:section', 'record:details', 'record:highlights', + 'record:related_list', 'record:activity', 'record:chatter', 'record:path', + 'record:alert', 'record:quick_actions', 'record:reference_rail', 'record:history', + 'app:launcher', 'nav:menu', 'nav:breadcrumb', 'global:search', + 'global:notifications', 'user:profile', 'ai:chat_window', 'ai:suggestion', + 'element:text', 'element:number', 'element:image', 'element:divider', + 'element:button', 'element:filter', 'element:form', 'element:record_picker', + 'element:text_input', + ], + }, + { type: 'string' }, + ], + }, + id: { type: 'string' }, + label: { type: 'string' }, + properties: { type: 'object', additionalProperties: {} }, + events: { type: 'object', additionalProperties: {} }, + }, + required: ['type'], + additionalProperties: false, +}; + +const PAGE_SLOTS = { + type: 'object', + properties: Object.fromEntries( + ['header', 'actions', 'alerts', 'highlights', 'details', 'tabs', 'discussion'].map(k => [ + k, + { anyOf: [PAGE_COMPONENT, { type: 'array', items: PAGE_COMPONENT }] }, + ]), + ), + additionalProperties: false, +}; + +/** + * Pin for the shape-depth budget — #6374. + * + * The renderer has always opened exactly ONE `{ … }` level per cell, but it + * spent that budget in the key loop, so only a DIRECT object child was held to + * it. An array element, a `Record` value and a union variant each re-entered + * the object branch with the budget out of scope, and cell width became keys × + * variants × shape width, multiplied per level. `SHAPE_DEPTH_LIMIT` moves the + * same budget to the object branch itself, where all four descents pass. + * + * REVERSE VERIFICATION, predicted BEFORE running. Reverting = deleting the + * `depth >= SHAPE_DEPTH_LIMIT` guard and putting the + * `child?.type === 'object' && child.properties ? 'object' : …` ternary back. + * The direction is NOT uniformly red, and that asymmetry is the point of the + * block: this fix makes an existing rule uniform, so the cases pinning the + * limb that already existed MUST stay green under the revert or they are not + * pinning uniformity at all. Predicted: + * RED — every case whose `object` is reached through an array, a `Record` + * value or a union variant (the three descents the budget adds), and + * the no-`ctx` case, which reaches its `object` through a variant. + * GREEN — 'a direct object child was ALREADY opaque' (that IS the ternary), + * both 'a wrapper does not spend the budget' cases (one level either + * way), and 'a keyless `Record` is not a shape' (the guard sits + * inside the declared-keys branch and cannot fire there). + * Predicted split: 6 red, 4 green. + * ACTUAL: recorded in the PR body against this prediction. + */ +describe('formatType — one shape level, whichever way down (#6374)', () => { + it('renders the `Page.slots` cell without re-expanding `PageComponent` (the filed instance)', () => { + const rendered = formatType(PAGE_SLOTS, ctx()); + expect(rendered).toBe( + '{ header?: object | object[]; actions?: object | object[]; alerts?: object | object[]; ' + + 'highlights?: object | object[]; … }', + ); + // 1538 → 122 characters. The vacuity guard for the whole block: with the + // budget reverted this node renders the same `PageComponent` summary eight + // times, so both of these are false by more than an order of magnitude. + expect(rendered.length).toBe(122); + expect(rendered).not.toContain('Enum<'); + }); + + it('holds an ARRAY element to the budget — the `api/*.mdx` `error` shape', () => { + // `ERROR_RESPONSE`'s old job, kept as the array case: the 261-member + // vocabulary sat two shape levels down and reached the cell because + // `{ … }[]` re-entered the object branch. The array is not what is elided — + // the cell still says "an array of them". + expect(formatType(ERROR_RESPONSE, ctx())).toBe('{ success: boolean; error?: object[] }'); + }); + + it('holds a `Record` VALUE to the budget — the `GetTranslationsResponse` shape', () => { + expect( + formatType( + { + type: 'object', + properties: { + objects: { + type: 'object', + additionalProperties: { type: 'object', properties: { label: { type: 'string' } } }, + }, + }, + }, + ctx(), + ), + ).toBe('{ objects?: Record }'); + }); + + it('holds a union VARIANT to the budget — the `Object.userActions` shape', () => { + expect( + formatType( + { + type: 'object', + properties: { + edit: { + anyOf: [ + { type: 'boolean' }, + { type: 'object', properties: { enabled: { type: 'boolean' } } }, + ], + }, + }, + }, + ctx(), + ), + ).toBe('{ edit?: boolean | object }'); + }); + + it('holds a direct object child to the budget — the limb that was ALREADY opaque', () => { + // Unchanged by this commit and asserted here on purpose: it is the rule the + // other three cases were brought into line with, so it has to be read as + // one rule with them rather than as a fourth special case. + expect( + formatType( + { + type: 'object', + properties: { inner: { type: 'object', properties: { deep: { type: 'string' } } } }, + }, + ctx(), + ), + ).toBe('{ inner?: object }'); + }); + + it('spends the budget on SHAPES, so a wrapper at the top of a cell still opens one level', () => { + // `{ … }[]` and `Record` are one shape level, not two — the + // budget counts `{ … }`, and a wrapper is not one. Getting this wrong would + // collapse the whole `ConversationSession.messages` family to a bare + // `object[]` and take the corpus with it. + expect( + formatType({ type: 'array', items: { type: 'object', properties: { a: { type: 'string' } } } }, ctx()), + ).toBe('{ a?: string }[]'); + expect( + formatType( + { type: 'object', additionalProperties: { type: 'object', properties: { a: { type: 'string' } } } }, + ctx(), + ), + ).toBe('Record'); + }); + + it('does not fire on a keyless `Record` below a summary — that is not a shape', () => { + // `properties?: Record` is on `PageComponent` itself and on + // hundreds of other nodes. The guard sits inside the declared-keys branch, + // so an open object with NO declared keys renders as it always did. + expect( + formatType( + { type: 'object', properties: { properties: { type: 'object', additionalProperties: {} } } }, + ctx(), + ), + ).toBe('{ properties?: Record }'); + }); + + it('applies without a `ctx`, because depth is recursion state and not page state', () => { + // The ternary this replaces needed no `ctx` either. A budget that lived in + // `TypeContext` would silently switch itself off for every caller that + // renders a type string without page context — the failure mode + // `inShapeSummary` documents one field up, and not one to copy. + expect( + formatType({ + type: 'object', + properties: { + edit: { anyOf: [{ type: 'boolean' }, { type: 'object', properties: { a: { type: 'string' } } }] }, + }, + }), + ).toBe('{ edit?: boolean | object }'); + }); + + it('leaves a cell that never opened a second shape level byte-identical', () => { + // The corpus check that the budget is not a rewrite: 173 of the 215 pages + // do not move at all. `BulkActionDef.params` (#5340's instance) and + // `App.navigation` (#6226's) are both one level deep and both unchanged. + expect(formatType(BULK_ACTION_PARAMS, ctx()).length).toBe(174); + expect(formatType(INDEX_SCHEMA, ctx())).toBe( + "{ name?: string; fields: string[]; unique?: boolean | 'global' | 'organization' }[]", + ); + expect(formatType(BULK_ACTION_OPTIONS, ctx())).toBe( + '({ label: string; value: string | number | boolean } & Record)[]', + ); + }); + + it('prints an identical variant once per variant — the budget may not drop the arity', () => { + // Six object variants below a summary all print `object`, and the cell says + // so six times. It reads oddly and it is deliberate: #6226 ruled that a + // union elision must SELF-REPORT what it hid, and the shared + // pay-for-your-marker guard refuses a marker here because six six-character + // spellings are narrower than the count that would replace two of them. + // Collapsing them to one `object` would drop the arity — the one fact the + // cell still carries — and would re-decide #6226 on a surface the + // maintainer has just ruled on. The renderer already ships exactly this + // shape for scalars (`string | string | string | string | string`, pinned + // in the #6226 block), so this is that pinned behaviour meeting a new + // spelling, not a new behaviour. Filed for the maintainer as a finding. const rendered = formatType( { type: 'object', properties: { - slot: { anyOf: Array.from({ length: 6 }, (_, i) => variant(`k${i}`)) }, + slot: { + anyOf: Array.from({ length: 6 }, (_, i) => ({ + type: 'object', + properties: { id: { type: 'string' }, [`k${i}`]: { type: 'string' } }, + })), + }, }, }, ctx(), ); - expect(rendered).toContain('… +2 more'); + expect(rendered).toBe('{ slot?: object | object | object | object | object | object }'); + expect(rendered).not.toContain('more'); }); }); diff --git a/packages/spec/scripts/lib/format-type.ts b/packages/spec/scripts/lib/format-type.ts index aa235b7fca..2819662949 100644 --- a/packages/spec/scripts/lib/format-type.ts +++ b/packages/spec/scripts/lib/format-type.ts @@ -74,6 +74,62 @@ export const anchorFor = (schemaName: string) => `#${schemaName.toLowerCase()}`; /** How many declared keys an inline object shows before eliding the rest. */ const INLINE_KEY_LIMIT = 4; +/** + * How many `{ … }` shape levels one cell expands before printing `object` + * (#6374). + * + * `INLINE_KEY_LIMIT` caps a summary's KEYS at its own level; the two enum + * budgets cap one key's VOCABULARY; `VARIANT_LIMIT` caps a union's ARITY. + * Nothing capped how many times a cell could re-enter the object branch, and + * cell width is the product of all four axes. `ui/page.mdx`'s `Page.slots` was + * the corpus maximum at 1538 characters with the enum elision already firing + * eight times inside it: 4 keys × a 2-variant union × a 176-character shape. + * + * WHY 1 — the number is RECOVERED, not chosen. This renderer has always had a + * depth budget of exactly one shape level, written as the ternary in the key + * loop below: a child that is itself an object printed `object` rather than its + * shape. That budget was applied on ONE of the four ways down. An array element + * (`{ … }[]`), a `Record` value (`Record`) and a union variant + * (`{ … } | { … }[]`) each re-entered the object branch through `renderType` + * with the budget nowhere in scope, so the same shape at the same reader-facing + * depth printed opaquely or in full depending on whether its author had wrapped + * it in an array. That is a fact about the Zod spelling, not about how a reader + * needs to read it — the same asymmetry #6225 removed for vocabularies. This + * constant makes the existing rule apply to all four descents; the ternary it + * replaces is its depth-1 case. + * + * The corpus confirms 1 is the only admissible value. Regenerating all 215 + * pages (8499 type cells) at each candidate, counting emitted cell widths: + * + * depth limit | (none, before) | 1 | 2 | 3 | 4 + * cells >200 | 121 | 42 | 173 | 191 | 191 + * cells >400 | 9 | 1 | 19 | 35 | 35 + * cells >600 | 3 | 1 | 2 | 14 | 22 + * cells >900 | 1 | 0 | 1 | 1 | 1 + * p95 / p99 | 145 / 229 | 124/180 | 154/260 | 154/280 | 154/287 + * max | 1538 | 656 | 1538 | 1538 | 1538 + * + * Every limit above 1 is worse than shipping nothing: raising it necessarily + * LOOSENS the direct-object-child path, which was already at 1, so cells over + * 200 rise from 121 to 173+ and the flagship never moves. There is no sweep to + * balance here and no threshold to tune — 1 is the status quo made uniform, + * and 2 and up are a regression dressed as a budget. + * + * At 1, every cell still over 400 characters is `ui/page.mdx`'s + * `PageComponent.type` (656), a top-level vocabulary in a union variant that + * #6225 deliberately leaves alone and that carries no nested shape at all. + * Shape-driven width is gone from the corpus. + * + * WHAT A READER LOSES, and why `object` is the honest rendering. Nothing is + * silently truncated: `object` claims nothing about keys, so unlike a prefix it + * cannot be mistaken for a complete list — which is the #5340 principle applied + * to shapes rather than to enum members. It is also not a new elision style in + * these tables: `object` is what a nested shape has always printed, and the + * complete shape stays where it always was — its own `## Schema` section when + * the generator emits one, and `json-schema/` in every case. + */ +const SHAPE_DEPTH_LIMIT = 1; + /** * Character budget for one `Enum<…>` BODY rendered INSIDE an inline shape * summary. Over it, members are dropped until the body fits and the count of @@ -426,7 +482,18 @@ export function formatPropertyType(prop: any, ctx?: TypeContext): RenderedProper return { cell: formatType(prop, ctx), allowedValues: null }; } +/** + * `depth` is the count of `{ … }` shape levels already OPEN above this node, + * and it is a parameter rather than a `TypeContext` field on purpose: a caller + * that passes no `ctx` (every unit test that only wants a type string) must + * still get the width budget, the way it always got the ternary this replaces. + * `ctx` carries facts about the PAGE; depth is a fact about the RECURSION. + */ export function formatType(prop: any, ctx?: TypeContext): string { + return renderType(prop, ctx, 0); +} + +function renderType(prop: any, ctx: TypeContext | undefined, depth: number): string { if (!prop) return 'any'; // A `retiredKey()` tombstone. `never` is both the accurate TypeScript (the @@ -452,7 +519,7 @@ export function formatType(prop: any, ctx?: TypeContext): string { if (ctx!.expanding?.has(name)) return 'object'; const expanding = new Set(ctx!.expanding ?? []); expanding.add(name); - return formatType({ ...target, $ref: undefined }, { ...ctx!, expanding }); + return renderType({ ...target, $ref: undefined }, { ...ctx!, expanding }, depth); } const href = ctx?.schemaHref?.(name) ?? null; @@ -460,7 +527,7 @@ export function formatType(prop: any, ctx?: TypeContext): string { } if (prop.type === 'array') { - const element = formatType(prop.items, ctx); + const element = renderType(prop.items, ctx, depth); // An open object element renders as an intersection and a multi-variant // element as a union — `[]` would re-associate either — so parenthesize // and the cell keeps meaning "array of that". @@ -477,7 +544,7 @@ export function formatType(prop: any, ctx?: TypeContext): string { if (prop.anyOf || prop.oneOf) { const variants = prop.anyOf || prop.oneOf; - const rendered = variants.map((v: any) => formatType(v, ctx)); + const rendered = variants.map((v: any) => renderType(v, ctx, depth)); const full = rendered.join(' | '); if (rendered.length <= VARIANT_LIMIT) return full; // The variants a reader does not see are counted, never silently dropped — @@ -496,7 +563,7 @@ export function formatType(prop: any, ctx?: TypeContext): string { // open object with a declared shape printed as a bare `Record` // and the author-facing page lost keys the schema *requires*. const open = prop.additionalProperties - ? `Record` + ? `Record` : null; // Inline object: show its shape one level deep instead of an opaque `Object`. @@ -517,18 +584,22 @@ export function formatType(prop: any, ctx?: TypeContext): string { : []; if (keys.length > 0) { + // The shape budget, spent HERE rather than at each of the four descents + // that reach this branch (#6374). A cell opens `SHAPE_DEPTH_LIMIT` shape + // levels; below that a shape prints `object`, whichever way down it was + // reached — a direct object child, an array element, a `Record` value or + // a union variant. Wrappers do not spend the budget, only shapes do, so + // `{ … }[]` and `Record` are one level, not two. + if (depth >= SHAPE_DEPTH_LIMIT) return 'object'; const shown = keys.slice(0, INLINE_KEY_LIMIT).map(k => { const child = prop.properties[k]; const optional = (prop.required || []).includes(k) ? '' : '?'; - // Depth-limited: nested objects stay opaque so a table cell can't explode. // Everything below this point is a SUMMARY of the child, not the // child's own row, so a long enum reached from here is elided (#5340). // The flag is set once, here, and inherited by every branch underneath - // — arrays of objects recurse (only a direct object child is forced - // opaque above), so `errors?: { code: Enum<…> }[]` is reached this way. - const childType = child?.type === 'object' && child.properties - ? 'object' - : formatType(child, ctx && { ...ctx, inShapeSummary: true }); + // — arrays of objects recurse, so `errors?: { code: Enum<…> }[]` is + // reached this way. + const childType = renderType(child, ctx && { ...ctx, inShapeSummary: true }, depth + 1); return `${k}${optional}: ${childType}`; }); // `…` elides further LIVE declared keys; `& Record<…>` states that