Skip to content

Commit adc94da

Browse files
committed
Merge origin/main into claude/project-thread-q572zu
Conflicts: sys_organization.require_mfa help (source, en and zh-CN bundles) against #30529. Kept this branch's administrator wording, which already carries #30529's two-factor sentence; en confirmed by extract (byte-identical). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6CbDNK83SuSb8zsRt2s
2 parents 7e4a327 + 5d0904c commit adc94da

362 files changed

Lines changed: 17957 additions & 1497 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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/plugin-dashboard': patch
3+
---
4+
5+
refactor(plugin-dashboard)!: retire five zero-consumer exports (the config panel's defaults table and hook, the retired-node tombstone and its report and reset helpers)
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/plugin-dashboard#CONFIG_PANEL_DEFAULT_TRANSLATIONS, @object-ui/plugin-dashboard#useConfigPanelTranslation, @object-ui/plugin-dashboard#RetiredDashboardNodeTombstone, @object-ui/plugin-dashboard#reportRetiredDashboardNodeType, @object-ui/plugin-dashboard#resetRetiredDashboardNodeTypeReports) the retired operands are a provider-less English defaults table, the translation hook that reads it, the React widget that refuses a retired dashboard node type and its two console-report helpers; nothing in the workspace or in hotcrm imports any of them from the package entry, and no packages/spec schema, registered node type, conversion surface or stored shape names them, so there is nothing for `objectstack migrate meta` to rewrite -->
11+
12+
**BREAKING** for a host that imports any of these five names from `@object-ui/plugin-dashboard`. None had an importer anywhere in the workspace or in hotcrm, so the Console and every `@object-ui/*` package are unaffected. Every module stays in the package: the config panels keep using the hook and its table, and the package still registers the tombstone for every retired dashboard node type, so an authored `view:dashboard` is refused by name exactly as before. Only the entry stops publishing them. `RETIRED_DASHBOARD_NODE_TYPES`, `ConfigPanelTranslate`, `DashboardMetricNodeSchema`, the three `WidgetDataset*` types and every component stay exported unchanged.
13+
14+
What leaves, and what to write instead (none has a replacement export):
15+
16+
- FROM `CONFIG_PANEL_DEFAULT_TRANSLATIONS`, `useConfigPanelTranslation` TO no replacement: `DashboardConfigPanel` and `WidgetConfigPanel` translate their own labels; mount an `I18nProvider` to change them.
17+
- FROM `RetiredDashboardNodeTombstone` TO no replacement: the package registers it under every key of `RETIRED_DASHBOARD_NODE_TYPES` itself, so rendering a retired node through `SchemaRenderer` still shows the refusal.
18+
- FROM `reportRetiredDashboardNodeType`, `resetRetiredDashboardNodeTypeReports` TO no replacement: the tombstone reports on its own, once per retired type; read the migration text from `RETIRED_DASHBOARD_NODE_TYPES`.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/plugin-list': patch
3+
---
4+
5+
refactor(plugin-list)!: retire three zero-consumer exports (the view switcher dropdown, the tab bar select and the conditional formatting wrapper)
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/plugin-list#ViewSwitcherDropdown, @object-ui/plugin-list#TabBarSelect, @object-ui/plugin-list#evaluateConditionalFormatting) the retired operands are two React components and a function that wraps `@object-ui/core`'s conditional formatting resolver; nothing in the workspace or in hotcrm imports any of them from the package entry, and no packages/spec schema, registered node type, conversion surface or stored shape names them, so there is nothing for `objectstack migrate meta` to rewrite -->
11+
12+
**BREAKING** for a host that imports any of these three names from `@object-ui/plugin-list`. None had an importer anywhere in the workspace or in hotcrm, so the Console and every `@object-ui/*` package are unaffected. Every module stays in the package (`ListView` keeps rendering `ViewSwitcherDropdown`); only the entry stops publishing them. `ListView`, `ViewSwitcher`, `TabBar`, `UserFilters`, `normalizeFilterCondition`, `normalizeFilters`, `LIST_DEFAULT_TRANSLATIONS` and every exported type, `TabBarProps` included, stay exported unchanged.
13+
14+
What leaves, and what to write instead:
15+
16+
- FROM `ViewSwitcherDropdown` TO `ViewSwitcher`, or render `ListView`, which draws the dropdown itself.
17+
- FROM `TabBarSelect` TO `TabBar`, which takes the same `TabBarProps`.
18+
- FROM `evaluateConditionalFormatting` TO `resolveConditionalFormatting` from `@object-ui/core`, the function it wrapped: same arguments, result typed as `React.CSSProperties`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/console': patch
3+
'@object-ui/mobile': patch
4+
'@object-ui/auth': patch
5+
---
6+
7+
feat(console,mobile,auth): the Console registers the mobile shell's push token under its own session and unregisters it on sign-out and when the shell removes the connection
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
- **`@object-ui/console`:** in the ObjectStack mobile shell, a signed-in person who has reached Home or an app is asked for push permission once per page load, after a short delay (never on a sign-in screen): the Console calls `getPushToken()` and registers the token with `POST /api/v1/notifications/devices`. A `null` answer (simulator, permission denied, no token) sends nothing. A token the shell rotates is registered again; an unchanged token for the same person is not sent twice. Another person signing in on the page registers the device for them, and the registry moves the token away from the previous person. An impersonation never registers, so the impersonated person's pushes never reach the administrator's device. The registration sends `deviceId` as the shell's `installId` when it has one, and `origin` only when the page was loaded over `http:` or `https:`. The device is unregistered (`DELETE /api/v1/notifications/devices/:deviceId`) before sign-out ends the session, and when the shell removes this connection; a shell that is neither iOS nor Android registers nothing. A browser is unchanged.
13+
- **`@object-ui/mobile`:** `ObjectStackNative` gains the optional `installId` (`string | null`), and the package exports `NATIVE_PUSH_TOKEN_CHANGED_EVENT` (`objectstack-native:pushTokenChanged`, announced as `has('pushTokenChanged')`) with its event type `NativePushTokenChangedEvent`. Both are feature-detected: a shell that predates them carries neither.
14+
- **`@object-ui/auth`:** `registerBeforeSignOut(step)` registers a step that `signOut()` awaits while the session still exists, and returns the function that removes it. The steps run together and can never hold sign-out back: a rejection is reported and dropped, and sign-out goes ahead after `BEFORE_SIGN_OUT_TIMEOUT_MS` (3 s) whether or not they have settled.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
feat(cli): `os migrate blank-multi-values` rewrites the `[]` and `''` still stored on optional multi-value columns to `null`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Since #29343 the write door stores a cleared optional multi-value value (`[]` or `''` on a `multiselect`, `checkboxes` or `tags` column, or on a `multiple: true` `select`, `radio`, `lookup`, `user`, `file` or `image` column) as `null` (ruling A on #29343). Rows cleared before that still hold the JSON text `'[]'` or `'""'`, so `= null`, grouping and the read shape split them from rows cleared since. This new command is the one-time clean that ruling's execution step 3 binds, an `os migrate` data step of its own beside `blank-single-values`, not part of `os migrate value-shapes`, which rewrites nothing.
11+
12+
- **Columns.** Read from `blankAsNullColumns` in `@objectstack/core`, the rule the write door and the seed replay read: its optional columns on the `$empty` table's `multi_value` row. A `required` column, a `readonly` column (a platform-stamped `[]`, such as `sys_activity.changed_fields`) and a single-value column are never touched, nor is a non-empty list.
13+
- **Dry run by default.** It counts the stored `[]` and `''` per object and column and writes no row. `--apply` rewrites each affected row's blank columns to `null` through the engine. `--object` narrows the walk and refuses a name the deployment does not declare. `--apply` refuses a SQLite file another process holds unless `--force`.
14+
- **No flag.** Nothing is gated on it. A re-run that reports zero is the verification.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
fix(rest)!: the approvals request list refuses a status value no approval request can have, and reads a comma list of statuses as a list
6+
7+
Clause-②: yes (narrowing) — `GET /api/v1/approvals/requests` (and its environment-scoped twin) refuses a `status` value outside the set `sys_approval_request.status` declares (`pending`, `approved`, `rejected`, `recalled`, `returned`, `cancelled`) with `400 VALIDATION_ERROR`, where it answered `200 {"data":[]}`. An empty value or an empty comma entry is refused too. The widening half: a comma list of declared statuses now filters on all of them, where it matched no row.
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: the route keeps its parameter names and the approval service contract keeps its shape. What moves is the answer to a query-string value, and nothing at rest records that answer, so there is nothing for a migration to rewrite. -->
11+
12+
**BREAKING** accept-set narrowing at the approvals request list (`@objectstack/rest`): a `status` value that is not a declared approval status is refused instead of answering an empty list.
13+
14+
- **Before.** `?status=bogus`, `?status=Pending` or `?status=waiting` answered `200 {"data":[]}`, which reads the same as "no request is in that status", so an integration polling the inbox with a misspelt status skipped every pending approval in silence. `?status=pending,returned`, the spelling `@objectstack/client`'s `approvals.listRequests({ status: [...] })` sends for an array, was read as one status and answered the same empty list. `?status=` filtered on nothing and answered every status.
15+
- **Now.** Each comma-separated entry must be a declared status, spelt exactly. Otherwise the request is refused with `400` and the nested `{ "error": { "code": "VALIDATION_ERROR", "message" } }` body the route already answers for an unknown or repeated parameter name; the message names the bad value and the allowed values, and the approval service is not asked. One declared status filters as before. Several filter on any of them (the service's `$in` push-down), and `total` counts on the same filter. A declared status with no rows still answers `200 {"data":[]}`.
16+
- **Who moves.** The console and the client SDK send declared statuses only. A script that sends another spelling, or an empty `status=`, gets the 400.
17+
18+
**The remedy.** Send one of `pending`, `approved`, `rejected`, `recalled`, `returned`, `cancelled` (lower case), or several separated by commas. To list every status, leave `status` out.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
fix(lint)!: under a package closure, a dashboard widget `filter` or report `runtimeFilter` on another package's dataset is judged against that dataset's object, so a key the object lacks is refused at publish (#30005)
6+
7+
Clause-②: no (narrowing) — the runtime publish door, for a write that names its package, refuses a dashboard widget `filter` key or a report `runtimeFilter` key on a dataset outside the package's closure when the dataset's object has no such field, a body it accepted before. Nothing is widened: `narrowObjectsToPackageClosure` keeps its signature and its four-limb answer, and no export is added to any package entry.
8+
Metadata-protocol: narrowed
9+
Skills-docs: updated here — `content/docs/deployment/validating-metadata.mdx` (the fourth-door paragraph) now names the fifth closure limb for dashboard and report writes; `skills/**` teaches neither the rules nor the closure.
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed or renamed: DashboardWidgetSchema.filter and ReportSchema.runtimeFilter keep their shape and meaning, and a dataset outside the closure now gets the verdict a dataset inside it already got. A refused key never served the board or the report (every reader saw an empty tile or a Dataset query failed panel), so there is no rewrite that keeps what the author meant; the author names a real field, and the refusal says which. -->
12+
13+
**BREAKING** accept-set narrowing at the runtime publish door for `dashboard` and `report` writes (the per-item publish and the package draft publish; Studio, REST and MCP authors), for a write whose package is known. `os validate`, `os build` and `os lint` are unchanged: they never narrow to a closure.
14+
15+
- **Before.** The publish gate narrows the live `objects` to the written package's closure (the package, its declared dependencies, platform objects, rows with no package) but carries every dataset. A runtime package's dashboard on `showcase_task_metrics`, a dataset the showcase package ships over `showcase_task`, therefore found the dataset but not its object, and `widget-filter-field-unknown` and `report-filter-field-unknown` skipped the filter as "object not in this snapshot". `filter: { no_such_field: 'x' }` published `200`, and every reader saw an empty tile or an error panel.
16+
- **Now.** A dashboard or report write also carries the base object of each dataset it binds (`widgets[].dataset`; a report's `dataset` and each joined block's `dataset`), which is the object the dataset query resolves the filter against at run time. The same key is refused `422 INVALID_METADATA` at `dashboards[0].widgets[0].filter.no_such_field` (or `reports[0].runtimeFilter.no_such_field`), the verdict the same key already got on a dataset inside the closure or with no closure in force.
17+
- **Which rules now judge such a dataset.** Every rule that resolves a dashboard's or report's filter against the dataset's object, all six already judging datasets inside the closure: `widget-filter-field-unknown`, `widget-filter-field-not-included`, `dashboard-filter-field-unknown`, `dashboard-filter-field-not-included`, `dashboard-filter-field-unprovisioned` and `report-filter-field-unknown`.
18+
- **A dimension name as a widget filter key.** On a dataset outside the closure, a widget `filter` key that names a dimension of the dataset but no field of its object is now refused with `widget-filter-field-unknown`, as it already was on a dataset inside the closure. The dataset query binds such a key; the correction of that verdict, for both cases, is tracked in #30006.
19+
- **Accepted, as before.** Every key the object has, a registry-injected system column, a dotted relationship path, every `$` operator. A dotted key whose hop lands on an object the snapshot still does not carry is not judged.
20+
- **Not changed.** Only dashboard and report writes carry the extra objects. A flow trigger, a list view or a dataset base object outside the closure is still refused as before (`flow-trigger-unknown-object`, `list-view-object-outside-closure`, `object-reference-unknown`), and a list view's `chart.dataset` brings no object in.
21+
22+
**What an author does.** Nothing for a board or report whose filter keys name real fields. A refused key never filtered anything, so there is no rewrite that keeps what the author meant: name a field of the dataset's object. The refusal names the key and the nearest field.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
refactor(metadata-protocol): the eight `ObjectStackProtocolImplementation` doors that kept an inferred return type spell it on the signature
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- `getDiscovery`, `findData`, `getData`, `cloneData`, `saveMetaItem`, `publishMetaItem`, `rollbackMetaItem` and `deleteMetaItem` now declare their return type, copied from the published declaration file it was inferred into. The built `index.d.ts` and `index.d.cts` are byte-equal before and after, and no behaviour changes; only the source maps move with the new lines. Nothing to do on upgrade.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
refactor(objectql): the six `ObjectQL` methods the #4918 query-options erasure pin kept in `engine.ts` are typed and move into their facet modules; no behaviour or public declaration changes
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- The six internal reads in `seedAutonumber`, `referenceExists`, `resolveMasterDetailParent`, `resolveMasterDetailParents`, `inspectDanglingReferences` and `probeReferencingRows` no longer erase their query options to `any`. Four are plain literals `tsc` now checks against `EngineQueryOptions`; the keyset walk's `where` and the dangling-reference audit's port options are named as `EngineQueryOptions` slots instead. `engine.ts` leaves the erasure baseline with zero sites.
11+
- A new fourth pass of the `engine.ts` codemod, `--members`, moves the six bodies the way the first pass moves a body, into the facet modules it already wrote, and proves the move by undoing it. `engine.ts` goes from 11,771 to 11,316 lines.
12+
- Nothing to do on upgrade: the exported names, the public `.d.ts` declarations and what every method does are the same.

0 commit comments

Comments
 (0)