Skip to content

Commit e9c3b06

Browse files
marchtianclaude
andcommitted
Merge origin/main (PR #28828) into the #28829 branch
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2g7RyXa8ErM6GAkHyc
2 parents dd9e43d + 0c1829a commit e9c3b06

122 files changed

Lines changed: 7088 additions & 486 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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
fix(platform-objects): the Setup Users list and the user record's highlight chips stop showing better-auth's legacy `role` scalar as the platform role (#28105)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`sys_user.role` is the column better-auth's admin plugin needs. ObjectStack never writes it: when the plugin is off it is empty, and when it is on the plugin stamps `user` on every new user, the seeded platform admin included. Platform-admin standing comes from ADR-0068 D2 (the `OS_PLATFORM_OWNER_EMAIL` list, or an unscoped `admin_full_access` grant under the `single` posture), not from this column. So a "Platform Role: user" cell on a platform admin was wrong, and it read as authoritative to people and to agents.
11+
12+
- **Users list.** The `all_users` view drops the `role` column. Its columns are now name, email, phone, email verified, banned, identity source and created. `banned` stays (#27863).
13+
- **User record page.** The `record:highlights` chip strip on `sys_user_detail` drops `role`. It shows email, phone and email verification. The details grid still hides `role`, so the record page does not draw it.
14+
- **The field is unchanged.** `sys_user.role` stays a readonly text field in its Admin group, with the same label, storage and description, readable through the data API. Nothing synchronises platform-admin standing into it, and the better-auth `defaultRole` stamp and the import / create-user pass-through are untouched.
15+
16+
A list column that shows real platform-admin standing is tracked separately (#28793).
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(studio): customize a read-only base's flow into its pair under the flow's own name, with one ledger switch, the override badge and "Restore package original"
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
On a read-only base, Studio's Automations pillar now customizes a flow the way the Interfaces pillar customizes a view, a dashboard or a report (ADR-0146 D9 items 1 to 3, D2, D4).
12+
13+
- **Customize this flow.** The flow's header offers Customize, with the same control, the same ensure door (`POST /api/v1/packages/:id/ensure-customization`) and the same disabled reasons as the other customizable types. A walled tenant admin sees it disabled. Pressing it only unlocks the flow's editors. Nothing is written until an edit is saved.
14+
- **The first saved edit** asks the ensure door once and writes the flow's draft into the package that answer names, under the flow's own name. The body is the served flow without its `_` keys, declaring `overrides: { package, basedOn }`. The designer then edits that draft. The rest of the package stays read-only.
15+
- **One switch.** The open override keeps the activation-ledger switch on the flow's name. No definition switch is drawn for it, and Studio never writes the override's `status` to mean on or off.
16+
- **The badge.** The header carries the override badge, read from the served declaration (the pair's draft, else the layered read's `overlay`).
17+
- **Restore package original.** The flow's menu offers it for an override. Whether the flow is overridden is read from the declared override's own row, never from the served envelope's `deletable`, which follows the base's seal. The confirmation says the base's flow runs again under the same switch. Confirming deletes the pair's draft and then the pair's row, and the base's flow reads back. Delete flow stays on a writable package's own flow.
18+
- **Refusals.** A refused save into the pair, and a refused publish of it, read as the server sent them.
19+
- **New string.** `@object-ui/i18n`'s studio namespace gains `engine.studio.customize.restoreFlowBody` in `en` and `zh`. The other languages fall back to `en`, as the whole namespace does.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/lint": minor
4+
"@objectstack/objectql": minor
5+
"@objectstack/metadata-protocol": minor
6+
"@objectstack/runtime": minor
7+
"@objectstack/platform-objects": minor
8+
---
9+
10+
feat(metadata-protocol,objectql): an object extension is an item of a writable package — saved, published and exported through the existing doors
11+
12+
Clause-②: yes (widening)
13+
Metadata-protocol: added
14+
15+
A package can now add fields and rules to an object another package defines by saving an `object_extension` item through `/api/v1/meta`, the same way it saves any other item. This is what Studio's add-field on a base object writes (ADR-0146 D10): an extension in the base's paired `<base>.custom` package, its fields suffixed `__c`, the base untouched.
16+
17+
- **The type** (`@objectstack/spec/kernel`). `object_extension` joins `MetadataTypeSchema` and the metadata type registry. Its body is `ObjectExtensionSchema` as it stands: it carries `extend` and no `name`. The row is filed under the target object's name, one extension per package per target. `OVERRIDE_TYPE_CLASSIFICATION` lists it as `excluded`: an extension adds to its target, it never replaces a base item, so it never carries `overrides`. That disposition does not close the type to a `.custom` pair.
18+
- **The doors.** Draft, publish, history restore and delete are the doors every item uses. The save, publish and restore doors refuse, with `VALIDATION_ERROR` / 400 and an issue path: a write bound to no package; `defaultRowScope` or `membershipKeys` in the body (the registry's own sentences); a row filed under a name other than its `extend`; an unregistered target; a target outside the package's dependency closure (with the closure prescription the other save doors print); and a `.custom` field without the `__c` suffix (the existing `__c` judge). A body the schema refuses answers 422 `INVALID_METADATA` with the schema's issues. No error code is added.
19+
- **The registry** (`@objectstack/objectql`). A published row registers its whole parsed body as the package's `'extend'` contributor (`SchemaRegistry.registerObjectExtension`), so its fields, validations and indexes appear on every read of the object, and a publish adds the column to the target's table. Every stored-row sync re-registers it (boot, write-through, read hydration), and deleting the row or uninstalling the package withdraws it (`SchemaRegistry.withdrawObjectExtension`). What a manifest's `objectExtensions` register is unchanged.
20+
- **The export** (`@objectstack/runtime`). `GET /packages/:id/export` carries each of the package's extension items as an `objectExtensions` entry that re-installs. A package's `objects` are now only the objects it owns: an object the package extends is no longer exported as an `'own'` object, which re-install refused with `ObjectOwnershipConflictError`.
21+
- **`@objectstack/platform-objects`**: the metadata-forms translation bundles gain the type's `label` and `description`, authored in zh-CN, ja-JP and es-ES.
22+
- **`@objectstack/lint/runtime`** exports `closureDependencyRemedy`, the closure prescription the save doors print.
23+
24+
Two costs, both on purpose: `/meta/types` serves no JSON schema for `object_extension`, and a draft carries no `_diagnostics` badge, because the type is not bound in `BUILTIN_METADATA_TYPE_SCHEMAS` (that map requires the ADR-0010 envelope, which would change the schema a manifest's `objectExtensions` parse with).
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+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
fix(app-shell): Studio and Setup read an item's layered view at the package that owns it
9+
10+
The layered view (`GET /meta/:type/:name/layers`) naming no package is refused `409 RESOURCE_CONFLICT` for a name two
11+
installed packages hold, naming the holders and the `?package=` remedy. Reads in Studio and Setup still asked
12+
it naming no package, so on such a deployment the list-view inspector, the Interfaces leaf and app, the Data pillar's
13+
object, the Automations pillar's flow, the package OWD overview, the permission-set matrix, the embedded item editor and
14+
the item page's reset, publish and discard re-reads failed, and the permission-set matrix read the refusal as "no set
15+
known".
16+
17+
Each read now names the package the caller already reads and saves at: the Studio pillar's package (a read-only base's
18+
own package, whose read serves its declared override), the permission matrix's package door, or the owner the edit
19+
URL names (`?package=`). Where none is known, the read still names no package, and the server's refusal sentence is
20+
shown as the load's error; the permission matrix no longer reads it as "no set known". No holder is picked on the
21+
client.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/lint': minor
3+
'@objectstack/metadata-protocol': minor
4+
'@objectstack/types': minor
5+
'@objectstack/rest': patch
6+
'@objectstack/runtime': patch
7+
---
8+
9+
fix(lint,metadata-protocol,rest,runtime,types): a roll-up the engine refuses at every recompute is refused at save and publish, and a child write that landed answers its success (#28661)
10+
11+
Clause-②: no (narrowing)
12+
Metadata-protocol: narrowed
13+
Skills-docs: none — `skills/objectstack-data/rules/relationships.md` and `rules/field-types.md` teach `summaryOperations` only with `sum` over a numeric `amount` and `count`; no file under `skills/**` teaches a `sum`/`avg` over a text field, and every taught example stays admitted.
14+
15+
**BREAKING** (narrowing) for a `summary` field whose `sum` or `avg` names a child field the engine does not aggregate (and, on `os validate`, `os build` and the runtime object save/publish door, a `min` / `max` whose answer is not a number, which `os lint` already refused).
16+
17+
- **Before.** A published `summary` field with `sum` or `avg` over a child `text` field published as valid (`publish-drafts`, the draft's `_diagnostics: { valid: true }`, `os validate`, `os build`). Every later create, update and delete of a child then answered `500 INTERNAL_ERROR`, though the write had landed and the parent's valid roll-ups had recomputed: the recompute's aggregate refuses the pair after the commit. A client that retried a create made a duplicate.
18+
- **Now, at the doors.** The roll-up rule moves from `os lint` alone into the authoring-rule registry (`lintSummaryRollups`, gating, on `os validate`, `os build`, `os lint` and the runtime object save/publish door). A `sum` or `avg` over a child field outside the aggregate × field-type table (`AGGREGATE_FIELD_TYPE_COMPATIBILITY`, the table the engine's aggregate door reads) is refused under `rollup/non-numeric-aggregand`, at `objects.<parent>.fields.<field>.summaryOperations.field`, with the accepted child types and `count` as the fix. The `min`/`max` arm `os lint` already reported keeps its message and now also gates `os validate`, `os build` and the runtime object save/publish door. A child staged as a draft in the same publish batch is judged with it. `os lint` no longer reports the rule a second time.
19+
- **Now, after a write.** When a parent's roll-up cannot be recomputed after a child write — a roll-up stored before this gate, or any other recompute failure — `POST`, `PATCH` and `DELETE /api/v1/data/:object[/:id]` answer the write's own success (`201`/`200`, the same body the write answers when every roll-up recomputes), on both `RestServer` and the runtime dispatcher. The stale summary column is logged at `error`, with the fix, once per field per process (the memory is never cleared; a restart reports it again). The protocol's `createData`/`updateData`/`deleteData` still throw `ERR_SUMMARY_RECOMPUTE` carrying `written`, so the import runner and seed loader read it as before. The recogniser and the earned-answer carrier (`isSummaryRecomputeRefusal`, `earnedAnswerOf`, `staleSummaryAnswerLines`) are new exports of `@objectstack/types`, the one package the protocol, `rest` and `runtime` all depend on.
20+
- **Not refused.** A `summaryOperations.object` naming no object (the card's `qsf_nope`) is still admitted: an unmanaged install (`install-local --unmanaged`) saves a package's parent before the child its roll-up reads, so a door refusing a missing child would refuse that install.
21+
- **Who moves.** No `examples/**` or `hotcrm` roll-up is refused: every `sum`/`avg`/`min`/`max` there aggregates a `number` or `currency` child field.
22+
23+
<!-- adr-0087: not-required (no-migration-prescription) the diff registers no retirement and no key changes shape: a roll-up summing or averaging a non-numeric child field never answered a value, and the fix is an authoring choice (a numeric child field, or count) that no mechanical rewrite can make -->
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@objectstack/objectql": patch
3+
---
4+
5+
Clause-②: yes (widening) — the published `SchemaRegistry` gains one public method, `restoringOnRefusal`
6+
Metadata-protocol: none
7+
8+
fix(objectql): a refused upgrade leaves the installed version serving
9+
10+
When `ObjectQL.registerApp` re-registers a package that is already installed and the registry refuses the new version, the installed version keeps serving, whole, whatever point of the registration the refusal came from.
11+
12+
- The package record, every object, app and item, picklist options and extensions, namespaces, permission-set names and default record pages read as they did before the refused upgrade.
13+
- Another package's extension of the installed version's objects and a tenant's overlay of them are untouched and still served. The restore never goes through uninstall.
14+
- What is refused, and every refusal's code, status and words, is unchanged. A clean upgrade and a refused first install behave as before.
15+
- `SchemaRegistry.restoringOnRefusal(register)` runs one synchronous registration and, when it throws, puts back exactly the registry entries it wrote, then throws the same error. The engine runs an upgrade's registration through it.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/objectql': minor
3+
'@objectstack/cloud-connection': minor
4+
'@objectstack/spec': minor
5+
---
6+
7+
fix(objectql,cloud-connection)!: an object extension's `fieldPatches` reaches the owner's own fields only, at the registry fold and at the install door alike; the install door refuses a bad patch with 422
8+
9+
Clause-②: no (narrowing) — the marketplace install door now refuses a package whose `fieldPatches` names a field only another installed extension adds, which it admitted before, and the registry fold no longer applies such a patch. Nothing is widened.
10+
Metadata-protocol: narrowed
11+
12+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export is removed: the published `ObjectExtensionSchema.fieldPatches` sentence already says a patch changes the display of the target object's own fields, and the fold and the install door now read it that way too. No package in this repository patches a field an extension adds, so there is nothing for a migration to rewrite. -->
13+
14+
**BREAKING** accept-set narrowing at `POST /api/v1/marketplace/install-local` (`@objectstack/cloud-connection`) and in the object fold every read serves (`@objectstack/objectql`).
15+
16+
- **Before.** A patch of a field another extension adds got three answers. The fold applied it only when the adding extension had a lower `priority` than the patching one; the install door judged the fully resolved object, so it admitted it; `os validate` judged the stack's declared objects, so it refused it. A patch of a field the same extension adds in `fields` was applied by the fold and refused by both doors.
17+
- **Now.** A field patch reaches the fields the object's owner declares, and nothing else. The fold skips a patch of any other field, whatever the priorities, and names it once in the log, as it names a patch of a missing field. The install door judges a patch against the owner's declaration (`SchemaRegistry#getObjectOwner`), not the resolved object, so it refuses such a patch; `os validate` already did. The fold judges and patches the record spelling of `fields` only, the spelling the merge reads: a body with array-spelled `fields` handed to `foldObjectExtendersOnto` is not patched, and the patch is named.
18+
- **Status.** The install door's field-patch refusal answers `422 VALIDATION_ERROR` (was 400), the status every other body refusal at that door answers. The code, the message and `details.refusals` are unchanged.
19+
- **Guidance.** `ObjectExtensionSchema`'s `actions` refusal now says the merge also applies `fieldPatches` (`@objectstack/spec`, prose only).
20+
- **Who sees it.** A package that patches a field another extension (or its own `fields`) adds. Measured in this repository: none in `examples/**` or `packages/*/src`.
21+
22+
**The remedy.** Set that field's text where the field is declared: in the `fields` entry of the extension that adds it.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
fix(spec)!: `defineConnector()` refuses a top-level key the connector does not declare, as every other connector door does
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: narrowed
9+
10+
<!-- adr-0087: not-required (already-registered connector-entry-undeclared-key-refused) the same narrowing as that entry, reached through one more door; its surface now names defineConnector() -->
11+
12+
**BREAKING** accept-set narrowing on `defineConnector()` (`@objectstack/spec` and `@objectstack/spec/integration`).
13+
14+
- **Before.** The helper parsed the open `ConnectorSchema`, so a top-level key the connector does not declare was dropped without a word. Because the helper parses first, the closed authored entry behind `defineStack({ connectors })`, `os validate` / `os compile`, the artifact load and the metadata save door never saw the key either: the one helper every documented `connectors:` entry is wrapped in was the one door that stayed silent. TypeScript's excess-property check catches the key only in an object literal, so the gap was reached through a spread, a variable or a cast.
15+
- **Now.** The helper parses the closed connector carrier, behind the same retired-default residue stage as `ConnectorSchema`. Such a key throws a `ZodError` naming it (`unrecognized_keys` at the root), with the message the authored entry gives: a credential-shaped key (`apiKey`, `token`, `secret`, `password`, …) meets the `auth.credentialRef` prescription, a provider setting written at the top level (`baseUrl`, `spec`, `transport`, `headers`, …) is pointed at `providerConfig`, and `timeout` / `timeoutMs` are pointed at `requestTimeoutMs`. The ADR-0010 protection envelope stays declared and a retired key's emitted default (`status: 'inactive'`) stays accepted as residue.
16+
- **Unchanged.** The return type (`ConnectorParsed`). `ConnectorSchema`, the def a plugin hands to `registerConnector`, keeps its open posture. The stack-only rules of an authored entry (no inline `authentication`, `providerConfig` / `auth` need a `provider`, no `actions` on a provider-bound instance) stay at the stack doors, which still apply them to the value the helper returns.
17+
18+
## FROM → TO
19+
20+
```ts
21+
// before: returned without `timeout`; the request timeout stayed the 30 s default
22+
defineConnector({ ...billingApi, timeout: 5000 });
23+
24+
// after: `timeout` is refused by name and pointed at the declared key
25+
defineConnector({ ...billingApi, requestTimeoutMs: 5000 });
26+
```
27+
28+
Move a provider setting under `providerConfig`, put a secret into the secrets layer under the name `auth.credentialRef` gives, and delete any other undeclared key. The upgrade guide entry is `connector-entry-undeclared-key-refused`.
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+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
`postureStampsOrganization`'s docblock no longer says the open engine does not stamp `organization_id`. It now names the open stampers (`ObjectQL#driverWriteTarget` lands an insert in the caller's active organization, `SqlDriver#injectTenantOnInsert` fills an absent `organization_id`) and keeps the true half: activating a walled posture needs the enterprise `@objectstack/organizations` runtime (ADR-0105 D12). Comment only; no behaviour change.

0 commit comments

Comments
 (0)