Skip to content

Commit 3e24b82

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-sxds3e
2 parents 158742d + 645ed3d commit 3e24b82

27 files changed

Lines changed: 1175 additions & 311 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/cloud-connection': patch
3+
---
4+
5+
fix(cloud-connection)!: install-local's cloud branch refuses every register the engine refuses, and keeps the lenient answer only for a fault (#31478)
6+
7+
Clause-②: no (narrowing) — `POST /api/v1/marketplace/install-local` with a cloud-fetched package (`packageId` + `versionId`) whose `manifest.register` throws a refusal declaring a 4xx status answers `422 PLUGIN_REGISTER_FAILED` where it answered `200` with `hotLoaded: false` and wrote the ledger entry. Nothing is widened.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) no key of any schema is added, removed or re-shaped and no stored row is read differently: the narrowing is one runtime refusal at one door, for register refusals the inline branch of the same door already answers 422, and its remedy is the correction the engine's sentence names, so there is nothing for `objectstack migrate meta` to rewrite. A ledger entry an earlier build wrote this way is read as before at boot. -->
11+
12+
**BREAKING** for a cloud-branch install that relied on the lenient answer for a register the engine refuses.
13+
14+
- **Before.** The cloud branch refused only the registry's three held-name refusals (#25366, #31296, #31392). Every other register refusal was ledgered and answered `200` with `hotLoaded: false`, "loads at the next restart": a default record page another package holds, `isDefault: true` on a page that is not a record page, an object another package owns, an object name two packages of one artifact (or of one namespace shared through a declared dependency) define, a picklist no loaded package declares, a bound action whose object no loaded package registers. The next boot refused each again and loaded nothing.
15+
- **Now.** Each is answered `422 PLUGIN_REGISTER_FAILED` with the engine's sentence, as the inline branch answers it. Nothing is ledgered, and an installed earlier version stays registered and ledgered. The door recognises a refusal by the status the throw declares (any 4xx), never by a list of classes or codes, so the held-name refusals are answered by the same rule.
16+
- **Unchanged.** A register failure that declares no 4xx — an undeclared throw or a declared 5xx, the runtime's own fault — keeps the lenient answer (`200`, `hotLoaded: false`, `hotLoadError`) and loads at the next restart. A namespace held by a writable base is still refused `409 RESOURCE_CONFLICT` before the register.
17+
18+
**The remedy.** Make the correction the refusal names, then install again: rename or un-default the page, rename the object or `extend` the owner's, declare the picklist or the bound object (or install the package that brings it, and declare it in `manifest.dependencies`), or uninstall the holding package.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/plugin-grid': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(plugin-grid): the import wizard's "Save current" template button says why it is disabled
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
On the mapping step, while no column is mapped, "Save current" in the mapping-template bar was natively disabled with no reason: no tooltip on hover, no accessible description, and Tab skipped it. It now sits in a focusable wrapper, the same pattern as the step's Next button, whose tooltip and accessible description read "Map at least one column to save a template." Once a column is mapped it is a plain enabled button, as before. The sentence is a new `grid.import.saveCurrentNeedsMapping` key in every `@object-ui/i18n` locale pack.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): Studio's disabled Publish says why there is nothing to publish, on hover and on keyboard focus, the way it already does on a read-only package
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
With no drafts pending, the Studio header's Publish button was natively disabled and carried its reason ("No drafts pending publish") only as a `title` on the button itself. A disabled button fires no hover or focus events, so no tooltip opened and Tab skipped Publish entirely. It now takes the read-only state's treatment: a focusable wrapper that is the tooltip trigger and a tab stop, with the wrapper and the button both described by the existing `sr-only` reason. The text is the existing `engine.studio.publishNoneTitle` sentence; no locale key is added.
11+
12+
The read-only state is unchanged, a pending draft still shows the plain enabled Publish button, and a publish in flight keeps its spinner without a wrapper.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security)!: a delegated answer about a user the caller cannot read does not depend on that user's existence or memberships
6+
7+
Clause-②: no (narrowing) — the delegated-administration gate judges every target user through the caller's own read, so a user the caller cannot read is outside every delegated subtree: a delegated administrator no longer edits or removes a stored direct grant of such a user, and no longer explains such a user's access or reads its effective access. Nothing is widened: no export, 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 of the package entry changes: an adminScope and a direct grant keep their shape and meaning, and a target user is judged through the caller's own read. There is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing in the delegated-administration gate (`@objectstack/plugin-security`), only for a delegated administrator asking about a user it cannot read.
13+
14+
- **Rule.** A delegated answer about a user the caller cannot read does not depend on that user's existence or memberships (ADR-0090 D12). Wherever the gate judges a target user, such a user is outside every delegated subtree, exactly as an id naming no user is: the holder a direct grant names (a new grant, or a stored grant pointed at another user), the holder a stored grant keeps (its removal, or an edit that leaves `user_id` as stored), and the user the explain doors ask about.
15+
- **What a caller sees.** A delegated direct-grant write whose target user the caller cannot read answers `403 DELEGATION_SCOPE_VIOLATION`, with the sentence and details of every target outside the subtree. `security/explain` with a `userId`, and `security/users/:id/effective-access`, answer a delegate asking about a user it cannot read with their refusal of a user the caller may not describe (`403 PERMISSION_DENIED`), the answer an id naming no user gets.
16+
- **Unchanged.** A target user the caller reads is judged as before, on its business units. A tenant-level administrator's writes, and an organization administrator's writes and explain coverage, are judged as before.
17+
- **Who moves.** A delegate whose stored grant's holder it cannot read can no longer edit or remove that grant, and a delegate can no longer explain, or read the effective access of, a user it cannot read. Tenant-level and organization administrators keep both: either still removes such a grant, and explains such a user, as before.

‎content/docs/permissions/delegated-administration.mdx‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,15 @@ fact. Capability bits are never BU-scoped.
102102

103103
The [explain engine](/docs/permissions/explain) closes the loop twice:
104104

105-
- explaining **another user** is authorized by `manage_users` *or* a delegated
106-
`adminScope` whose subtree covers that user — a delegate can diagnose their
107-
own people, and only their own people;
105+
- explaining **another user** — and reading that user's effective access — is
106+
authorized by `manage_users`, organization administration of an
107+
organization that user is a member of, *or* a delegated `adminScope` whose
108+
subtree covers that user — a delegate can diagnose their own people, and
109+
only their own people. A scope covers a user the delegate can read, through
110+
a business unit of the subtree; a user it cannot read is covered by no
111+
scope, so the answer about that user does not depend on whether it exists
112+
or what it is a member of. The direct-grant write gate judges its target
113+
user the same way, a stored grant's holder included;
108114
- contributor attribution plus the `granted_by` stamp answer both *who
109115
granted this* and *who could have*.
110116

0 commit comments

Comments
 (0)