Skip to content

Commit c5fe7df

Browse files
committed
Merge origin/main into claude/project-thread-s2vcdk (#30892)
Brings in #30861 (delete_record confirm) and the rest of main before the draft PR opens. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyqd3A7fbCWrqM3KCz
2 parents 04ff76b + 8ae0ab9 commit c5fe7df

421 files changed

Lines changed: 21715 additions & 2212 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/types': patch
3+
---
4+
5+
refactor(types)!: delete the fifteen driver and datasource interfaces nothing reads
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/types#SqlDriverInterface, @object-ui/types#ConnectionConfig, @object-ui/types#DriverQueryResult, @object-ui/types#BatchOperation, @object-ui/types#BatchResult, @object-ui/types#TransactionContext, @object-ui/types#CacheManager, @object-ui/types#ConnectionPool, @object-ui/types#DatasourceRegistration, @object-ui/types#DatasourceType, @object-ui/types#DatasourceMetric, @object-ui/types#DatasourceAlert, @object-ui/types#DatasourceManager, @object-ui/types#HealthCheckResult, @object-ui/types#DatasourceMetrics) the retired operands are TypeScript-only interfaces and unions for a server-side SQL driver and a datasource manager that no renderer, adapter or host implements; nothing in the workspace or in hotcrm imports any of them from the package entry, they have no zod twin, 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 fifteen types from `@object-ui/types`. None had an importer anywhere in the workspace or in hotcrm, so the Console and every `@object-ui/*` package are unaffected.
13+
14+
They are **gone from the package, not only from the entry**: the "Phase 3.6" driver interfaces and the "Phase 3.7" datasource interfaces at the end of `data-protocol.ts` were read only by each other. Nothing implemented the driver interface and nothing constructed the datasource manager; the client talks to data through the `DataSource` interface instead. Every other name of `data-protocol.ts` stays exported unchanged, among them `DriverQueryConfig`, `SqlQueryAST`, the query-AST node types, `AdvancedFilterSchema`, `FilterBuilderConfig` and `ObjectValidationRule` with its variants.
15+
16+
What leaves, and what to write instead:
17+
18+
- FROM `SqlDriverInterface`, `ConnectionConfig`, `DriverQueryResult`, `BatchOperation`, `BatchResult`, `TransactionContext`, `CacheManager`, `ConnectionPool` TO no replacement in this package. A client reads and writes data through `DataSource` (`BatchTransactionOperation` with `DataSource.batchTransaction` for a transactional batch); the platform's runtime driver contract is `DriverInterface` from `@objectstack/spec/data`.
19+
- FROM `DatasourceRegistration`, `DatasourceType`, `DatasourceMetric`, `DatasourceAlert`, `DatasourceManager`, `HealthCheckResult`, `DatasourceMetrics` TO no replacement in this package. An authored datasource is `DatasourceSchema` from `@objectstack/spec/data`; a host that kept its own registry declares these shapes locally.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): an objectstack-data eval checks that a vendor field is relabelled with `fieldPatches`, never redeclared in `fields`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`skills/objectstack-data/evals/objects-fields-relationships.json` gains case 7: an author wants HotCRM's `crm_account.industry` to read "Sector" instead of "Industry". The accept set requires `fieldPatches` on a `defineObjectExtension` authored in `hotcrm.custom` (an option's label keyed by its stored value where the field declares its own options); it refuses `industry` redeclared in `fields`, which takes the field over and cuts it off from HotCRM's upgrades. No skill text changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the objectstack-data skill now leads a hook author to the save notice
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The `objectstack-data` rule index described `rules/lifecycle.md` as retention-only, and `references/data-hooks.md` listed no `notice` member, so an author writing a save hook from this skill never found `ctx.notice(message)`. The index entry now names the save notice, and the sandbox `ctx.*` table and the `HookContext` listing each carry a `notice` row that points at `rules/lifecycle.md` § Save Notice. Growth is paid by same-file deletions of restated prose; both token-ratchet rows are re-locked down at the landed counts.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
fix(plugin-auth)!: an MCP access token can be revoked — `/oauth2/revoke` and the Console's consent withdraw end it on the next request, through the OAuth consent it was minted under
6+
7+
Clause-②: no (narrowing) — the MCP door refuses a bearer whose consent is gone or newer than the token, which the published contract already promises (`packages/spec/src/contracts/auth-service.ts` `verifyMcpAccessToken`: "a falsy value when the token is unknown/expired/revoked … callers fail CLOSED"), and a bearer with no `azp` claim to key that read; the revoke door's 200 for an invalid or another client's token removes a refusal RFC 7009 §2.1/§2.2 already negates. The contract text is not changed.
8+
Metadata-protocol: none
9+
Skills-docs: none — `skills/**` says nothing about revoking an MCP OAuth access token or withdrawing a consent, so no published skill text moves; the rule is documented in `mcp-token-revocation.ts` and on `AuthManager.verifyMcpAccessToken`.
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) no authorable key, value or export is removed or renamed; no metadata parses or saves differently, and only which OAuth bearers the MCP door admits after a revoke narrows -->
12+
13+
**BREAKING** narrowing at the MCP OAuth door and `/oauth2/revoke` (`@objectstack/plugin-auth`), executing the maintainer's ruling A of objectstack-ai/cloud#3606.
14+
15+
- **The consent is the revocable record.** An MCP access token is a JWT the authorization server never stores, so `verifyMcpAccessToken` now reads, on every MCP request, the consent the token was minted under: one indexed read of `sys_oauth_consent` by the token's client (`azp`), user (`sub`) and organization (`organization_id`). The token is refused when that consent is gone, when it was created after the token was issued (revoked, then granted again), when the token carries no `azp`, and when the read fails (fail closed). The MCP route answers a refused bearer `401`.
16+
- **`POST /oauth2/revoke`.** The provider still authenticates the client first, unchanged. For an MCP access token issued to that client, the revoke now deletes the consent and the client's refresh family for that user and answers `200`, where it answered `400 unsupported_token_type` and the token kept working until it expired. A token issued to another client answers `200` and changes nothing. An unknown or invalid token answers `200` (RFC 7009 §2.2) where it answered `400 invalid_request`. A `token_type_hint` that names another token type does not end the search (RFC 7009 §2.1): the MCP access token is revoked all the same. A request the provider refuses before it looks at the token (no client, an unknown client, a malformed request) keeps its refusal. A store error while revoking answers `503 server_error` (RFC 7009 §2.2.1: the token still stands, retry later), and a retry completes it.
17+
- **The Console's withdraw (`POST /oauth2/delete-consent`)** now ends the agent's access on its next request, with no other change: it deletes the same row the MCP door reads.
18+
- **A server-only `skip_consent` client** has no consent row: its tokens are admitted without one, and revoking one keeps the honest `unsupported_token_type`.
19+
- **The accepted cost.** After a revoke, the person sees the consent page again on the next connect.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime)!: `PATCH /api/v1/packages/:id/disable` refuses while an enabled package's validation rule reads a field the package adds through `objectExtensions`
6+
7+
Clause-②: no (narrowing) — the package disable door, and the install door's `enableOnInstall: false` arm on an overwrite of an enabled package, refuse (`409` `PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE`) a disable they accepted before: one that would withhold a field an active validation rule reads. Nothing is widened: no export, request shape, response member or authorable key is added, and the code is the one #30724 registered.
8+
Metadata-protocol: none
9+
Skills-docs: updated here — `content/docs/api/metadata-api.mdx` (the disable door's entry), `content/docs/deployment/marketplace-handbook.mdx` (the Enable-and-disable step and the Disabling-a-module list) and `content/docs/api/error-catalog.mdx` (the code's 409 entry) name the refusal; `skills/**` teaches no package disable door (grep for `packages/:id/disable`, `disableable`, `WRITABLE_PACKAGE_REQUIRED`: none), so no skill or eval moves.
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type of the package root changes: a disable request that would have left an object refusing every write is refused before anything changes, with an error code already registered. Nothing at rest records that answer, so there is nothing for a migration to rewrite and no tombstone. -->
12+
13+
**BREAKING** accept-set narrowing on the package disable door and the install door's `enableOnInstall: false` arm on an overwrite of an enabled package (`@objectstack/runtime`, `domains/packages.ts`), for one case only.
14+
15+
- **Before.** A disabled package's `objectExtensions` fields are withheld from the object they extend. When a validation rule of another package that stayed enabled read one of those fields, the disable answered `200`, and every write to that object was then refused (`400 VALIDATION_FAILED`, naming the rule) for as long as the package stayed off.
16+
- **Now.** The door refuses such a disable with `409` and `error.code` `PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE`, after the read-only check and before anything changes: the package row, the served objects and the environment's stored disable state stay as they were. The message names every reading rule as `package · object · rule (reads FIELD)`, one or several. The refusal carries no other `error.details`.
17+
- **Who is scanned.** Any enabled package, declared dependent or not (no `manifest.dependencies` entry is read), an app's `<base>.custom` pair (where tenant-written rules live) included, at every severity. Each rule is judged as the object will serve it once the disable lands: one rule per name (a later layer's copy replaces an earlier one), with a tenant overlay's `message` and `active` on it, so a packaged rule the tenant switched off through the overlay does not refuse the disable. The message names each rule after the layer whose copy is served (`tenant overlay` for a rule the overlay declares itself). A rule reads a field when its predicate names it on the rule's own object or one hop through a reference field of that object (`record.account.FIELD`), or when it is a `state_machine` / `format` / `json_schema` rule on that field.
18+
- **The install door too, on an overwrite of an enabled package.** `POST /api/v1/packages` with `overwrite` and `enableOnInstall: false`, over a package that is enabled before the install, asks the same scan before it disables: such an overwrite still lands, the package stays enabled, and the door answers the same `409` instead of a `201` claiming a disable. A fresh install (or a reinstall after a delete) and an overwrite of a disabled package withhold nothing a rule is served against, and land disabled as asked, as before.
19+
- **Unchanged.** A formula over the field is no reason to refuse (it answers `null`), nor is a rule switched off (`active: false`) or a rule of a package that is itself disabled. Every other disable answers as before.
20+
- **Who moves.** An administrator or client disabling such a package, or overwriting it, while enabled, with `enableOnInstall: false`: a refusal naming the rules, instead of a disable that left the object refusing every write.
21+
22+
**The remedy.** Switch off or change each named rule first, or disable the package that owns it, then disable the package; or keep the package enabled.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
i18n(service-settings): the built-in Setup settings pages ship in Russian, German and Korean
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The built-in settings pages (Mail, SMS, Branding, Company, Localization, Authentication, File Storage, AI & Embedder, Knowledge, and the Data Lifecycle page `@objectstack/objectql` registers) now carry `ru`, `de` and `ko` beside the existing eight locales: page titles and descriptions, group headings, field labels and help, option labels, banners, action buttons and the source badges. An administrator reading Setup in one of those languages sees these pages in their own language instead of English. Country pickers name each country in the reader's language through `Intl.DisplayNames`, as the existing locales do. The settings coverage test now holds each new locale to the same full coverage. Second half of package 6 of #30512; with it every built-in settings page carries all eleven platform locales.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
fix(rest): the external-datasource routes relay a declared service outage as `503 SERVICE_UNAVAILABLE`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`POST /api/v1/datasources/:name/external/tables/:remote/import` answered a `tenancy` service outage as `400 EXTERNAL_IMPORT_ERROR`, telling the caller the request was malformed. The import reads the effective tenancy posture to decide where the imported object lands, and a registered `tenancy` service that cannot answer throws the `503` / `SERVICE_UNAVAILABLE` envelope there. The route now relays that envelope, as the datasource admin routes and the MCP `import_object` tool already do, so the caller is told to retry. Nothing was saved before and nothing is saved now. The other routes under `/datasources/:name/external/*` share the same catch and relay a declared outage the same way; every other refusal keeps its `400` and code.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/plugin-sharing': patch
3+
---
4+
5+
fix(plugin-sharing)!: a sharing rule on an object without record sharing is refused at save, not saved to do nothing
6+
7+
Clause-②: no (narrowing)
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. What moves is the save door's answer to a sys_sharing_rule row whose object no sharing gate consults; a rule already stored keeps its row and can still be switched off, so there is nothing for a migration to rewrite. -->
11+
12+
**BREAKING** accept-set narrowing at the sharing-rule save doors (`@objectstack/plugin-sharing`): a rule whose object is not under record-sharing enforcement is refused with `422 SHARING_NOT_ENABLED` instead of saved.
13+
14+
- **Before.** An administrator could save a sharing rule on an object no sharing gate consults, such as `sys_session`, `sys_account` or `sys_api_key` (a system object with no `owner_id`), a public object without an owner, a `controlled_by_parent` detail or a bypass-listed object. The save answered `201`, the rule showed as active in Setup, and its reconcile then refused every grant (`SHARING_NOT_ENABLED`) and wrote no `sys_record_share`, so the rule's recipients saw nothing more than before. Nothing told the administrator.
15+
- **Now.** Both save doors refuse that rule and save nothing: the `/data` door (Setup's New and edit forms, scripts, imports) through a `sys_sharing_rule` hook guard, and `POST /api/v1/sharing/rules` (`SharingRuleService.defineRule`). The answer is `422 SHARING_NOT_ENABLED`, the pair the per-record share routes and `POST /api/v1/sharing/rules/:idOrName/evaluate` already answer for the same verdict. Its sentence names the object and says how to make it one a rule applies to, for example "give the object a private or read-only sharing model (`sharingModel: 'private'` or `'read'`) and an `owner_id` field", or "author the rule on its master object instead". An update is refused when it moves a rule onto such an object.
16+
17+
The verdict is the reconcile's own: the save doors read the same ADR-0111 D7 inert-grant check (`SharingService.sharingRuleObjectRefusal`, over `inertGrantReason`) that the grant pre-flight throws on, so the two cannot disagree. A public object that has an owner column is still admitted, because the grant service there still grants an external member of the record's organization (#25549). System writes pass, as with the declared-name guard.
18+
19+
To fix a rejected save, author the rule on an object that uses record sharing, or turn record sharing on for the object as the sentence says. A rule already stored on such an object is not migrated or deleted: it keeps sharing nothing, and an edit that leaves it on its object (for example switching it off) still saves.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the solution-blueprint widget `aggregate` describes, on the lenient schema and its strict mirror, no longer promise "the reader's own default" when the key is unset. Both now read one shared rule: a widget that names a `measure` and leaves `aggregate` unset is incomplete, and a reader may refuse it; no default aggregation is promised. Only a count widget, which names no `measure`, may leave it unset, and the strict mirror's `null` stays legal there, as its describe now says. Describe text only: the accepted set of both schemas is unchanged.
6+
7+
Clause-②: no
8+
Metadata-protocol: none
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-api's Metadata API section teaches the `/meta/:type` list door's `?names=a,b,c` filter (one comma-separated value; an unknown name absent, an empty value refused `400`, the `package` list refuses the key) and its closed query set (`package`, `preview`, `object`, `include`, `id`, `names`, `locale`; any other key `400 VALIDATION_ERROR`)
6+
7+
Clause-②: no
8+
Metadata-protocol: none

0 commit comments

Comments
 (0)