Skip to content

Commit 4a4fe17

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-80b6qg
2 parents bc8ea24 + 94d3e25 commit 4a4fe17

79 files changed

Lines changed: 4079 additions & 261 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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
perf(app-shell): a dashboard opens from the app bundle and asks only its widgets' objects by name
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Every open of `/apps/APP/dashboard/NAME` read the `GET /api/v1/meta/dashboard` list, the whole object registry (`GET /api/v1/meta/object`) and the `GET /api/v1/meta/view` list, and the widgets' dataset queries waited for all three, though the app bundle already held the dashboard. Now:
11+
12+
- The dashboard route reads its dashboard from the app bundle when the bundle holds the one the app's package owns, and the `/meta/dashboard` list (prefer-local) only for a dashboard outside it, as the page route does.
13+
- It reads the app's object scope instead of the whole registry: the objects its widgets require (`requiresObject`), asked by name, beside the app's package list. A widget whose object the server answers absent is left out, as before. A dashboard whose widgets require no object does not wait for any object read.
14+
15+
What the dashboard shows is unchanged.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
'@objectstack/platform-objects': patch
4+
'@objectstack/core': patch
5+
---
6+
7+
feat(plugin-auth): a platform administrator sees who holds platform-admin standing from Setup → Users (#28793)
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
The Users list carries no column for platform-admin standing, because that standing is derived per user and is not stored on `sys_user`. The list toolbar now has one entry instead, **Platform Administrators**, which opens a list of every user who holds that standing right now.
13+
14+
- **The answer.** `POST /api/v1/auth/admin/list-platform-admins` (`@objectstack/plugin-auth`) enumerates the candidates with the last-admin guard's own enumerator: the in-window `admin_full_access` grants the Default Organization owns, and the verified `sys_user` rows on the `OS_PLATFORM_OWNER_EMAIL` list. It then confirms each candidate through `hasPlatformAdminStanding`. The roster can only be narrower than that predicate, never wider. Organization owners and admins are not platform administrators and are not listed.
15+
- **Who may read it.** Only a caller who holds platform-admin standing. Anonymous callers get 401, and every other caller, an organization owner or admin included, gets 403 `PERMISSION_DENIED` with no data.
16+
- **Exported.** `resolveAdminUserIds(engine, { standing })` (`@objectstack/plugin-auth`): `'administrators'` (the default) is the population the break-glass guard protects, and `'platform-admins'` drops the organization half. The guard's own behaviour is unchanged.
17+
- **Pinned.** `ADMIN_STANDING_PLATFORM_ENUMERATION` (`@objectstack/core`) names the enumerator and classifies every input of the administrator derivation as a candidate source or not, so a new source of platform-admin standing is red until the enumerator reads it.
18+
- **Unchanged.** The Users list still reads one page per request. No `sys_user` field is added.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/metadata-protocol': minor
4+
'@objectstack/lint': minor
5+
---
6+
7+
feat(spec): a field marked `searchable: true` is searched when its object declares no `searchableFields`
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: semantic
11+
12+
`FieldSchema.searchable` was declared, defaulted and served, and nothing read it: `$search` skipped a field marked `searchable: true`, and a `$searchFields` override naming it was refused `400 INVALID_FIELD` "is not searchable" while the object's served schema said `searchable: true`. It is now honoured (ruling record 6573569376, letter A).
13+
14+
- **Before.** With no `searchableFields` declared, the search set was the type-based auto-default only: the text-like and `select` columns. A `code`, `html`, `lookup` or `json` field marked `searchable: true` was not scanned, and naming it in `$searchFields` answered 400.
15+
- **Now.** With no `searchableFields` declared, the set is the type-based auto-default plus every field marked `searchable: true`, of any type except `secret`, `password` and `vector`, which stay excluded (a virtual `formula` field has no stored column and stays out too). The `$searchFields` ingress allow-list is the same set, so naming a marked field narrows the search to it instead of answering 400. The flag is read before the auto-default's own exclusions, so a marked `hidden` field or a marked field named like a system column joins too.
16+
- **Unchanged.** A declared `searchableFields` is still the whole search set, and no field's own flag is read there. A field without the flag (or with `searchable: false`, the default) resolves exactly as before. A credential field is still never scanned.
17+
- **What may now match more.** A field you already marked `searchable: true` on an object without `searchableFields` is now part of `$search`. For a `lookup` that means its stored record id, not the related record's title. Remove the flag, or declare `searchableFields` to pin the set.
18+
- **New export.** `SEARCH_FLAG_EXCLUDED_TYPES` (`@objectstack/spec/data`) names the three hard exclusions. `SearchFieldMeta` gains the optional `searchable`.
19+
- **Messages.** The `$searchFields` refusal for a field outside the auto-default, and the `searchable-field-unsearchable` lint verdict, now name the missing `searchable: true` and offer it as the fix, instead of calling the field's type unscannable.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/service-automation': patch
3+
---
4+
5+
fix(service-automation)!: a flow's record nodes treat a computed filter operand as a value, never as a query operator
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes. A flow definition the save doors accept is accepted as before; what narrows is the run-time value a `get_record`, `update_record` or `delete_record` node takes from an evaluated filter operand. There is no stored row or authored key for `objectstack migrate meta` to rewrite and no tombstone. -->
11+
12+
**The rule.** A flow's record nodes (`get_record`, `update_record`, `delete_record`) treat an evaluated value in a filter's operand position as a value, never as a query operator: a value that is not a scalar (or a list of scalars where the operator takes a list) is refused before the query runs.
13+
14+
- **A value.** A string, a number, a bigint, a boolean or a date. At the comparand of `$in`, `$nin` or `$between`, a list of them with no empty entry.
15+
- **Refused.** Anything else an operand expression evaluates to (an object, a list where one value belongs, a list holding anything but values, a list with an entry that is nothing), at every operand position and under every key, whatever the value came from. The node fails as a guard before the data engine is asked, and the message names the node, the expression and the operand's place in the filter.
16+
- **Unchanged.** The filter the author wrote: its fields, operators and `$and` / `$or` / `$not` conditions, and every literal operand. An operand that evaluates to nothing is refused as before.
17+
18+
**BREAKING** for a flow that hands a computed object or list into a filter operand where the rule above refuses it: it now fails at that node. Make the expression evaluate to the value the condition compares against (for a record, read its `id`), and write any condition the flow needs in the filter itself.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
fix(objectql)!: a number or a boolean written to a text-family field is stored as the string the validator judged
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No metadata key, value or type moves, and no export or type changes: the write door now stores the string the record validator already judged a number or boolean by. The narrowed face is a runtime answer on a unique text-family field (a second write that the stored value now collides with), not anything an author wrote, so there is nothing for objectstack migrate meta to rewrite and no tombstone. Rows already stored are not rewritten by this change, so no stored-row migration is prescribed either. -->
11+
12+
**BREAKING** for one case only, a `unique` text-family field. FROM: `{ code: 42 }` and then `{ code: "42" }` (or `true` and then `"true"`) both answered `201` and stored two rows, on driver-memory and on SQLite. TO: both writes store `"42"`, so the second one is refused `409 UNIQUE_VIOLATION`. The fix for a caller that hits it: the two writes are the same value; send the string once, or update the existing row. In the other direction, SQLite used to refuse `true` after `1` (and `false` after `0`) because both were stored as `"1.0"` (`"0.0"`); both now land as two different strings (`"true"` and `"1"`). driver-memory admitted both before and still does.
13+
14+
The record validator's string arm (`text`, `textarea`, `email`, `url`, `phone`, `password`, `markdown`, `html`, `richtext`, `code`, `signature`, `qrcode`) judges a number or a boolean by its string reading: `42` is bounded and format-checked as `"42"`, `true` as `"true"`. The write used to carry the raw scalar to the driver, so the stored value depended on the driver: driver-memory stored and read back the number `42` and the boolean `true`, and SQLite stored the TEXT `"42.0"` for `42` and `"1.0"` for `true` (a `maxLength: 2` field admitted `42` and then held four characters). The write door now writes the judged string into the payload, beside the numeric-string rewrite on number fields, on insert, insertMany, update by id, update by predicate and the `validate()` dry run.
15+
16+
What you will see: a text-family column now stores and reads back `"42"` where driver-memory stored `42`, and `"42"` / `"true"` where SQLite stored `"42.0"` / `"1.0"`. A single write is answered as before: the same payloads are admitted and refused. What moves is the unique answer above, and hooks, formulas and filters now see the string. A filter that compares a text-family field to the number `42` (or to a boolean) no longer matches a row written as `42`, because that row is now stored as `"42"`: filter with the string. The fix on the filter side is tracked separately. Rows already stored are not rewritten, so an old SQLite `"42.0"` row, or a raw `42` in a driver-memory store with `persistence` enabled, does not collide with a new `"42"`. Unchanged: strings, `null`, missing keys, objects and arrays (refused), `json` and the structured types, and number and boolean fields.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): a form view opened in Studio shows its served warnings, such as a section naming a field the object lacks, at that section
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Since `@objectstack/metadata-protocol` began serving the lint's `form-field-unknown` finding in a form view's `_diagnostics.warnings`, Studio's form view canvas never showed it: the Interfaces pillar's load stripped `_diagnostics` with the other read decorations and handed the canvas no verdict, so an author kept designing a form whose field is silently skipped at runtime.
11+
12+
- **The Interfaces pillar keeps the verdict.** The open view's load keeps the pending draft's `_diagnostics` (else the published layer's) beside the body it judged, and hands it to the canvas through the preview contract's `diagnostics`. It is never part of the edit buffer, so no save carries `_diagnostics`. The pillar does not read the view again after an autosave, so a warning is dropped once the author edits the value it judged.
13+
- **The form view canvas shows the warnings.** Each warning is listed in the canvas's warn block under the existing "N warning(s)" heading, named by the section it points into (its label, else its name), with the server's message as served. Nothing is shown when there are none, and nothing holds a save.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
fix(rest)!: a bracketed `packageId` or `type` key on `GET /meta/_drafts` (`?packageId[]=ID`, `?packageId[0]=ID`, `?type[]=TYPE`, `?type[0]=TYPE`) is refused 400 naming the key instead of being ignored and answered with every package's or every type's drafts
6+
7+
Clause-②: no (narrowing) — `GET /meta/_drafts` with any query key that opens `packageId[` or `type[` (on `RestServer` and on the runtime dispatcher, which share the arity decision) is refused with `400 VALIDATION_ERROR`; it answered 200 with every draft the caller may read before. No package export, answer member, error code, status or `rule` tag is added: the refusal reuses the `bracketed-parameter` rule the list and the book tree already answer.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped: a drafts request that spells its one-package or one-type filter as an array key is refused instead of served unfiltered. There is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing on the `/meta/_drafts` read (`@objectstack/rest`).
13+
14+
- **Before.** `?packageId[]=ID`, `?packageId[0]=ID`, `?type[]=TYPE` and `?type[0]=TYPE` (the qs-style array spellings) reached the door under the key as spelled, which neither filter read, so an author who asked for one package's or one type's drafts got every draft, as if it were the filtered list. Beside a plain `?packageId=` the bracketed key was silently dropped.
15+
- **Now.** The door refuses a key that opens `packageId[` or `type[` after its capability gate and its multiplicity gate, before any read: `400 VALIDATION_ERROR`, message `Query parameter 'packageId[]' names no package; the 'packageId' filter takes exactly one package id, spelled ?packageId=ID. …` or `Query parameter 'type[]' names no metadata type; the 'type' filter takes exactly one metadata type, spelled ?type=TYPE. …`, and no list. A bracketed key beside the plain one is refused too.
16+
- **Unchanged.** `?packageId=ID` and `?type=TYPE` narrow as before, and leaving them out lists every draft; a repeated `packageId` or `type` keeps its 400; a caller with no authoring capability is still refused 403 first.
17+
- **Who sees it.** An API client that serialises arrays qs-style. `@objectstack/client`'s `listDrafts` builds both keys with `URLSearchParams` (no brackets), and the console sends `?packageId=` plain.
18+
19+
**The remedy.** FROM `?packageId[]=ID` (or `?packageId[0]=ID`) TO `?packageId=ID`; FROM `?type[]=TYPE` (or `?type[0]=TYPE`) TO `?type=TYPE`; leave the key out to list every draft.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): Studio's flow definition switch keeps keyboard focus while its save is in flight and after the answer lands
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
On Studio › Automations, a writable package's flow carries a switch that turns the flow on or off in its draft. While that save was in flight the switch drew `disabled`, and a browser moves focus off a focused control that turns disabled, to the page body, where it stayed once the save landed. A keyboard or screen reader author had to tab back from the top of the page after every flip, saved or refused. In flight the switch is now `aria-disabled` and refuses the press in its own handler, so it keeps focus and a second Space still sends no second save. A read-only package's switch, and one with no canvas to edit, stay `disabled` and described as before.

‎.claude/skills/pm-dispatch/references/rest-channel.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
- ✓ 请审 `POST .../pulls/{n}/requested_reviewers`(Tier H 归中继 op)· 开 PR `POST .../pulls` 带 `draft=true`。
4848
- ✓ `origin/main` 合进 PR head:中继 op `pr_update_branch`(`pull` + 40 位 `expected_head_sha`),真合并提交。
4949
- 挂子卡走中继 op `sub_issue_add`/`sub_issue_remove`(`issue` 父号,`sub_issue` 子号),回读父列表。
50-
- ✓ 安全通报经中继 `advisory_report`·`advisory_collaborators_add`·`advisory_state`;发布归维护者。
50+
- 公告经中继 `advisory_create`(首选)等四操作,规则住 `security-findings.md`;发布归维护者。
5151
- ✓ 落地经中继 `node scripts/pm/fleet-write/dispatch.mjs --repo O/N --actions-file F`;`--via dispatch` 同载。
5252
- ✓ 启动 run 走 op `workflow_dispatch`:解锁物是白名单 run 才启;入表只经 workflow 自己的 PR。
5353
- F = `[{"op":"workflow_dispatch","workflow":"release.yml","ref":"main","inputs":{"refresh_version_pr":true}}]`。

‎.claude/skills/pm-dispatch/references/security-findings.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@
1818
## 3. 私卡是仓库安全公告(选 B)
1919

2020
- 私卡 = 受影响仓上一份未发布的安全公告,只有管理员、安全经理与受邀协作者可见。
21-
- 建草稿公告须管理员或安全经理,中继无此操作 ⇒ 草稿由维护者建,⛔ 席位不建。
22-
- ⛔ 席位永不以自身账号私报或写公告:GitHub 写只走中继,以 App 身份。
23-
- 中继私报操作上线前一律会话专递(选 A):机制只在会话里交维护者,由其私报或建草稿。
24-
- 中继私报与加协作者操作上线后,席位经它私报、带具名账号进公告;未上线 ⛔ 不假设有。
21+
- 首选中继 `advisory_create` 建草稿公告,再 `advisory_collaborators_add` 带具名账号进公告。
22+
- `advisory_report` 只留给 App 无公告写权的仓:GitHub 按账号限每日新私报,全队共一个 App。
23+
- ⛔ 席位永不以自身账号私报或写公告:GitHub 写只走中继,以 App 身份;发布归维护者。
24+
- 两操作皆被拒 ⇒ 会话专递(选 A):机制只在会话里交维护者,由其建草稿,⛔ 不换账号重发。
2525
- 私有仓无私密漏洞报告功能,那些操作上线后仍会话专递。
2626
- 回退时座位贴只写不透明编号与状态,⛔ 无细节。
2727
- 修复经 pm-dispatch 以无卡派发令派 `os-dev`,`xhigh`;机制只进派发令与公告。
2828
- 用到公告的临时私有 fork ⇒ 修复从该 fork 落地。
2929

3030
## 4. 跨仓发现者(应用仓量框架、cloud 席位)
3131

32-
- 中继私报操作上线后在框架仓经它私报(第 3 节);此前 ⇒ 机制在会话里交维护者。
32+
- 在框架仓经中继建草稿公告或私报(第 3 节);中继被拒 ⇒ 机制在会话里交维护者。
3333
- 公开面至多一行「a security item is with the maintainer」,⛔ 无细节。
3434

3535
## 5. os-dev 报告

0 commit comments

Comments
 (0)