Skip to content

Commit c700e99

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-1zrim8
2 parents ed27959 + 6410029 commit c700e99

992 files changed

Lines changed: 57722 additions & 9374 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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/plugin-chatbot': patch
3+
'@object-ui/i18n': patch
4+
'@object-ui/app-shell': patch
5+
---
6+
7+
feat(studio): the right slot counts the copilot's items awaiting confirmation, 「AI 副驾 · N 项待确认」
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
Studio's right slot used to say only that "a plan awaits your approval", and only for a build plan. The copilot's change plan (the 「修改方案 · N 项」 card it proposes for an app that already exists) did not light it at all. Now:
13+
14+
- **The count.** The copilot tab of the slot's 属性 | AI 副驾 header carries a badge with the number of items awaiting confirmation, and while the properties show, the folded copilot strip at the slot's foot reads 「AI 副驾 · N 项待确认 · 回到对话 →」 (en: "AI copilot · N awaiting your approval"). The number is the rows of the newest change plan, or 1 for a build plan. The badge and the strip go away the moment the author clicks 存为草稿 / Confirm or Build it, or the change lands.
15+
- **A change plan is a proposal.** The chat's reading of "the newest proposal still waits for the user" now includes a change plan whose card still offers its confirm button, not only a build plan whose card offers "Build it". The console's chat launchers (the FAB and the ChatDock edge launcher) therefore also show their waiting marker while a change plan awaits confirmation.
16+
17+
`@object-ui/plugin-chatbot`: the `onPlanApprovalPendingChange` prop on `ChatbotEnhancedProps` now receives a second argument, `count: number` (the items the newest proposal asks the user to confirm; 0 when nothing is pending), and it fires when either value changes. A host that reads only the first argument is unaffected. The boolean now also reads `true` for a pending change plan (see above).
18+
19+
`@object-ui/i18n`: the `studio` namespace gains `engine.studio.slot.pendingCount` (en and zh, interpolates `count`). No key is removed or renamed; `engine.studio.slot.planPending` stays in the table.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
fix(plugin-audit)!: the compliance ledger's deployment-level rows (no `tenant_id`) are served to the platform administrator alone, in every tenancy posture, `single` included (#26750)
7+
8+
Clause-②: no (narrowing) — under `single`, a non-system reader of `sys_audit_log` that is not the platform administrator (an organization administrator, the holder of a set that grants the ledger read, a holder of `view_all_audit_log`) is no longer served the ledger rows that carry no `tenant_id`; under a wall, the shipped sets' row scope already excluded them. Nothing is widened.
9+
Metadata-protocol: none
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export is added, removed or renamed: one runtime read rule on sys_audit_log changes which stored rows a reader who is not the platform administrator is served, and the view_all_audit_log description gains one sentence. There is nothing for a migration to rewrite and no tombstone. -->
12+
13+
**BREAKING** read narrowing on `sys_audit_log` (`@objectstack/plugin-audit`), on every engine read of the ledger: the data API's list, by-id, count and aggregate, and the Setup views and record history built on them.
14+
15+
- **Before.** The ledger has no organization column (ADR-0131 D7); the organization a row is about is the attribution field `tenant_id`, and a row about a deployment-level action carries none: a GLOBAL settings change, a user-import run, a platform-admin standing change, a user account the platform administrator creates or resets through the admin endpoints, a system write about an object with no organization column. Under a wall the shipped row policy `sys_audit_log_org` kept those rows from every organization reader. Under `single` no wall is enforced and that policy is stripped, so every reader whose sets grant the ledger read was served them, and a holder of `view_all_audit_log` was served them past the parent-record gate too.
16+
- **Now.** In every posture, a ledger row with no `tenant_id` is served only to the platform administrator: the carried `PLATFORM_ADMIN` posture rung, which the unscoped `admin_full_access` grant derives. Every other non-system reader is served only the rows about an organization. Which organization's rows stays the row policy's answer, unchanged. The same holds under a wall for a set that authors its own ledger row policy or grants the superuser read bypass on the ledger: its holder is not the platform administrator.
17+
- **Unchanged.** The rows stay stored. A system-context read and a context-less programmatic read still return every row. The platform administrator reads every row as before, on a request that carries the `PLATFORM_ADMIN` rung, which the request's authorization context resolves for the administrator. A context built outside that resolution carries no rung and is served the rows about an organization only (fail-closed, never wider): for example, an automation run with `runAs: 'user'` whose run context seeds no posture. The parent-record gate and the field-level redaction of the snapshots are unchanged. A walled deployment on the shipped sets serves every reader the same rows as before.
18+
- **The capability text.** The `view_all_audit_log` description (`PLATFORM_CAPABILITIES`, `@objectstack/spec`) gains one sentence saying the same for `single`.
19+
20+
**Upgrade note for `single` operators who granted the ledger to someone who is not the platform administrator.** An auditor role (a set granting `sys_audit_log` read, with or without `view_all_audit_log`), an organization administrator, or a `viewer_readonly` holder no longer sees deployment configuration history: global settings changes, user-import runs, platform-admin standing changes, and the user accounts the platform administrator creates or resets through the admin endpoints. The organization's own record history and tenant-scope settings changes are still served. A person who must read deployment configuration history needs the platform administrator's grant (`admin_full_access`); no narrower grant serves those rows. A job that exports the full ledger reads it in system context and is unaffected.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-formula gains an eval for a mail amount shown in each record's own currency (`currency:@path`)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The skill taught `{{ amount | currency:@currency_code }}` for a mail template that shows each record's amount in that record's own currency, but no eval checked that an AI author reaches for it. An author could write a bare path after the colon (refused at authoring), hard-code one currency code for every record, or leave the argument out (the organization's default currency for every record), and nothing scored that wrong.
11+
- **Now.** Case 8 of `evals/cel-predicates-formulas.json` asks for an `email_template` that mails one overdue notice per contract with the amount in that contract's own currency. Its reference answer names the marked form, that only a mail template may read the code from the send's data, that the stored code is three capitals exactly as stored, and that a declared `variables` list must name the path or a root above it; the assertions require `currency:@` and refuse the bare path, a hard-coded code and the no-argument form. Six reference answers of existing cases are shortened where they restated a point they already make, with every assertion and fact kept.
12+
- No schema, key or behaviour changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
'@objectstack/spec': patch
4+
'@objectstack/cli': patch
5+
---
6+
7+
feat(platform-objects): `sys_exchange_rate` — an organization's dated exchange rates, with a Setup list under Localization
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
A new system object holds the rates multi-currency amounts are converted with, one row per currency and effective date:
13+
14+
- **Columns.** `currency` (an ISO 4217 code, checked on write), `rate` (exact decimal, 12 decimal places and 24 digits in all, strictly positive), `effective_date` (date) and `source` (`manual` / `import` / `app`, default `manual`).
15+
- **Direction.** One row means 1 unit of the organization's home currency (Localization → Default currency) = `rate` units of the row's currency, so a home amount is the foreign amount divided by `rate`. The sentence is exported as `EXCHANGE_RATE_DIRECTION` and shown on the Setup list page; the scale and precision are exported as `EXCHANGE_RATE_SCALE` and `EXCHANGE_RATE_PRECISION`.
16+
- **One rate per currency per day.** `(currency, effective_date)` is unique within an organization; two organizations may each hold their own EUR rate for the same day.
17+
- **Who.** Per organization (its `organization_id` is injected and the tenant wall scopes it). Organization administrators read and write it; rank-and-file members do not see it. Every change is captured in the audit log and shown on the record's History tab.
18+
- **Where.** Setup → Configuration → Exchange Rates, directly under Localization. `PlatformObjectsPlugin` registers the object with the other platform-infrastructure objects. `PLATFORM_OBJECTS_BY_PACKAGE` (`@objectstack/spec`) lists it under `platform-objects`, so a reference to `sys_exchange_rate` resolves as a real platform object.
19+
- **Upgrade ceremony.** The organization-ownership inventory (`@objectstack/cli`) lists `sys_exchange_rate`, so `os migrate organization-ownership` plans it instead of refusing it as an uninventoried platform table.
20+
21+
Nothing reads the table yet: the write-time home-currency conversion that uses it ships in the same release.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/core': patch
3+
'@objectstack/service-messaging': patch
4+
---
5+
6+
fix(core, service-messaging): a failed settings read no longer dead-letters a templated mail as `TEMPLATE_INVALID`; only a read that finds no default currency is permanent (#27257)
7+
8+
Clause-②: yes (widening) — `resolveLocalizationContext`'s result (`@objectstack/core`) gains one optional member, `readFailed?: true`. Nothing is narrowed.
9+
Metadata-protocol: none
10+
11+
- **Before.** `resolveLocalizationContext` answered `currency: undefined` both when the organization has no default currency and when its `sys_setting` read failed (the failure is memoized for 30 s). So a messaging delivery whose template has a `{{ amount | currency }}` hole with no argument was refused `TEMPLATE_INVALID` during a transient settings fault, graded permanent and never sent, although a retry would have worked.
12+
- **Now.** The answer carries `readFailed: true` when the direct `sys_setting` read threw, on the fresh read and on a hit of the failure memo alike. A settings-service refusal does not count, and the memo is not lengthened. Messaging's tenant-currency read rejects on it; the email and inbox channels then render without a currency. When that render answers `TEMPLATE_INVALID`, they render the template once more without sending, with the no-currency code `XXX` as the default: if that passes, the missing default was the only refusal, and the delivery is recorded without a `TEMPLATE_*` code and retried; if it still refuses, that refusal stands and is graded permanent, as before. Once the read succeeds, an organization with no default currency gets `TEMPLATE_INVALID` with its remedy, graded permanent, as before.
13+
- **Unchanged.** A template with no no-argument `currency` hole is sent during a settings fault, as before. Every other template failure keeps its grade. The other callers of `resolveLocalizationContext` see the same values; the new member is optional and absent on every successful read.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/service-automation': minor
3+
---
4+
5+
fix(service-automation): a `connector_action` node evaluates a CEL envelope in `connectorConfig.input`, so a flow can send a per-record value to a connector's action
6+
7+
Clause-②: no
8+
Metadata-protocol: semantic
9+
10+
`connectorConfig.input` is now a value-slot map, resolved by the same per-key resolver as a `subflow` or `map` `input`, a `script`'s `inputs`, a `screen`'s `defaults` and a `notify` node's `templateData`:
11+
12+
- **An envelope is evaluated.** A top-level value written as `{ dialect: 'cel', source: '…' }` is evaluated in the run's scope, and the action's handler receives the value it computes, its type kept (a string, a number, a list, a record). Before, the handler received the envelope object itself, so a declarative connector that put the value into a request path sent `[object Object]` and the run reported success.
13+
- **A constant payload is unchanged.** Every other value, an envelope-shaped object nested inside a literal included, reaches the handler as written. The handler now receives a copy, so a handler that writes into its input no longer writes into the stored flow.
14+
- **A fault fails the step.** An envelope that faults on the live variables (an absent key, a type error) fails the node with the slot (`input.<key>`) and its source, and the handler is not called, so nothing is sent upstream.
15+
16+
To keep a value an author meant as a literal envelope-shaped object, nest it inside another literal; only a top-level value of the map is evaluated.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-ui and objectstack-data teach a primary related list as a body table in the Details tab, and the relatedLists refusal with its package boundary
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** Both skills said a `relatedList: 'primary'` child gets its own tab on the record page, `objectstack-ui` described one tab per primary list plus a shared Related tab and an app-level `relatedLayout` override, and `objectstack-data` said a `relatedLists` name matching no inbound FK is refused, with no boundary.
11+
- **Now.** A primary list is a full-width body table in the details column after the first field section (inside the Details tab, no tab of its own); every other list is a side card in the shared Related tab; field changes join the Timeline tab under its Changes filter. A `relatedLists` child in the stack with no inbound FK is refused everywhere; one that exists nowhere is refused by `os validate` / `os build` and by a metadata write naming no package, while a write into a package stores it and the page places nothing. No ceiling moves; no schema, key or behaviour changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/components': patch
3+
'@object-ui/plugin-list': patch
4+
'@object-ui/plugin-detail': patch
5+
'@object-ui/i18n': patch
6+
---
7+
8+
fix(ui): four KPI tiles sit two across on a phone, a calendar's footer says its count is the loaded dates', and the record not-found page says the record may also be one you cannot read
9+
10+
Clause-②: no
11+
Metadata-protocol: none
12+
13+
Three console rows from a full HotCRM browser pass:
14+
15+
- **A layout `grid` with a bare `columns` of 4 or more draws two tiles across on a phone** (below `sm`), then the authored count from `md`. A home page's KPI strip (`columns: 4` over `object-metric` tiles) took a row per tile and filled the first screen; it now reads as the dashboard's own phone layout for metric tiles. A bare 2 or 3 still stacks on a phone and pairs from `sm`, and an authored breakpoint object (`columns: { xs: 1, md: 4 }`) is drawn as written.
16+
- **A calendar view's footer says `N records loaded for these dates`.** The calendar fetches the window of dates it draws, so the footer's count is that window's records, not the view's total; it read "N records", the words a grid uses for the whole view. A grid, a board and a calendar over rows handed in whole keep "N records". New count family `list.calendarRecordCount` in all ten packs.
17+
- **The record not-found page says the record "does not exist, may have been deleted, or you may not have access to it".** The server answers a record the reader may not see the same as a missing one (`404 RECORD_NOT_FOUND`, so existence is never revealed), so the page cannot tell the two apart and now names both. `empty.recordNotFoundDescription` and `detail.recordNotFoundDescription` change in all ten packs.
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(flow designer): a flow wider than the canvas opens zoomed out to fit its width
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The flow canvas opened every flow at 100%, centred. On a narrow canvas, such as Studio's Automations pillar at phone width, a branching flow's outer branches were cut off at both edges until the author pressed fit-to-view. Now a diagram wider than the canvas opens zoomed out until its width fits (never below the canvas's 40% floor), still centred. A diagram that already fits opens at 100% as before. The canvas never zooms in when it opens, and only width counts, so a tall linear flow still opens at 1:1. Zoom, fit-to-view and the re-fit on a canvas width change are unchanged.
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+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(console): an admin can open a read-only package's app in Studio to look at it
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The app bar's Edit app pencil and the app switcher's 「Edit APP in Studio」 row were drawn only for an app whose package Studio lists as writable (objectui#12615), so a code or installed package's app (HotCRM, for one) offered no way into Studio at all. The same metadata authors now get a browse entry for such an app: an eye in the app bar, 「Open app in Studio」, and 「Open APP in Studio」 in the switcher, landing where the pencil would, in Studio's read-only posture. A writable package's app keeps the pencil and 「Edit APP in Studio」. The console's own apps, a package the list does not carry and a list not yet read still draw neither.
12+
13+
`useStudioEntryGate` answers the gate with a posture (`edit` or `view`) from the same server `writable` verdict; `useStudioDesignGate` is its `edit` half, so the sidebar's Design entry and a page's pencil are unchanged. Two keys are added in all ten locales: `topbar.openAppInStudio` and `layout.appSwitcher.openAppInStudio`.

0 commit comments

Comments
 (0)