Skip to content

Commit b897c9d

Browse files
committed
Merge origin/main into the 2i PR A branch (#26137)
#26278 widened the ci.yml `ui` filter (every root workflow, .github/prompts, skills/objectui). Both sides kept: main's entries, then this PR's turbo globalDependencies and the SDUI manifest's four files. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS
2 parents d36dc42 + 66d5cd6 commit b897c9d

304 files changed

Lines changed: 10497 additions & 3751 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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
'@object-ui/react': patch
3+
'@object-ui/core': patch
4+
'@object-ui/data-objectstack': patch
5+
'@object-ui/app-shell': patch
6+
'@object-ui/plugin-designer': patch
7+
---
8+
9+
refactor(react): `AdapterCtx` / `useAdapter` are typed by `DataSource`, and `@object-ui/react` no longer depends on `@object-ui/data-objectstack`
10+
11+
Clause-②: yes (narrowing)
12+
Metadata-protocol: none
13+
14+
<!-- adr-0087: not-required (runtime-interface-only ui/packages/react/src/context/AppShellContext.tsx#ContextAdapter) No metadata moves: the narrowed type is the one `AdapterCtx` and `useAdapter()` carry, a runtime TypeScript type of a React context, with no Zod schema, spec declaration or stored row behind it, so there is nothing for objectstack migrate meta to rewrite; the compiler names the affected call site. -->
15+
16+
The runtime no longer depends on one adapter, so a bring-your-own backend installs `@object-ui/react` without the ObjectStack adapter, and learns at the type level which `DataSource` members it lacks.
17+
18+
- **`@object-ui/react`**: `AdapterCtx` and `useAdapter()` are typed by the protocol-level `DataSource` from `@object-ui/types`, not by `ObjectStackAdapter`. A new exported extension point, `AdapterContextRegister`, lets a host declare the concrete adapter its provider installs; `useAdapter()` then returns that adapter and an `AdapterCtx.Provider` value is checked against it. `ContextAdapter` names the resolved type. `@object-ui/data-objectstack` moves from `dependencies` to `devDependencies` (one boundary test still reads it).
19+
- **`@object-ui/core`**: exports `API_ACCESS_DENIED_CODES` and `isApiAccessDeniedError`, the `enable`-block API denial predicate, moved here unchanged so `@object-ui/react` can classify the denial without the adapter.
20+
- **`@object-ui/data-objectstack`**: re-exports both names from `@object-ui/core`; its published face is unchanged.
21+
- **`@object-ui/app-shell`**: declares `ObjectStackAdapter` on `AdapterContextRegister`, and the declaration ships in its published types, so a host that mounts `AdapterProvider` keeps today's `useAdapter()` type.
22+
- **`@object-ui/plugin-designer`**: declares the same adapter for its own pages, which read the ObjectStack client.
23+
24+
**BREAKING** (a type narrowing) for a TypeScript reader of `useAdapter()` / `AdapterCtx` from `@object-ui/react` that reads an `ObjectStackAdapter` member `DataSource` does not declare (`getClient`, `probeAppAccess`, `probeMetaItemAccess`, `getConnectionState`, `onConnectionStateChange`, `clearCache`, `queryDataset`, …) and does not import `@object-ui/app-shell`. Runtime behaviour is unchanged: the context holds the same adapter object as before.
25+
26+
- **Before.** `useAdapter()` returned `ObjectStackAdapter | null` whatever the host installed.
27+
- **Now.** It returns `DataSource | null` unless the host declares its adapter, and reading `getClient` off it is `error TS2339`.
28+
- **Fix.** Either declare the adapter your provider installs, once, in your own package:
29+
30+
```ts
31+
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';
32+
33+
declare module '@object-ui/react' {
34+
interface AdapterContextRegister {
35+
adapter: ObjectStackAdapter;
36+
}
37+
}
38+
```
39+
40+
or narrow at the call site that needs the ObjectStack-only member, and keep reading `DataSource` members everywhere else.
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+
refactor(app-shell): the Console `ObjectView`'s leaf pieces move into a sibling module
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The "object not found" state (`ObjectNotFoundState`), the viewport check that decides whether the saved-view tab strip is drawn, and the inline-edit toggle's session-only callback move verbatim out of `views/ObjectView.tsx` into `objectViewChrome.tsx` beside it. No logic, name or rendered output changes. `ObjectView.tsx` re-exports every name it exported before, and the package entry is unchanged.
11+
12+
What to do: nothing.
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+
refactor(app-shell): the Console `ObjectView`'s realtime refresh and record-count fetch move into a sibling hook
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The realtime subscription that refreshes the list when the server reports a change, the reconnection conflict resolution, and the footer's `$top: 0` record-count fetch move verbatim out of `ObjectViewInner` in `views/ObjectView.tsx` into `useObjectViewLiveData` in `objectViewLiveData.ts` beside it. The component calls the hook at the same place, so the hook order is unchanged; the record-count state stays in the component and is passed in with the other inputs. No logic, name or rendered output changes, and the package entry is unchanged.
11+
12+
What to do: nothing.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/plugin-grid': patch
3+
---
4+
5+
refactor(plugin-grid): `ObjectGrid`'s module-level helpers move into sibling modules
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The query helpers (the held filter resolution, the record-source ladder, the find-params signature), the column helpers and column emit types, the record-link cell, the English fallback translations, the display defaults (row height, page sizes, selection default) and the row-version helpers move verbatim out of `ObjectGrid.tsx` into six modules beside it. No logic, name or rendered output changes. `ObjectGrid.tsx` re-exports every name it exported before, and the package entry is unchanged.
11+
12+
What to do: nothing.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/cli': patch
3+
'@object-ui/i18n': patch
4+
'@object-ui/app-shell': patch
5+
'@object-ui/console': patch
6+
---
7+
8+
fix(cli,i18n,console): the hosted product reads "ObjectOS Cloud" everywhere a user reads it; the Community Console carries a "Powered by ObjectStack" mark
9+
10+
Clause-②: no
11+
Metadata-protocol: none
12+
13+
ObjectOS is one product in three editions — Community (this repository's runtime and Console, open source), Cloud (cloud.objectos.ai) and Enterprise — and ObjectStack is the name of the protocol, SDK, CLI and framework. The hosted product is "ObjectOS Cloud" wherever a user reads it; it was still "ObjectStack Cloud" in the CLI and in the Console's cloud-install strings.
14+
15+
- **`@objectstack/cli`.** `os cloud login`, `os cloud logout`, `os cloud whoami`, `os package publish` and `os plugin publish` say "ObjectOS Cloud" in their `--help` descriptions, their `--url` / `--server` / `--token` / `--visibility` flag descriptions, their headers and their "Not logged in" prescriptions; so do the `os serve` welcome page's link to the cloud console and the shared "Authentication required" message. The CLI README and the deployment CLI reference follow. Internal identifiers are unchanged: `~/.objectstack/cloud.json`, `OS_CLOUD_API_KEY`, `OS_CLOUD_URL`, the `os cloud …` command names.
16+
- **`@object-ui/i18n`.** The cloud-connection "Connected to …" title and the three cloud-install dialog sentences ("You need to be signed into …", "… sign into … first") name "ObjectOS Cloud" in all ten locales, the product name kept untranslated inside each locale's sentence. New key `auth.shell.poweredBy` ("Powered by ObjectStack") in all ten locales.
17+
- **`@object-ui/app-shell`.** New `isDefaultProductName()`: true while the deployment carries the default `branding.productName` ("ObjectOS"), false once a runtime has pushed its own.
18+
- **`@object-ui/console`.** The sign-in layout (login, register, forgot/reset password, verify email, setup, consent, device, invitation) shows a small "Powered by ObjectStack" link to objectstack.ai under the form when `isDefaultProductName()` is true. A deployment that pushes its own product name — ObjectOS Cloud, Enterprise, a cloud-connected workspace — shows its own name and no second brand line. The default product name, `logo.svg`, `manifest.json` and `admin@objectos.ai` are unchanged.
19+
20+
What to do: nothing. An operator who rebrands the Console through `branding.productName` keeps a single brand line, as before.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
---
4+
5+
fix(plugin-audit): a member removes their own comment reaction through an own-row unreact door, with no object delete grant
6+
7+
A member whose grants are the every-member ones could add a comment reaction and never take it back. The console removes a reaction through the generic data door's `DELETE /api/v1/data/sys_comment_reaction/:id`, which needs an object-level `delete` grant, and no every-member set may carry one: ADR-0090 D5 keeps `delete` off the `everyone` anchor (`security-anchor-high-privilege`), and `member_default` carries none. Measured as a plain member: add a reaction → `201`, delete it → `403`.
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
`AuditPlugin` now mounts `DELETE /api/v1/comments/reactions/:id` on the kernel's HTTP server. It removes the caller's own `sys_comment_reaction` row and nothing else:
13+
14+
- The row is read as the caller, so every read gate decides. A reaction on a comment the caller cannot read answers `404 RECORD_NOT_FOUND`, the same answer as an id that names no row, so the door says nothing about a reaction the caller cannot see.
15+
- The row's reactor (`created_by`, the column the platform's own-record floor reads) must be the caller. Anyone else's reaction is refused with `403 PERMISSION_DENIED` and stays.
16+
- Only then is the row removed, under a system context attributed to the caller and kept in the caller's organization. Success answers `200` with `{ success: true, data: { id, deleted: true } }`.
17+
- Before any of that the door answers what every door answers: `401 UNAUTHENTICATED` for a request that names nobody, a held sign-in's own `403`, the permission store's `503` when it cannot be read, and `400 VALIDATION_ERROR` for any query parameter.
18+
19+
The grant model does not move. The generic data-door delete answers exactly what it answered before (`403` for a member with no delete grant), no permission set gains `delete`, and `member_default` and the `security-anchor-high-privilege` rule are unchanged. The console's reaction chip moves to the new route in a separate change; until then the route is reachable but nothing in the console calls it.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/console': patch
4+
---
5+
6+
fix(app-shell,console): one app opens under one address — a name-alias route moves onto the package id
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
An app with a package id answered under two address families in one session. ADR-0048 keys `/apps/<segment>` on the package id, and every sidebar entry builds on it, but the app's `name` still resolves as an alias, and a page opened on the alias built every link of its own (list rows, record pages, related lists) on the alias. Landing on `/apps/clm/…` therefore opened contracts under the name and obligations and the approvals inbox under `/apps/app.objectstack.hotclm/…`.
12+
13+
- **`@object-ui/app-shell`:** a route under `/apps/<name>/…` whose app carries a package id is rewritten, with `replace`, to the same path under `/apps/<package id>/…`, keeping the query string, hash and history state. Old name-based bookmarks and shared links keep opening, now on the one address. The top-bar logo and every Home affordance (`resolveDeclaredHomePath`) name the declared app by its package id. An app without a package id, an app whose name is its package id, and every app after the first of a package that ships several (the package id opens the first) keep their name as their address.
14+
- **`@object-ui/console`:** the `/` landing (`resolveLandingPath`) names the landing app by the same address, so post-login and the logo agree and no redirect hop follows.
15+
16+
What to do: nothing. Links that name an app by its name keep working; they now arrive on its package-id address.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
'@objectstack/plugin-security': patch
4+
'@objectstack/spec': patch
5+
---
6+
7+
fix(metadata-protocol)!: a permission set that grants a hierarchy scope is refused on publish, and warned on a draft save, when the runtime mounts no hierarchy resolver; explain names the absent resolver (#26188)
8+
9+
Clause-②: yes (narrowing: on a runtime with no hierarchy resolver, publishing a permission set whose grant uses `own_and_reports`, `unit` or `unit_and_below` is refused, where it used to answer 200; widened: `@objectstack/spec/security` gains the export `findHierarchyScopeGrants`, and `@objectstack/plugin-security`'s `ExplainEngineDeps` gains the optional member `hierarchyResolverMounted`)
10+
Metadata-protocol: none
11+
Ruling-ref: #26059 ruling Q3 / Q4 (`6416233738`), items 9a and 9b of the item reading (`6415971852`)
12+
13+
`own_and_reports`, `unit` and `unit_and_below` are enforced only by the enterprise hierarchy resolver (capability `hierarchy-security`, ADR-0057). Without it the open sharing service evaluates them as `own`. `defineStack` already refuses such a grant unless the stack declares `requires: ['hierarchy-security']`, but a runtime save of the same permission set answered 200 with no word, and explain then told a manager that a subordinate's record was outside their unit.
14+
15+
What changes:
16+
17+
- The runtime authoring gate reads whether a hierarchy resolver is mounted (the kernel service `hierarchy-scope-resolver`) on every metadata write. When none is, a permission set whose `readScope` or `writeScope` is a hierarchy depth is refused on publish: a direct active save, the per-item publish of a draft, the package batch publish and a restore to such a version all answer 422 `INVALID_METADATA`, with one `issues[]` entry per grant under the rule `hierarchy-scope-capability-required`. The entry is located at the grant and names the `hierarchy-security` requirement.
18+
- A draft save of the same body still succeeds. Its answer carries the same finding, at `severity: 'warning'`, in `advisories`.
19+
- With a resolver mounted, nothing changes. A host with no service registry (the CLI) judges nothing here; `defineStack` is that host's door.
20+
- `explainAccess`: on a deployment with no resolver, the depth layer's record cell for a hierarchy read depth now reads "Hierarchy resolver absent — requires enterprise; depth evaluated as 'own'." instead of saying the owner is outside the caller's read depth.
21+
- `@objectstack/spec/security` exports `findHierarchyScopeGrants`, the one list of hierarchy depths and the one finding sentence. `defineStack`'s refusal reads it too, and its message is unchanged.
22+
23+
**BREAKING** for a runtime with no hierarchy resolver that publishes hierarchy-scope grants at runtime.
24+
25+
- **Before.** The publish answered 200 and the grant was enforced as owner-only.
26+
- **Now.** The publish answers 422 `INVALID_METADATA` with the rule `hierarchy-scope-capability-required`. A draft save warns.
27+
- **Fix.** Set the grant's `readScope` / `writeScope` to `own` or `org`, or run the permission set on a runtime that mounts the enterprise hierarchy resolver (`hierarchy-security`). Rows already stored are read as before.
28+
29+
<!-- adr-0087: not-required (no-migration-prescription) a run-time narrowing at the metadata publish door, conditional on a deployment fact (no hierarchy resolver mounted). No authorable key, spelling or stored shape moves: the permission-set schema parses exactly as before, `defineStack` already refused the same grant, and no stored row is read, rewritten or converted. The repair is the author's choice between a non-hierarchy depth and an enterprise runtime, which no ledger entry can derive. The other categories are closed on facts: the packages publish (not unpublished); no ADR-0087 id covers this verdict (not already-registered); the one spec export is added, not moved, renamed or removed; and nothing a stored artifact carries changes type, so no conversion has a source to rewrite. -->
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/plugin-security': patch
4+
'@objectstack/plugin-auth': patch
5+
---
6+
7+
feat(plugin-security,spec,plugin-auth)!: a delegated admin's write outside their own scope answers `403 DELEGATION_SCOPE_VIOLATION` with the gate's reason
8+
9+
When a delegated administrator's write on `sys_user_position` or `sys_user_permission_set` fell outside their own `adminScope`, the delegated-administration gate knew exactly why ("business unit … is outside the delegated subtree", "… is not in the scope's allowlist"), but threw it as a plain `403 PERMISSION_DENIED`. A client could not tell it from a denial whose reason must stay hidden, so the console showed only its fixed "no permission" line.
10+
11+
Clause-②: yes (widening: the ADR-0112 ledger registers `DELEGATION_SCOPE_VIOLATION` under `@objectstack/plugin-security`)
12+
Metadata-protocol: none
13+
14+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, object definition, stored row or stored shape is added, removed, renamed or re-shaped, and no exported declaration is removed or narrowed, so there is nothing for `objectstack migrate meta` to rewrite. What changes is the error code three of the gate's refusal reasons answer with: every write that was refused is still refused, with the same 403 status and the same reason, and every write that was admitted is still admitted. The remedy is on the consumer's side, reading `code`; never a rewrite of a package source or a stored row. The other categories are closed on facts: all three packages publish (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
15+
16+
**BREAKING** (a wire-answer change), shipped as `patch`: no metadata protocol kind moves.
17+
18+
- **`@objectstack/plugin-security`.** Three reasons describe the caller's own delegated scope: a business unit outside the delegated subtree (the new row's or the existing row's), a permission set not in the scope's allowlist (one the position distributes, or one granted directly), and a target user outside the subtree. They now throw `DelegationScopeViolationError`: status `403`, code `DELEGATION_SCOPE_VIOLATION`, and the message `[Security] Outside your delegated scope: delegated 'OP' on OBJECT rejected — REASON (scope from 'SET').` Every other refusal of the gate keeps `403 PERMISSION_DENIED`, unchanged: the audience anchor, principal-less writes, `sys_member` being tenant-only, the organization boundary, plain CRUD with no scope, filter writes, delegation of duty, a scope without `manageAssignments`, an unanchored assignment, adminScope containment, a stored grant whose set is unnamed or not in the catalog, a grant naming no target user, and a target user with no business unit.
19+
- The message does not open with `[Security] Access denied`, which `mapDataError`, `isPermissionDeniedError` and the explain route read as `PERMISSION_DENIED`. The REST door (`mapDataError`) and the runtime dispatcher (`resolveThrownHttpError`) carry the new code as it was thrown.
20+
- **`@objectstack/spec`.** One ADR-0112 ledger row, `DELEGATION_SCOPE_VIOLATION`, registered as its own meaning. It is not a synonym of `PERMISSION_DENIED`, and no waiver is needed.
21+
- **`@objectstack/plugin-auth`.** The invitation door reads the new code as the gate's verdict too, so an invitation placed outside the issuer's scope still answers `403 INVITE_PLACEMENT_OUTSIDE_ISSUER_SCOPE`, not the `503` read-fault answer.
22+
- For a client: a `403` with code `DELEGATION_SCOPE_VIOLATION` carries a message about the caller's own scope, which is safe to show. A `403 PERMISSION_DENIED` stays opaque. A client that matched `PERMISSION_DENIED` to catch these three refusals should also match the new code.

0 commit comments

Comments
 (0)