Skip to content

Commit 8fc7f88

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-qf9muq
2 parents 3ff9597 + 2f380dd commit 8fc7f88

279 files changed

Lines changed: 17332 additions & 721 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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): the connector docs say where an undeclared key's refusal is located: at the entry, with the key named in `keys`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A connector entry refuses a top-level key it does not declare, through `DeclarativeConnectorEntrySchema` and `defineConnector()`. The TSDoc on both said the refusal was "located at the key". It is not. zod raises one `unrecognized_keys` issue at the entry itself: its path is empty when the entry is parsed alone and `connectors.N` inside a stack, and it lists every undeclared key in `keys` and in its message. An author or agent looking for an issue at the key's path found none.
11+
12+
The TSDoc now says this. The same correction is made in the reason of the protocol-18 upgrade note for undeclared connector keys (`connector-entry-undeclared-key-refused`), which is not released yet.
13+
14+
Prose only. The refusal, its code, its path, its message and what the connector schema accepts are unchanged.

‎.changeset/28856-action-stale-summary-answer.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ When a declarative update action (`operation: 'update'`) writes its record but a
1515
- **MCP `run_action`**: the usual `{ ok, action, objectName, recordId, result }`.
1616
- The action's invocation audit row records `succeeded`, the outcome the door answers.
1717

18-
The runtime composes the action's result once and attaches it to the refusal through the existing carrier; each door relays it. The stale summary column is logged at `error` once per field per process. A refusal before the write answers exactly as before. Other action kinds are unchanged: an `api` action's forward already lands on a data door that answers success, a `flow` action's record nodes turn the refusal into a node failure, and a script or body action is a separate follow-up.
18+
The runtime composes the action's result once and attaches it to the refusal through the existing carrier; each door relays it. The stale summary column is logged at `error` once per field per process. A refusal before the write answers exactly as before. Other action kinds: an `api` action's forward already lands on a data door that answers success, and a `flow` action's record nodes and a script or body action's own writes answer a landed write's success since #28861.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/runtime': patch
3+
'@objectstack/service-automation': patch
4+
---
5+
6+
fix(runtime,service-automation): a write in an action, hook or job body, or a flow record node, that landed under a failing roll-up answers its own success (#28861)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
When a child write is stored but a parent's roll-up summary cannot be recomputed after it (`ERR_SUMMARY_RECOMPUTE`, "the triggering records WERE written"), the data doors and MCP tools already answer the write's success. The write surfaces an author's code holds now do the same, so the code's next line runs instead of stopping on a write that landed. Each answers exactly what the same write answers when every roll-up recomputes — no new code, member or shape:
12+
13+
- **`ctx.engine`** in an action handler: `insert` resolves `{ id }`, `update` and `delete` resolve. A multi-id `delete` deletes every id; a stale roll-up after one id no longer leaves the later ids undeleted.
14+
- **`ctx.api`** in an action handler, and every context it derives (`sudo()`, `withRunAs(...)`, a `transaction(fn)` callback's context): each repository write verb resolves with the engine's result.
15+
- **A sandboxed hook, action or job body's `ctx.api.object(...)`**: the write resolves in the body with the engine's result, so a hook no longer refuses the write that fired it, an action body returns its own value, and a job run reports its own outcome.
16+
- **Flow `create_record`, `update_record` and `delete_record`**: the node succeeds with the output it gives on a clean write (`output`, `outputVariable`, `droppedFields`, `acted`), and the run goes on to its next node.
17+
18+
The stale summary column is logged at `error` once per field per process, with the fix, as on the data doors. A refusal raised before the write is stored (validation, permission, constraint) reaches the author's code exactly as before. No transaction is added: a body's earlier writes stay committed, as they always did. Inside a transaction the body opens itself (`ctx.api.transaction(fn)`), such a write now answers its success in the callback too, so that transaction commits where the refusal used to roll it back.
19+
20+
The REST and MCP action doors answer a refusal carrying a write's attached answer as the action's success only for a declarative update action, whose refusal carries the action's own result. Any other action that throws such a refusal is answered as the failure it is, and its invocation audit row records `failed`.

‎.changeset/28912-hide-fields-default-on.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ A declared default moves. A list view whose `userActions` block says nothing abo
1212

1313
- **Before.** `UserActionsConfigSchema.hideFields` defaulted to `false`, and the list renderer drew the control only for an explicit `hideFields: true`. Generated apps and the CRM example never declared it, so their lists had no way to hide a column.
1414
- **Now.** The default is `true`, and the list renderer reads `hideFields !== false`, so the spec default and the renderer agree. A view with no `userActions` block parses with none, and the renderer reads the absent key as the new default.
15-
- **Interface pages.** A page `interfaceConfig.userActions` block that omits the key parses to `true` as well, but an interface page keeps its own closed-by-default toolbar, as it already does for `filter`, `rowHeight` and `group`: it draws the control only for an explicit `hideFields: true`.
15+
- **Interface pages.** A page `interfaceConfig.userActions` block that omits the key parses to `true` as well, and the interface page reads the same default: a page that omits the key, or has no `userActions` block, offers the control, and `hideFields: false` withholds it.
1616
- **Where the choice is kept** does not change. The control only subtracts from the columns the view draws. On a view that declares `columns`, or a saved view, the choice is stored on the reader's own list state and survives a reload for that reader only. On a system view that declares no `columns`, it lasts for the session. It never writes the shared view.
1717
- **To withhold the control**, write `userActions: { hideFields: false }`. Every authored `hideFields` parses exactly as before, and a non-boolean is still refused.
1818
- **Compiled artifacts.** A document that serialised an earlier parse (an `os compile` artifact) carries the old written `false` and keeps withholding the control. Recompile it, or delete the key.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): an MCP grant that holds no federation capability is no longer listed `list_remote_tables` / `import_object` (#28938)
6+
7+
Clause-②: no — no call that passed before is refused now, and none that was refused passes: what changes is that a request whose every federation call would answer `403 PERMISSION_DENIED` is no longer listed the pair.
8+
Metadata-protocol: none
9+
10+
The `/mcp` door hands an OAuth agent the delegating user's system capabilities only when the grant names `actions:execute`. A `metadata:write`-only grant therefore holds none, yet it was listed `list_remote_tables` and `import_object`, and every call answered `403 PERMISSION_DENIED`, even when the user is an administrator.
11+
12+
- **Now.** The runtime's MCP bridge carries the pair only when the request holds `manage_platform_settings` (the listing's capability) or `manage_metadata` (the import's). Without either, `tools/list` omits both, the way `list_actions` omits an action its caller may not run. The listing decision and the per-call check read the same capabilities, so they cannot disagree.
13+
- **Unchanged.** A grant naming `metadata:write` and `actions:execute`, for a user who holds the capabilities, lists and calls both as before. A request holding one capability is still listed both, and the call that needs the other still answers `403`. API keys and sessions are still not listed the pair.
14+
- **To use the pair from an MCP client:** connect with `metadata:write` and `actions:execute`, as a user who holds the capability.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(app-shell): a landed Studio draft autosave names what it saved
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
A Studio draft autosave used to land with no feedback but the top bar's "Saved HH:MM", which names nothing. Now, once the save lands, Studio shows a toast that names the item: "Draft saved: Task" for an object (or for its customization, in a package that customizes a base), for an Interfaces page, dashboard or report, for an app's navigation, and for a flow. The toast uses one id per item, so the saves that follow each editing pause update the toast already on screen instead of stacking more toasts; another item saved meanwhile gets its own. The top bar's saved hint stays. A refused save keeps its refusal strip and shows no "saved" toast.
12+
13+
- **`@object-ui/app-shell`**: the Data, Interfaces and Automations pillars announce each landed draft autosave by the item's label.
14+
- **`@object-ui/i18n`**: one new studio-namespace string, `engine.studio.draftSaved` ("Draft saved: {item}"), in English and Chinese.
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(studio): a dashboard Global Filter whose Options From is begun but not finished is held, instead of autosaving a filter the draft door refuses
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Studio already held a list item just added under Properties (Global Filters, header Actions, a report's Order or Blocks) until its required inputs are set. It only asked for an unset input directly below the item, so a filter with Input Type `select` and Options From given its Object but not yet its Value Field and Label Field read as finished. The autosave sent it, the draft door answered 422, and every later edit (Description, Label) carried the same filter and was refused too, then lost on reload. The rule now follows each refusal down from the item: an input left unset at any depth below it (absent, or a text cleared to blank) holds the item, named by the item's own form row ("Options From on item #1 of “Global Filters” needs a value"). Every later edit is kept and sent once the item is finished. An item refused for a value the author gave, at any depth, is still sent, so that refusal stays on screen.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/plugin-list': patch
3+
---
4+
5+
fix(plugin-list): a list view whose `sharing` sets `lockedBy` and no `type` shows its lock indicator
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The list toolbar drew its read-only sharing indicator only when the view declared `sharing.type`. `ViewSharingSchema` defaults `type` to `collaborative`, and the served view carries the authored body without that default, so a view authored as `sharing: { lockedBy: 'admin@objectos.ai' }` showed no lock: other readers did not learn the view was locked.
11+
12+
- **Now.** The indicator draws when `sharing` declares a `type` or a `lockedBy`; the lock keys on `lockedBy` alone. An absent `type` shows the spec's default type label ("Shared view"), read from `ViewSharingSchema` rather than restated in the renderer.
13+
- **Unchanged.** A view with `type` set renders as before, a `sharing` block with neither key shows nothing, and no key, value or copy changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/plugin-report': patch
3+
---
4+
5+
fix(plugin-report): a report draws a dataset dimension's `nullLabel` for its null bucket
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A dataset dimension can name its null bucket with `nullLabel` ("No region"), and the dataset query answer carries it on the column, `fields[].nullLabel`. The dashboard widget already drew it, but a report drew the same bucket as "—", so one dataset read "No region" on a dashboard and "—" in a report.
11+
12+
The report now draws the declared label wherever it draws a null dimension value: the summary table's rows and group headers, the matrix's row and column headers, and the CSV and XLSX export cells, so a download says the same words as the screen. A dimension that declares no `nullLabel` keeps "—", and every non-null value, an empty string included, is drawn as before. Grouping, totals and drill-through are unchanged: they still read the raw value.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): an app that shares its package with the package's first app opens itself from the avatar menu, the ⌘K palette, the app switcher, Home's tiles and the report index's other-apps links
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`/apps/PACKAGE_ID` opens a package's first app, so a link keyed on the package id cannot reach the package's other apps; their name is the only address that does. The avatar menu's entry for a hidden app linked to its package id, so a hidden app shipped beside its package's main app (a settings-style app) opened the main app instead and could not be reached from the menu at all. The ⌘K palette's "Switch app" items, the app switcher's rows, Home's "Your apps" tiles (and the `app:launcher` block, which draws the same tiles) and the empty report index's "Reports in your other apps" links had the same fault for every non-first app of a package.
11+
12+
Each now links to the app's one address, the address the Console already resolves an open app to: the package id when it opens this very app, else the app's name. An app that is the only or first app of its package keeps its package-id address. Home's tiles resolve the address among the full app list, hidden apps included, so a visible app behind its package's hidden first app also opens itself.

0 commit comments

Comments
 (0)