Skip to content

Commit 453d4e1

Browse files
committed
Merge origin/main (#29505) into claude/project-thread-fubg69
Conflicts in scripts/pm/fleet-write/execute.mjs, resolved as follows: - the ops import list takes both sides: advisory_read's names and #29505's ADVISORY_STATE_SHAPE; - the battery map keeps #29505's advisory rows count and advisory_read's row; - advisoryPlatformText is imported again. #29505 moved the other advisory rows onto advisoryRefusalCause and dropped the import, but advisory_read's refused arm still quotes only the messages ADVISORY_PLATFORM_MESSAGES names (review S2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8VVAng9QxNxoEzyWkqbW6
2 parents 7b0268e + 8e505db commit 453d4e1

96 files changed

Lines changed: 3376 additions & 207 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.

‎.changeset/27842-disabled-actions-served.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ Metadata-protocol: none
1212

1313
- **Served beside the object** (`@objectstack/metadata-protocol`, `@objectstack/rest`). `GET /api/v1/meta/object/:name` serves `disabledActions` beside `item`: the names in `item.actions` that the deployment's activation ledger switches off. `GET /api/v1/meta/object` serves the same answer keyed by object name. The default cached door carries it too, read live on every call and kept out of the ETag, so a read that carries it is answered in full rather than `304 Not Modified`. The key is absent where no ledger answered (no engine, or an engine with no ledger attached), on a draft read, and on every type other than `object`. `item.actions` is unchanged. The protocol reads the engine's `getDisabledActions` through the kernel's service table by name and imports no engine.
1414
- **The fact it serves** (`@objectstack/objectql`). `ObjectQL.getDisabledActions()` returns the names the engine's activation projection switches off, the same projection both dispatch doors consult before they refuse a call, or `undefined` when no ledger is attached. `listDisabledActions()` is unchanged.
15-
- **The refusal sentence** (`@objectstack/objectql`, `@objectstack/runtime`). A switched-off action is still refused 409 `ACTION_DISABLED` at the REST and MCP doors, and the message now reads: "This action is turned off for this deployment. An administrator can turn it on again under Setup › Packaged automation, or you can use another action." It no longer names the ledger table or the ADR. The engine writes those, with the code and the action's name, to an `info` log line each time it refuses one.
15+
- **The refusal sentence** (`@objectstack/objectql`, `@objectstack/runtime`). A switched-off action is still refused 409 `ACTION_DISABLED` at the REST and MCP doors, and the message now reads: "This action is turned off for this deployment. An administrator can turn it on again under Setup › Packaged Actions, or you can use another action." It no longer names the ledger table or the ADR. The engine writes those, with the code and the action's name, to an `info` log line each time it refuses one.

‎.changeset/27966-flow-disabled-plain-sentence.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ fix: a flow an administrator switched off is refused with a plain sentence, the
99
Clause-②: no
1010
Metadata-protocol: none
1111

12-
- **The flow refusal** (`@objectstack/service-automation`). A packaged flow switched off under Setup › Packaged automation is still refused `FLOW_DISABLED`, at every door that runs it: its trigger, a direct run, and a parent flow that calls it as a subflow. The message now reads: "This flow is turned off for this deployment. An administrator can turn it on again under Setup › Packaged automation, or copy it there under a new name." It no longer names the ledger table or the ADR. The engine writes those, with the code and the flow's name, to an `info` log line each time it refuses one. A flow disabled by its own status (`obsolete` / `invalid`) still reads "Flow '…' is disabled", so the two reasons stay distinguishable.
13-
- **One sentence for both** (`@objectstack/types`, `@objectstack/objectql`). `describeTurnedOffPackagedItem('action' | 'flow')` builds the sentence. The action refusal (`ACTION_DISABLED`) reads from it too, and its wording is unchanged. Only the last clause differs: a flow can be copied under a new name, and an action cannot, so the action sentence still offers another action instead.
12+
- **The flow refusal** (`@objectstack/service-automation`). A packaged flow an administrator switched off is still refused `FLOW_DISABLED`, at every door that runs it: its trigger, a direct run, and a parent flow that calls it as a subflow. The message now reads: "This flow is turned off for this deployment. An administrator can turn it on again in its package's Automations in Studio, or copy it there under a new name." It no longer names the ledger table or the ADR. The engine writes those, with the code and the flow's name, to an `info` log line each time it refuses one. A flow disabled by its own status (`obsolete` / `invalid`) still reads "Flow '…' is disabled", so the two reasons stay distinguishable.
13+
- **One sentence for both** (`@objectstack/types`, `@objectstack/objectql`). `describeTurnedOffPackagedItem('action' | 'flow')` builds the sentence. The action refusal (`ACTION_DISABLED`) reads from it too, and its wording is unchanged. The two sentences differ in two clauses: where an administrator turns the item back on (an action under Setup › Packaged Actions, a flow in its package's Automations in Studio), and what can be done meanwhile (a flow can be copied under a new name, and an action cannot, so the action sentence offers another action instead).
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): Setup → Integrations → Connector Connections asks for the installer's OAuth client when an instance declares `clientSource: 'installer'`
7+
8+
Clause-②: yes
9+
Metadata-protocol: none
10+
11+
An `oauth2` `authorization_code` connector instance whose declaration says `clientSource: 'installer'` takes the OAuth client the administrator registered with the vendor. Connect and Reconnect on such an instance now open a form for its client ID and client secret before the sign-in window opens; Continue begins the connect with `{ clientId, clientSecret }` (the secret omitted for a public client). The secret input is masked, starts empty on every opening and is cleared the moment the connect is sent; the stored secret is never read back. A connected instance shows which client it keeps (`OAuth client ID, secret stored`), and Reconnect prefills that client ID and sends the client again, because the server takes a client on every connect of such an instance. Every other instance connects with no form, as before.
12+
13+
Twelve new keys under `connectorConnections` in all ten `@object-ui/i18n` locales: `clientKeptWithSecret`, `clientKept`, `clientTitle`, `clientIntro`, `clientStored`, `clientStoredSecret`, `clientStoredNoSecret`, `clientResend`, `clientId`, `clientSecret`, `clientSecretHint`, `clientContinue`.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-core': patch
3+
---
4+
5+
docs(metadata-core): the `ITEM_KEY_DISCRIMINATORS` docblock no longer says email template resolution is exact with no language-subtag folding
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The docblock ships in the package's type declarations, and it still described the exact `(name, locale)` ladder: the named tag matched exactly, then `en-US`. Over the built-in registry template loader a named locale is now folded against the bundle's tags, then the stack's declared fallback locale is tried, then `en-US`. The docblock now cites `SendTemplateInput.locale` in `@objectstack/spec` as the ladder of record instead of restating its rungs, and keeps the point the table rests on: every rung loads one row at one tag exactly, so `en` and `en-US` stay two distinct rows of one bundle. No code changed.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@object-ui/types': minor
4+
---
5+
6+
fix(spec)!: a dashboard global filter keyed `dateRange` beside a declared `dateRange` is refused at every door that parses it
7+
8+
Clause-②: no (narrowing)
9+
Metadata-protocol: narrowed
10+
Skills-docs: none — `skills/objectstack-ui/rules/dashboards.md` teaches `dateRange` as the reserved name of the built-in date range and no file under `skills/**` keys a `globalFilters` entry `dateRange`; every taught use stays true. `content/docs/ui/dashboards.mdx` gains one sentence naming the refusal.
11+
12+
**BREAKING** for a dashboard that declares `dateRange` and also keys a `globalFilters` entry `dateRange`.
13+
14+
- **Before.** The built-in date range is a filter the dashboard declares under the reserved key `dateRange`. A `globalFilters` entry keyed `dateRange` (its `name`, or its `field` when it has no `name`) beside it parsed, and the dashboard save door (`PUT /api/v1/meta/dashboard/X`) answered 200 in draft and in publish. The console builds the date range first and lets the later definition win, so the board drew the entry's control in place of the declared date range and every widget binding `dateRange` was filtered by the entry's field. The only trace was a browser console warning.
15+
- **Now.** `DashboardSchema` refuses the entry, so every door that parses a dashboard refuses it: `defineStack`, `os validate`, `os build` and the dashboard save door (422 `INVALID_METADATA`). One issue per such entry, at its `name` (or its `field`, when it has no `name`): ``globalFilters[i] is keyed 'dateRange', the reserved name of the built-in date range this dashboard declares in `dateRange`. …``. The rule reads the sibling `dateRange`, so it sits on the dashboard object and is exported as `checkDashboardReservedDateRangeGlobalFilters`: a mirror that rebuilds the dashboard from `DashboardSchema.shape` drops it unless it chains the export, as `@object-ui/types`' `DashboardComponentSchema` now does, so the console's authoring door refuses the same dashboard. On `DashboardSchema` itself, zod 4 now throws on `.omit()` / `.pick()` / `.partial()` (an object carrying a refinement); no caller in this repository derives it that way.
16+
- **Not changed.** On a dashboard that does not declare `dateRange`, an entry keyed `dateRange` is an ordinary filter and stays legal. A named entry whose `field` is `dateRange` is keyed by its name and stays legal.
17+
- **The remedy.** Give the entry another `name` (and rename every widget `filterBindings.dateRange` key that means it), or delete it and keep the built-in `dateRange`.
18+
- **Stored rows.** A stored dashboard carrying the pair still loads, with `_diagnostics` reading `valid: false` and carrying the refusal, and its next save (draft or publish) is refused until it is fixed. Nothing rewrites it: whether the entry was meant as the date range or as a filter of its own only the author knows. The upgrade guide entry is `ui-dashboard-global-filter-reserved-date-range-refused`.
19+
20+
<!-- adr-0087: registered ui-dashboard-global-filter-reserved-date-range-refused -->
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/types': patch
3+
---
4+
5+
fix(types)!: `DashboardComponentSchema` refuses a dashboard that declares two global filters under one key, as the spec does
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (already-registered ui-dashboard-global-filter-duplicate-name-refused) The refused shape is the one that entry already registers for `@objectstack/spec`'s `DashboardSchema`: a `globalFilters` array that declares one filter key twice. This change makes the published `@object-ui/types` mirror refuse the same shape, with the same issue, by taking the spec's slot by reference. No spec key, accepted value or stored shape moves, so the upgrade guide entry, its remedy and its stored-row behaviour are the ones already published; `objectstack migrate meta` has nothing new to rewrite. -->
11+
12+
**BREAKING** for a dashboard node whose `globalFilters` repeats a filter key.
13+
14+
- **Before.** `DashboardComponentSchema` (`@object-ui/types/zod`) rebuilt `globalFilters` as an array of `GlobalFilterSchema`. That kept every check on each filter and dropped the spec array's own refusal, so two filters under one key passed this validator while every spec door (`defineStack`, `os validate`, `os build`, the dashboard save door) refused the dashboard. A filter's key is its `name`, or its `field` when it has no `name`.
15+
- **Now.** The mirror takes `@objectstack/spec`'s `globalFilters` slot by reference, so it refuses the pair with the spec's own issue, at the second declaration's `name` (or its `field`): `This dashboard declares the global filter 'X' twice in globalFilters: globalFilters[i] and globalFilters[j]. …`. Each element is still the exported `GlobalFilterSchema`, so nothing else about a filter is judged differently. The slot's description is now the spec's.
16+
- **Not changed.** Distinct keys, and two filters with distinct names over the same `field`. The TypeScript type of `globalFilters` and the `DASHBOARD_SPEC_EXCLUDED` list are unchanged.
17+
- **Fix.** Rename all but one of the colliding entries (and every widget `filterBindings` key that means a renamed one), or delete the copy. The upgrade guide entry is `ui-dashboard-global-filter-duplicate-name-refused`.
18+
19+
Also: the `ObjectFormSchema` key-taxonomy docblock no longer lists `drawerSide`/`drawerWidth` as spec-aligned form-view keys. The spec retired both on the form view; they are the React `ObjectForm` block's own props. Comment only.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-datasource': patch
4+
---
5+
6+
fix(spec)!: a datasource named `drivers` is refused at every door that parses a datasource, not only at the admin create door
7+
8+
Clause-②: no (narrowing)
9+
Metadata-protocol: narrowed
10+
Skills-docs: none in `skills/**` — `skills/objectstack-data/rules/datasources.md` teaches `default` as host-reserved and no file under `skills/**` declares or shows a datasource named `drivers`; every taught use stays true. Docs: `content/docs/data-modeling/drivers.mdx` said `drivers` was reserved at the admin API only; it now names the schema refusal and every door.
11+
12+
**BREAKING** for a datasource named `drivers`.
13+
14+
- **Before.** The datasource admin API mounts the driver catalog at `GET /api/v1/datasources/drivers`, ahead of the by-name read `GET /api/v1/datasources/:name`, so a datasource named `drivers` can never be read by name (its Setup edit form never loads). The admin create door (`POST /api/v1/datasources`) refused the name, but `DatasourceSchema.name` accepted it, so the same unreachable datasource was still declared in code (`defineDatasource`, a stack's `datasources`) and saved through the metadata save door (`PUT /api/v1/meta/datasource/drivers` answered 200).
15+
- **Now.** `DatasourceSchema.name` refuses every name in the new export `ROUTE_SHADOWED_DATASOURCE_NAMES` (`@objectstack/spec/data`, today `['drivers']`) with one `invalid_format` issue at `name`: `Datasource name 'drivers' is reserved: the datasource admin API serves its own route at /api/v1/datasources/drivers, ahead of the by-name read /api/v1/datasources/:name, so a datasource of that name could never be read by name. Pick another name.` So `defineStack`, `os validate`, `os build` and the metadata save door (422 `INVALID_METADATA`, nothing stored) all refuse it. The published JSON Schema carries the refusal as a second `pattern` on `name`.
16+
- **One list.** `@objectstack/service-datasource` now reads the spec's list: the admin registrar mounts the driver catalog from a segment typed as a member of it, and the create door refuses each name in it, as before.
17+
- **Not changed.** `default` (the host's primary datasource row still parses; a stack still may not declare it), `test`, and names that only contain the word (`driver`, `drivers_archive`, `my_drivers`).
18+
- **Stored rows.** A stored datasource row named `drivers` still loads, with `_diagnostics` reading `valid: false` and carrying the refusal, and its next metadata save is refused. Through the admin door it stays listed and removable, and a `label`/`active`-only edit still saves. Nothing rewrites it: the new name, and every object that routes to it, are the author's call. The upgrade guide entry is `datasource-name-route-shadowed-refused`.
19+
- **The remedy.** Rename the datasource (for example to `driver_registry`) and point every object `datasource` / `datasourceMapping` rule that names it at the new name.
20+
21+
<!-- adr-0087: registered datasource-name-route-shadowed-refused -->
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): declare `features.sso` on the `GET /api/v1/auth/config` response
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
`AuthFeaturesConfigSchema` gains an optional boolean `sso`. The server has served it for a long time, and the console login form reads it to decide whether to show "Sign in with SSO", but the contract did not declare it. A client that parsed the response through `GetAuthConfigResponseSchema` lost the flag, because a plain object schema strips keys it does not know.
11+
12+
- **What the value means.** `true` when enterprise SSO is usable: the SSO plugin is wired and at least one SSO provider is configured. `false` when SSO is off, or on with no provider yet. The `/auth/config` route answers this "usable" value, not the coarser "wired" flag, and falls back to "wired" when providers cannot be counted.
13+
- **How it differs from `ssoEnforced`.** `ssoEnforced` takes the password form away. `sso` only says whether the SSO entry exists.
14+
- **Absent** means not usable. The schema sets no default.
15+
16+
What to do: nothing. Every payload accepted before is accepted unchanged. A client that typed the response from `@objectstack/spec` (for example through `@objectstack/client`'s re-exported `AuthFeaturesConfig`) now sees `sso?: boolean`.
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)!: the delegated fallback face refuses a date bucket over a formula or a field that is not a date, as the engine face does (#29364)
6+
7+
Clause-②: no (narrowing) — a cube query (or `/analytics/sql` dry run) that `FallbackDelegateStrategy` hands to the host's fallback analytics service is refused `INVALID_FIELD` / 400 at `groupBy.N.dateGranularity` when it buckets a `formula` field, or a field declared anything but a date or a datetime, with the engine face's refusal field for field, before the fallback service is called. It answered 200 with one `null` group before. Only a host that composes the fallback face sees the change; no shipped composition reaches it. Nothing is widened: no export, public member, answer member, error code or status is added.
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: the analytics service's existing date-bucket door, already in front of the engine, the draft preview and the dry run, now also stands in front of the delegated fallback service. A refused query is fixed in the query (or by storing the value on a date or datetime field), so there is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing on the analytics fallback face (`@objectstack/service-analytics`), for hosts that compose it.
13+
14+
- **Who sees it.** Only a host that composes the fallback face: `new AnalyticsService({ fallbackService })`, or `AnalyticsServicePlugin` capturing an analytics service registered before it (for example `MemoryAnalyticsService` from `@objectstack/driver-memory`) under a `queryCapabilities` override with `objectqlAggregate: false`. The default composition sends every bucketed query to the engine, which already refuses these, and is unchanged.
15+
- **Before.** The fallback service folded the bucket itself and read no field type. `MemoryAnalyticsService` put every row into one `null` group for a `formula` bucket, and a `null` group beside whatever text parsed as a date for a `text` bucket, under an ordinary 200. The dry run handed back the fallback service's statement.
16+
- **Now.** Both the query and the dry run answer the engine's refusal (`INVALID_FIELD` / 400, `param: 'groupBy'`, `path: 'groupBy.N.dateGranularity'`, the same message), and the fallback service is not called. The field's type is read from the host's `sourceFieldMeta`, which `AnalyticsServicePlugin` answers from the data engine.
17+
- **Unchanged.** A date or datetime bucket is delegated as before. A host that wired no field types (`sourceFieldMeta`) is not judged, as on the draft preview and the dry run.
18+
19+
**The remedy.** Bucket a date or datetime field. For a formula, store the value on a date or datetime field written when its source changes, and bucket that.

0 commit comments

Comments
 (0)