Skip to content

Commit bac10f7

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-jqgfyc
2 parents 44d5553 + c16040d commit bac10f7

137 files changed

Lines changed: 10671 additions & 416 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+
'@object-ui/i18n': patch
4+
'@objectstack/service-automation': patch
5+
'@objectstack/platform-objects': patch
6+
---
7+
8+
feat(console): Connector connections, a Setup page to connect, reconnect or disconnect each OAuth connector instance and see its status
9+
10+
Clause-②: yes (widening: new Setup entry nav_connector_connections resolving automation:connector-connections; no existing key, route or shape changes)
11+
Metadata-protocol: none
12+
13+
A connector instance declared with `auth: { type: 'oauth2', grant: 'authorization_code' }` needs an administrator to sign in to the vendor once for their organization. The Console now has a page for that (`automation:connector-connections`). `@objectstack/service-automation` contributes its Setup → Integrations entry, **Connector Connections**, gated on `manage_connector_connections`, the organization-scoped capability the connection door asks, so an organization administrator sees it; `@objectstack/platform-objects` carries the entry's label in its four Setup translation packs. The page lists every such instance with the organization's connection status as the server answers it (Not connected, Connected, or Reconnect required with the reason the server recorded), the granted scopes, who connected it and when, and when the access token renews. A `degraded` instance shows the server's reason in the same row as the connection that caused it.
14+
15+
**Connect** and **Reconnect** open the vendor's sign-in in its own window, detached from the page, and send it to the authorization URL the server answered. The page re-reads the status until the connect lands, then closes the window and shows the new status. **Disconnect** removes the connection after a confirmation. No token, code or authorization URL is ever rendered.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/formula': patch
3+
---
4+
5+
feat(formula): a CEL syntax refusal keeps the engine's own fault code and the source range it points at, so an editor can say what is wrong and where in its own words (#27904)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`validateExpression`'s catch-all CEL refusal, `invalid-cel`, now carries two optional params beside `role` and `detail`:
11+
12+
- `celCode` — cel-js's own code for the fault, read off its error object: the parser's for a syntax fault (`unexpected_token` when an operand or the end is missing, `expected_token` when a closing `)` / `]` / `}` is missing, `unexpected_character`, `unterminated_string`, …) or the checker's for a type fault (`no_such_overload`, …).
13+
- `pos` — `{ start, end }`, the UTF-16 offsets into `source` that the engine points at (`end` exclusive). A missing `)` reads `start === end === source.length`.
14+
15+
Both are absent when the engine reported none, and `pos` is absent when the engine read a source the platform rewrote first (the null-guard ternary lowering), since a range into that text would point at characters the author never wrote. A guessed `0` is never written.
16+
17+
`EvalError.pos`, declared before and never filled, is now filled by `celEngine.compile` and `celEngine.evaluate` for a cel-js parse, check or evaluation fault that carries a range, on the same terms.
18+
19+
The refusal's `message` is unchanged, byte for byte, and so are the codes and the params of every other refusal.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics)!: members read through a relationship path answer one refusal on the native, ObjectQL and draft-preview faces
6+
7+
Clause-②: no (narrowing) — the draft preview (`queryDataset` with `previewDrafts: true`) now refuses `INVALID_FIELD` / 400 any member it would read through a relationship path (a dimension, time dimension, measure or filter member), which it served as one `null` group, `[]` or a total of `0`, because it joins nothing; a `timeDimensions` entry and a `runtimeFilter` member spelled through a relationship path the dataset's `include` does not declare are refused `INVALID_FIELD` / 400 on every face, where the native strategy answered `DATASET_INVALID` / 400 and the preview served; and a declared path to a column the related object lacks is refused `INVALID_FIELD` / 400 before SQL, where the native strategy answered `DATABASE_ERROR` / 500. Nothing is widened: no export, answer member, error code or status is added; every code was already on the wire.
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 selection that reads through a relationship path is refused by the preview that cannot join it, a selection naming an undeclared path or a missing related column is refused on the faces that served it or failed on it, and the native face's refusal takes the member-level code the other member gates answer. There is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing on the `queryDataset` draft preview (`@objectstack/service-analytics`), and a code change on two native refusals.
13+
14+
- **Before.** Measured on real SQLite over a dataset `include: ['account']`: the preview answered a backed `account.name` (or a declared dimension over it, or a declared measure over a related field) with one `null` group holding the whole total, a backed `runtimeFilter` member with `[]`, and a windowed backed time dimension with a total of `0`, while the live strategies served the real answer. `OBJECT.region` and `CUBE.region`, which both live strategies resolve to the declared `region`, also came back as one `null` group. A time dimension through an undeclared path was dropped by the preview and refused `DATASET_INVALID` by the native strategy when only windowed; a `runtimeFilter` member through an undeclared path got `DATASET_INVALID` (native), the cross-object filter refusal (ObjectQL) and `[]` or, under an `$or`, the other arm's rows (preview). `account.bogus` reached SQL on the native strategy as a 500.
15+
- **Now.** The service's join gate (the one `dimensions` already asked since the previous fix) also judges `timeDimensions` entries and `where` leaves, so an undeclared path is refused on every face with one message naming the member and the path. A second gate refuses a declared path to a column the related object does not have, naming the member, the column (`field`) and the object (`object`), on every face with one message. The preview then refuses any remaining relationship-path member with a message saying it joins no related object, and answers `OBJECT.region` / `CUBE.region` with the declared dimension's groups.
16+
- **Unchanged.** The live strategies serve a backed path as before (the ObjectQL strategy keeps its own cross-object refusals for a filter, a bucketed time dimension or a measure it cannot join). A path through an object the caller may not read keeps its 403. A hand-authored cube's configured allowlist still answers `DATASET_INVALID`, and so does every other reason that code is raised.
17+
- **Who sees it.** An author previewing a draft dataset that groups, filters or measures through a related object: the preview now says it cannot join instead of showing a number that changes after publish. A caller naming a related field the dataset does not include, or a related column that does not exist: one `INVALID_FIELD` / 400 on every face.
18+
19+
**The remedy.** Publish the draft to query a related field through the live strategies, or preview without it; add the relationship to the dataset's `include`; check the related column's spelling.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): customize a read-only package's view, dashboard or report into its `<base>.custom` pair, with an override badge and "Restore package original"
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
An installed package is read-only in Studio, so until now its views, dashboards and reports could only be looked at there. Studio now customizes them into the base's paired customization package (`<base>.custom`, ADR-0146 D2 and D3), as a declared override of the base's item.
12+
13+
- **Customize.** The item header of a view, dashboard or report on a read-only base, and of an object leaf's list view, offers Customize. Pressing it only unlocks the item's editors. Nothing is written until an edit is saved.
14+
- **The first saved edit.** It calls `POST /api/v1/packages/:id/ensure-customization` once for that item, with no prompt and no body. It then saves the draft into the package the answer names (`201` when the door created the pair, `200` when it was there). The draft is the item as Studio holds it, without its `_` keys, with `overrides: { package, basedOn }`. `basedOn` is the base's `manifest.version` as the package list serves it. ⛔ The pair's id for a write is the door's answer, never a `.custom` spelled in the browser. The first save is pinned to "no draft there" (`If-None-Match: *`), so a draft someone else left in the pair is asked about, never overwritten. A base whose listed manifest states no version gets a disabled Customize that says so. Nothing is guessed.
15+
- **Walled callers.** Under a `group` or `isolated` posture, an admin who is not the platform operator sees Customize disabled, with the reason. This is read from the two facts the server publishes (`features.tenancyPosture`, `user.isPlatformAdmin`). A door that refuses anyway is shown as the server sent it.
16+
- **Pending and Publish.** On a read-only base with a pair, the pending count, the changes panel and Publish act on the pair's drafts. A refusal at the publish door is shown as it is sent. A read-only base with no pair keeps the read-only Publish, as before.
17+
- **Badge.** The header says the item overrides the base (for example "Overrides HotCRM", with the version in its title). It is read from the declaration the server serves: the pair's draft, else the layered read's overlay. It is never computed.
18+
- **Restore package original.** The item's ⋯ menu offers it on an item customized into the pair. After a confirmation, it deletes the pair's draft of the item, then its row (`DELETE /meta/:type/:name?package=<pair>`). The base's item reads back.
19+
- **The read-only mark** no longer links a view, dashboard or report to Setup's overlay page.
20+
- **Unchanged.** A writable package is edited in place, as before. A new view ("Add view") on a read-only base stays unavailable. The item tools (history, source) stay read-only on the base.
21+
- **New strings.** `@object-ui/i18n`'s studio namespace gains twelve `engine.studio.customize.*` keys in `en` and `zh`. The other languages fall back to `en`, as the whole namespace does.
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+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): "New similar" on a flow in the Automations pillar, opening the copy where the clone door landed it
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
An admin who wants a second flow like one a package ships, for another rule, gets it without changing the package's flow (ADR-0146 D4). The flow clone door lands the copy switched off in a writable package, by default the source's paired `<base>.custom`, so Studio can offer the gesture where the flow is designed.
12+
13+
- **New similar in the flow's menu.** The open flow's ⋯ menu offers "New similar" on a read-only package's flow and on a writable package's flow. It asks for a new machine name and a label, both mandatory, and posts them to `POST /api/v1/automation/:name/clone`. On a read-only base the request names no package, so the door picks the pair; on a writable package it names the open package.
14+
- **The copy opens where the answer says.** The door's answer names the package the copy landed in. A copy in another package opens on that package's Automations pillar (`/studio/<package>/automations?surface=flow:<copy>`); a copy in the open package opens in place after the rail re-reads. Either way the server's sentence that the copy is switched off is shown as sent. An answer that names no package says so and opens nothing. Nothing records or draws what the copy was made from.
15+
- **Refusals and gates.** A refusal, a `409` for a taken name included, is the server's sentence, shown in the dialog, which stays open. A caller without `manage_metadata` sees the item disabled with the console's sentence for why. A read-only package's flow still offers no Delete, and the package-less scope draws no menu.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): the All metadata area marks a base's customizations, filters to them and counts them
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
In Studio's All metadata area (ADR-0146 D3, "On top, one surface"), an installed base's lists now show its own items and the items of its paired `BASE.custom` package together. Every mark and number comes from the server's customization read, `GET /api/v1/packages/:id/customizations`. Nothing is computed from the documents: there is no diff and no `basedOn` verdict.
12+
13+
- **Marks.** An item the read lists as an override is marked "Customized (BASE)". For a type identified by name and member, such as an email template's locale, only that member is marked. Any other item of the pair is marked "Added". The base's other items carry no mark.
14+
- **Filter.** Each list offers "Only customizations (n)" once the read has answered and the base has a customization.
15+
- **Counts.** Each directory tile shows how many items of its type the read lists as overrides. The object tile also counts the extension members (fields, validation rules, indexes). The directory header shows the base's total, the served overrides plus the served extensions, which is the landing's "customized" figure.
16+
- **Pending or failed read.** While the read is pending, or after it fails, the area draws no mark, no filter and no number, never a "0". A failure shows the server's sentence. A base with no pair is unchanged, and so is Setup, which makes no customization read.
17+
- **Across packages.** In `/studio/~all/metadata`, an item of a `BASE.custom` package is labelled "Customization of BASE".
18+
- **One mark component.** `CustomizationMark` (`views/studio-design/CustomizationMark.tsx`) draws these marks for the whole console, and the pillar rails will reuse it. `fetchPackageCustomizations` returns the read in full, and `fetchPackageCustomizationCounts` (the landing's reader) now uses it.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): "New similar" on a read-only base's permission set in the Access pillar, opening the copy where the clone door landed it
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
A customer's own permission set is a new set in the package that holds their changes (ADR-0146 D6). Since the clone door lands a copy of a read-only package X's set in X's paired `X.custom`, Studio can offer that gesture where the base is designed.
12+
13+
- **New similar in Studio.** In the Access pillar of a read-only package, the open permission set offers "New similar". It asks for a machine name and a label (the same dialog as Setup's catalog) and posts exactly `{ name, label }` to `POST /security/permission-sets/:name/clone`. The client sends no package and no facet: the server decides where the copy lands.
14+
- **The copy opens where the answer says.** The door's answer names the package the copy was saved in. The pillar opens that package's Access pillar with the copy selected (`/studio/<package>/access?surface=permission:<copy>`), and shows the server's notice there as sent. An answer that names no package (absent or `null`) or no copy says so, and opens nothing.
15+
- **Refusals and gates.** A refusal (400, 403, 404, 409) is shown in the server's words in the dialog. Under a walled posture, or for a caller without `manage_metadata`, the action is drawn disabled with the console's own sentence for why. The base's sets stay read-only, and no override gesture is drawn on a permission set.
16+
- **One label.** The action reads "New similar" (zh: 新建类似) on Setup's catalog too, and the guidance strings that name it say the server saves the copy in the package that holds your changes. Setup's post-clone hop is unchanged.
17+
- **Access pillar keyed by package.** Like its sibling pillars, it remounts when the package changes, so opening the copy in another package's pillar no longer keeps the base's open set.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@objectstack/service-automation": minor
3+
---
4+
5+
fix(service-automation)!: a connector stored in the environment never reads the host's files
6+
7+
A provider-bound connector saved through the metadata door (`PUT /api/v1/meta/connector/:name`, Studio), environment-wide or into a package created in the environment (`POST /api/v1/packages`, an `authored` package), had its package-relative refs resolved against the HOST's root, as if the host's own stack had declared it. A stored definition is no package's declaration and has no directory on disk, so a relative ref it names is now refused, the way an installed package's has been since #28534.
8+
9+
Clause-②: no (narrowing) — a stored connector entry (environment-wide, or in an `authored` package) is refused its package-relative file and path refs, which it was handed before against the host's root. Nothing is widened.
10+
11+
Metadata-protocol: narrowed (a stored connector's `providerConfig` may no longer name a package-relative file, and a stored stdio `mcp` transport is refused)
12+
13+
<!-- adr-0087: not-required (no-migration-prescription) No key is added, removed or renamed and the stored shape is unchanged: the same `providerConfig` is accepted at the save door, and what is refused is the host's root being read on the stored entry's behalf. No conversion can carry a file the environment never held; the refusal names the entry, its package if it has one, the ref and the remedy. -->
14+
15+
**BREAKING** behaviour narrowing for stored connectors (`@objectstack/service-automation`), shipped as `minor`.
16+
17+
- **Before.** A stored entry's package-relative refs (an `openapi` spec path, a stdio `mcp` transport's working directory) resolved against the host's root.
18+
- **Now.** Every such ref is refused for a stored entry, naming the entry, its package if it has one, and the ref: a configuration fault, so the instance is not materialized and the reconcile logs the refusal. It never fails a boot: a stored row already at rest when the platform starts is logged and skipped like one saved while it runs, and the host's own entries beside it serve. Only an entry the host's stack declares reads the host's root: one a package registration filed with the same provider binding, or one the host's own artifact carries. A stored row is never read as the host's, whatever `_packageId` its body carries.
19+
- **Unchanged.** A stored entry carrying its document inline or as an `http(s)` URL, every entry of the host's own stack, and an installed package's entries (refused as before).
20+
- **Who sees it.** A connector saved in the environment whose `openapi` spec is a package-relative path, or whose `mcp` transport is stdio.
21+
22+
**The remedy.** Carry the document inline in the definition, or name it by an `http(s)` URL; reach an MCP server from a stored entry over `http`.

0 commit comments

Comments
 (0)