Skip to content

Commit df0c0c8

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20333-create-objectstack-wire-barrels
2 parents d50d46f + a88a1bb commit df0c0c8

286 files changed

Lines changed: 2087 additions & 367 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
"@objectstack/lint": minor
3+
---
4+
5+
A row-level-security predicate that compares a field with a `json` or `multiple` field is refused when it is authored, at `os validate` / `os build` / `os lint` and at the metadata save door, instead of only when it runs (#19886).
6+
7+
**BREAKING** — an accept-set narrowing, shipped by `@objectstack/lint` as `minor` under the repo's launch-window convention for accept-set narrowings. The hand-migration prescription is already registered under protocol major 18 as `cel-predicate-one-value-comparand-refused`, which names this class.
8+
9+
Clause-②: no (narrowing)
10+
11+
`record.status != record.tags`, with `tags` a `json` field or a `multiple` lookup, lowers to a legal filter shape, because the CEL lowering sees the predicate's text and not the object's field types, and the engine's filter admission does not judge a `{ $field }` reference against the referenced column's type either. Measured before this change: 400 cells (`==`, `!=`, `!(==)`, `>`, `<=`; a `json`, `address`, `multiselect`, `multiple` lookup and `multiple` user field; both operand orders; `using` on `select` / `all` / `update` / `delete` / `insert` and `check` on `insert` / `update` / `all`) were all accepted by the real `os validate` and by the save door. The runtime refused every one of them, measured through the real plugin-security on driver-sql: a read the `using` scopes answered `INVALID_FILTER` / 400, a by-id update or delete it scopes `PERMISSION_DENIED` / 403, and every insert or by-id update judged by the `check` (or by a `using` standing in as the check) `INVALID_FILTER` / 400, with nothing stored.
12+
13+
What changes:
14+
15+
- `@objectstack/lint`: `validateRlsPredicateEnforceability` reports `rls-predicate-unenforceable` for every lowered field-to-field comparison (`==`, `!=`, `>`, `>=`, `<`, `<=`, on either side, under `!` too) in which either column is DECLARED to hold a list or an object. The declaration is read from the stack's own objects through the spec's value-shape classes, the same two driver-sql refuses such a comparison by: a structured JSON type (`json`, `composite`, `repeater`, `record`, `location`, `address`, `vector`), or a multi-valued field (`multiselect`, `checkboxes`, `tags`, or `select` / `radio` / `lookup` / `user` / `file` / `image` with `multiple: true`). It judges `using` and `check` on every operation. The finding names each comparison and the declaration behind it, and states the clause's run-time consequence. A clause it refuses is not also handed to the engine's filter judge, so one defect earns one finding.
16+
- Both doors run this rule already, so both refuse: `os validate` / `os build` / `os lint` fail, and a publish through the metadata save door answers `422 INVALID_METADATA` with the same sentence in `issues[]`. `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns the save-door refusal into a logged warning.
17+
18+
Not changed: a field compared with a single-valued field (`record.status != record.owner`, `record.amount > record.budget`), a `json` or `multiple` field compared with a literal or tested against `null`, and any column the stack does not declare (an object from another package, an external object with no field map), which the rule does not judge. The runtime refusals of stages 2d and 2e stay as the backstop. The stage 2d changeset's sentence that a field compared with a `json` or `multiple` field "is not reported at authoring time" no longer holds: it is now reported at both doors.
19+
20+
No shipped predicate moves: 0 of the 187 `using` / `check` / `condition` strings in this repository's packages and examples, and 0 of the 3 in the cloud repository, compare a field with a `json` or `multiple` field, and the real `os validate` over `app-crm`, `app-multi-package`, `app-showcase` and `app-todo` reports no `rls-predicate-*` finding.
21+
22+
**What to change.** A field compared with a `json` or `multiple` field has no row-filter form: compare with a single-valued column, or with a literal or a `current_user` value ("one of these values" is `record.status in ['open', 'pending']`, or `record.owner in current_user.org_user_ids`), or move the condition into a validation rule or a hook.
23+
24+
<!-- adr-0087: not-required (already-registered cel-predicate-one-value-comparand-refused) The entry already names this exact class in its surface ("a field compared with another field (==, !=, or an ordering operator) where either column holds a list or an object on the record, as a json column or a multiple lookup does") and carries its prescription in its replacement ("A field compared with a json or multiple field has no pushdown form: compare with a single-valued column, or move the condition into a validation rule or hook"). This change moves where that registered class is refused, from run time to authoring time; it adds no class and no prescription the entry does not already hold, and it rewrites no stored metadata. -->
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec)!: retire the `inline` and `grid` arms of form `layout` — every renderer folded both to `vertical`, and multi-column is `columns` (ADR-0049)
6+
7+
<!-- adr-0087: registered form-layout-inline-grid-to-vertical, ui-form-layout-inline-grid-retired -->
8+
9+
**BREAKING** — form `layout` accepts exactly `'vertical' | 'horizontal'` on both surfaces
10+
that declared the four-arm enum: the `object-form` page component
11+
(`ObjectFormPropsSchema.layout`) and the form view (`FormViewSchema.layout` — `view.form`,
12+
`view.formViews.*`, a form view item's `config`, and the flattened form overlay, which
13+
spreads the form view's shape). `'inline'` and `'grid'` are refused at parse, each with a
14+
prescription naming what to write instead.
15+
16+
| surface | before | after |
17+
|:--|:--|:--|
18+
| `object-form` `layout: 'grid'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`); for multi-column set `columns` |
19+
| `object-form` `layout: 'inline'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`) |
20+
| form view `layout: 'grid'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`); for multi-column set `columns` |
21+
| form view `layout: 'inline'` | parsed clean, rendered as `vertical` | refused — write `'vertical'` (or omit `layout`) |
22+
| `layout: 'vertical'` / `'horizontal'` | accepted | **unchanged** |
23+
| `columns` | honoured under every layout | **unchanged** — the key multi-column always lived under |
24+
25+
**What was actually wrong.** No renderer ever gave either value a behaviour of its own.
26+
Measured at the `.objectui-sha` pin this repo builds against (`f8a9d0fb0`): the simple
27+
`object-form` arm folds both to `vertical` (`ObjectForm.tsx:1406-1410`, under the comment
28+
"Map 'grid' and 'inline' to 'vertical' as fallback"); the drawer and modal arms
29+
(`ObjectForm.tsx:463`, `:499`) and `DrawerForm.tsx:575` / `ModalForm.tsx:597` pass only
30+
`vertical` / `horizontal` through; `TabbedForm.tsx:556`, `SplitForm.tsx:445` and
31+
`WizardForm.tsx:1075` hard-code `vertical`. So both values were a green parse for a value the
32+
renderer threw away. The spec had admitted them from two declarations — the designer palette
33+
and the registry `inputs` offered all four — never from a read.
34+
35+
Under the maintainer's ADR-0049 family criterion (the capability exists on mainstream
36+
platforms ⇒ build the consumer; it does not ⇒ retire), multi-column — what `grid` would
37+
mean — already exists here under another key, `columns`, which the renderer honours under
38+
every arm; `inline` is a toolbar / filter-row pattern, not a record-form layout. The two arms
39+
are redundant vocabulary, retired with no alias window.
40+
41+
## What to write instead
42+
43+
```ts
44+
// before — parsed clean, rendered single-column 'vertical'
45+
{ type: 'object-form', properties: { objectName: 'crm_lead', layout: 'grid' } }
46+
// after — what it rendered; add `columns` if a multi-column form was the intent
47+
{ type: 'object-form', properties: { objectName: 'crm_lead', layout: 'vertical', columns: 2 } }
48+
```
49+
50+
`'inline'` → `'vertical'` (or delete `layout`: `'vertical'` is the renderer default). A form
51+
that wrote `'grid'` without `columns` always rendered single-column; only its author knows
52+
whether more columns were meant — set `columns` to the count you meant.
53+
54+
Existing sources: `os migrate meta --from 17` lists the mechanical edits; apply them by hand.
55+
56+
The retirement kit:
57+
58+
- both enums narrowed to `'vertical' | 'horizontal'`, each with a per-value error map keyed on
59+
the input (the `record:chatter` `position` precedent), so only a value that used to be legal
60+
is told it "was removed"; a never-vocabulary value keeps zod's own enum refusal
61+
- the D2 conversion `form-layout-inline-grid-to-vertical` (protocol 18, retired from the load
62+
path) rewrites both values to `'vertical'` and leaves `columns` untouched — on `object-form`
63+
page components, on every form payload a view carries, and on the assembled-manifest
64+
`viewItems` channel, so stored rows and assembled artifacts replay clean; wired into the
65+
protocol-18 chain step
66+
- one D3 entry for the family, `ui-form-layout-inline-grid-retired`, carrying the one judgement
67+
the chain cannot make: whether a form that said `grid` wanted columns it never declared
68+
- pin tests (`ui/form-layout-inline-grid-retired.test.ts`): both values refused with the
69+
prescription on five doors (`ObjectFormPropsSchema`, the `object-form` props-map row,
70+
`FormViewSchema`, a view container's `formViews`, the flattened form overlay), `vertical` /
71+
`horizontal` green on each as controls, the conversion's rewrite parsing green on the door
72+
that refused its input, and the chain registration
73+
- the `columns` descriptions no longer say "grid layout"; the generated references
74+
(`content/docs/references/**`, `skills/objectstack-ui/references/react-blocks.md`, the JSON
75+
Schema) print the two-arm enum; the hand-written `layout-dsl` page and the `form.layout`
76+
liveness row (still `live`) are updated
77+
- `api-surface/` and `authorable-surface/` are unchanged, correctly: they ratchet export and
78+
key existence, and no export or key leaves — `layout` is still declared, two values narrower
79+
80+
Clause-②: no (narrowing)

‎.claude/skills/spec-property-retirement/SKILL.md‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ preview renderer 不算消费者)与 AGENTS.md §"Touched `packages/spec`?"(八
4040
`rls.enabled` 曾是「证据错误的 live」而实际**无人读取** —— disabled 的
4141
policy 仍在贡献它的授权。那个键最后是**被强制执行**,不是被删除。功能存在时,强制
4242
执行赢。
43-
- [ ] **它是文档形状的吗?** `hook.label`、`hook.description`、`flow.description`
44-
没有运行时消费者,但被**有意保留** —— 它们为下一个读者(按 ADR-0033,常是模
45-
型)记录意图。把豁免写进台账 `note`,下次审计不再重审。良性展示元数据
46-
(`description`、`tags`、`icon`)永远谈不上「误导」;不要标 `authorWarn`,也
47-
不要退役它。
43+
- [ ] **主流平台有没有这个能力?** 声明而未执行的键每族问这句,按维护者原话(裁决评论
44+
`5727134555`):「有 ⇒ 补消费端(一次做对);没有 ⇒ 退役,而不是看仓里有没有人读」。
45+
文档形状的键(`hook.label`、`flow.description`)为下一个读者记录意图(ADR-0033),是「有」的
46+
一例:补渲染、不退役,判定写进台账 `note`,下次审计不再重审。`description`、`tags`、`icon`
47+
不自动豁免:RLS 策略的 `tags` 主流没有 ⇒ 退役。`authorWarn` 另按 README 判。
4848
- [ ] **有已承诺的路线图吗?** 那就是 `experimental` + `.describe()` 里的
4949
`[EXPERIMENTAL — not enforced]` 标记,不是删除。
5050
- [ ] **同 major 记账。** 同一个*未发布 major* 里更早的 conversion 改名了你现在要删
@@ -58,7 +58,7 @@ preview renderer 不算消费者)与 AGENTS.md §"Touched `packages/spec`?"(八
5858
`live-elsewhere` 一节。
5959
- [ ] **零编写实例普查要并跑一个同族已知存活的键作对照**,两读数都报;同得零即没测出。
6060
- [ ] **拿 `cross-repo` 行当论据,就当刻重核路径与行号**:漂了读作重新取证,⛔ 不是 `dead`。
61-
- [ ] **「零编写实例」≠「没有代码读它」。** 前者答的是示例应用,后者才是退役的证据。
61+
- [ ] **「零编写实例」≠「没有代码读它」。** 前者答的是示例应用,后者才证 `dead`。
6262

6363
## 1. 裁判是构建,不是台账
6464

@@ -174,8 +174,8 @@ conversion 已不再覆盖的那位。
174174
`spec-changes.json`、生成的 upgrade guide 与 `spec_changes` MCP 工具才是主通道,不
175175
注册它们就一直空着。
176176

177-
⚠ **这是两个独立义务,其中只有一个是字符串匹配。** registry 条目是门禁读的;
178-
conversion 是消费者跟的。两个都要写。
177+
⚠ **这是三个独立义务,其中只有一个是字符串匹配。** registry 条目是门禁读的;
178+
conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都要写。
179179

180180
- [ ] **一条 `MetadataConversion`**,在 `packages/spec/src/conversions/registry.ts`:
181181
kebab-case 的 `id` 以 `-removed` 结尾、`toMajor`、每键一条
@@ -203,9 +203,7 @@ conversion 是消费者跟的。两个都要写。
203203
- [ ] **`surface` 保持散文 —— 它不再被匹配。** 按作者写元数据的方式写
204204
(`flow.nodes[].outputSchema`),那也是 upgrade guide 打印的。多键 conversion
205205
仍用恰好 `' / '` 连接子句(tool 清扫以来的 house style)。下游不再有任何东西
206-
从它解析归属 —— 那个职责移给了上面的条目。#5898 起这对**每个**消费者都成立:门
207-
(c) 的 *aged-out tombstone* 证明曾是最后一个叶匹配者,现在也读同一张精确键
208-
表,再没有任何规则从 `surface` 解析归属。
206+
从它解析归属 —— 那个职责移给了上面的条目。
209207
- [ ] **`retiredFromLoadPath: true`** —— 退役恒真,但管辖权只有 authoring 漏斗
210208
`normalizeStackInput`;三处 data-at-rest seam 以 `includeRetired: true` 故意重放退役
211209
条目,它**一处也拦不住**:`applyConversionsToStoredItem`(钉死)、automation
@@ -231,8 +229,10 @@ conversion 是消费者跟的。两个都要写。
231229
`retiredFromLoadPath`。你的 transform 不属于这些形状,就自己证明幂等 —— CLI
232230
e2e(`packages/cli/test/migrate-meta.e2e.test.ts`)会 replay 迁移后的快照并断
233231
言 `applied` 为空。
234-
- [ ] **没有 source 可重写的响应面键**,改注册成 `SemanticMigration`(D3
235-
`semantic[]`),`reason` 与 `acceptanceCriteria` 非空 ——
232+
- [ ] **每个退役家族一条 D3 `semantic` 条目**(`SemanticMigration`;按家族,不按键),
233+
即使 D2 conversion 无损也欠 —— D2 只承载机械数据修复。`reason`(升级方仍欠的判断)
234+
与 `acceptanceCriteria` 非空;一条一个文件,见 `packages/spec/src/migrations/entries/`
235+
的 README。**没有 source 可重写的响应面键**写不出 conversion,只走这一条 ——
236236
`EnhancedApiError.fieldErrors` 是成品示例。
237237

238238
## 4. 面清单
@@ -248,7 +248,7 @@ conversion 是消费者跟的。两个都要写。
248248
窄全盲(#3883 从 `defineAction` 的输入删了三个键,快照没动)。它的门还住在另
249249
一个 workflow(`TypeScript Type Check`,不是 `Check Generated Artifacts`),读
250250
的是构建出的 `dist/*.d.ts`。
251-
- [ ] **Conversion + 链步 + 精确键 `RETIRED_KEYS_BY_MAJOR` 条目**(§3)。
251+
- [ ] **Conversion + 链步 + 精确键 `RETIRED_KEYS_BY_MAJOR` 条目 + 每家族一条 D3 `semantic` 条目**(§3)。
252252
- [ ] **Liveness 台账** —— 按 §2 的路线表,带 `verifiedAt`。更新 README 的按类型行
253253
**连同计数**(那张表狠狠漂过一次;用 README 里的 python 片段重新生成计数,不
254254
要手改)。

‎content/docs/api/declarative-endpoints.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Custom endpoints declared as metadata
33
navTitle: Declarative Endpoints
4-
description: "Expose your app to systems outside the platform by declaring an apis: endpoint as metadata — which channel to pick, the four publish gates, and the obligation that comes with an anonymous endpoint."
4+
description: "Expose your app to outside systems by declaring an apis: endpoint as metadata — pick the channel, pass the four publish gates, secure anonymous calls."
55
---
66

77
A stack can publish an HTTP endpoint as **metadata** instead of writing a handler: a URL,

‎content/docs/api/plugin-endpoints.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Plugin endpoints — add routes from a plugin
33
navTitle: Plugin Endpoints
4-
description: REST endpoints that become available when the corresponding plugin is installed — auth, workflow, automation, views, realtime, notifications, AI, i18n, and file storage.
4+
description: REST endpoints each installed plugin adds — auth, workflow, automation, views, realtime, notifications, AI, i18n and files — and how to discover them.
55
---
66

77
These REST endpoints are only available when the corresponding plugin is installed. Check the [discovery manifest](/docs/api#discovery) `services` map before calling them; all paths are relative to the base URL (defaults to `/api/v1`).

‎content/docs/automation/approvals.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Approval chains — multi-step sign-off rules
33
navTitle: Approval workflow
4-
description: Route a record for sign-off — who can configure the automation, and the run-identity decision that keeps an approval flow from quietly bypassing row-level security.
4+
description: Route a record for sign-off with an approval flow — who may configure it, and the run-identity choice that keeps it from bypassing row-level security.
55
---
66

77
## Scenario

‎content/docs/automation/connectors.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Connectors — call external systems safely
33
navTitle: Connectors
4-
description: Call external systems from flows — plugin-registered connectors, and declarative provider-bound instances (rest / openapi / mcp) authored as pure metadata with reference-based credentials.
4+
description: Call external systems from flows with plugin connectors or declarative rest, openapi and mcp instances — pure metadata with reference-based credentials.
55
---
66

77
> **Status:** Shipped · **Audience:** App authors (human and AI), integration

‎content/docs/capabilities/index.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: What can it do? — the capability overview
33
navTitle: What Can It Do?
4-
description: The platform's capabilities in business language — data, views, automation, approvals, permissions, analytics, AI, integrations — each with a real CRM as the running example
4+
description: What the platform can do, in business language — data, views, automation, approvals, permissions, analytics, AI and integrations, shown on a real CRM.
55
---
66

77

‎content/docs/concepts/metadata-lifecycle.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Metadata lifecycle — load, publish and HMR
33
navTitle: Metadata Lifecycle & HMR
4-
description: How metadata flows through Repository → Change Log → Cache → Registry — the canonical event stream that powers Studio HMR, REST writes, and future cloud editing.
4+
description: How metadata flows through Repository, Change Log, Cache and Registry — the event stream behind Studio hot reload, REST writes and cloud editing.
55
---
66

77
This page documents the metadata data path introduced by [ADR-0008](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0008-metadata-repository-and-change-log.md) and refined by [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0005-metadata-customization-overlay.md). It is the canonical event stream that powers Studio Hot Module Replacement (HMR), REST writes, and future cloud editing.

‎content/docs/concepts/north-star.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: North star — why this framework exists
33
navTitle: North Star
4-
description: ObjectStack's current product and architecture direction.
4+
description: "Where ObjectStack is headed: a metadata-native backend that humans and AI agents can both operate safely — its principles, runtime shape and non-goals."
55
---
66

77
ObjectStack is the metadata-native backend for business software that humans and

0 commit comments

Comments
 (0)