Skip to content

Commit 274ac85

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-c22v4e
2 parents ff5b181 + 7e6fca0 commit 274ac85

117 files changed

Lines changed: 2980 additions & 586 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: 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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): the Delegated Admin Scope block lists the companion grants a delegate needs, and grants the missing ones in one click
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A permission set's admin scope says what its holder may administer; it does not let the holder in. A delegate also needs the `setup.access` capability to enter Setup, read on `sys_user`, `sys_business_unit` and `sys_business_unit_member` so the lookups can pick people and units, and create, read, edit and delete on `sys_user_position` for the assignment rows. The scope block in the permission set editor now lists these five once the scope names a business unit, and marks each one as granted or not by the set being edited.
11+
12+
"Grant the missing ones" adds only what is missing to the same set, in one edit that the set's normal Save persists: `setup.access` joins `systemPermissions`, and each object row gains only the flags it lacks. No grant the set already carries is removed or narrowed, and no new key is written. Under a package (the Studio Access pillar) the list is shown without the action, with a note that these grants belong on an environment permission set in Setup: the package editor saves only the package's own objects.
13+
14+
What to do: nothing. A set's stored shape is unchanged.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/runtime': patch
4+
'@objectstack/service-settings': patch
5+
---
6+
7+
fix(spec,service-settings): the AI 501 remedy and the AI / knowledge settings hints name the hosted product "ObjectOS Cloud"
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
ObjectOS is the product, in Community, Cloud and Enterprise editions; ObjectStack is the protocol, SDK, CLI and framework. Three runtime sentences still named the hosted product by its retired name:
13+
14+
- **`@objectstack/spec`.** `REMEDY_DETAIL['ai']` — the message every `/ai/*` route's 501 body carries and `/discovery` reports as `services.ai.message` on an open-edition boot — now reads "Provided by @objectstack/service-ai in ObjectOS Cloud/Enterprise — no implementation ships in the open framework".
15+
- **`@objectstack/service-settings`.** The AI live-call hint ("realized only by an ObjectOS Cloud tier") and the knowledge `turso` adapter's "Test connection" hint ("built in the ObjectOS Cloud monorepo") on Settings → AI & Embedder.
16+
- **`@objectstack/runtime`.** A doc comment on the kernel-resolver seam; no behaviour change.
17+
18+
What to do: a client that string-matches the old 501 / discovery message updates the product name in its expected text. Nothing else changes: package names, `OS_CLOUD_*` env vars, `~/.objectstack/cloud.json` and the `os cloud …` commands keep their names.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@object-ui/components': patch
3+
---
4+
5+
fix(components)!: the filter builder's row takes the spec rule's value, so the authored group a `filter-builder` node carries reaches the builder as it is
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-interface-only ui/packages/components/src/custom/filter-builder.tsx#FilterBuilderRow) A published runtime TypeScript interface of a React component with no metadata surface: no Zod schema, no spec declaration and no stored representation, so `objectstack migrate meta` has nothing to rewrite; the affected party is a TypeScript consumer, reached by its own compiler. -->
11+
12+
**BREAKING** type-surface change, compile-time only. `FilterBuilderRow` is exported from `@object-ui/components`. What the builder accepts widens, and what a reader of `row.value` may assume narrows: the member is now optional and admits `null`. Nothing changes at runtime for a reader: the builder already passed both through.
13+
14+
`FilterBuilderRow.value` is now `ViewFilterRule['value']` from `@objectstack/spec/ui`, taken by reference, plus the builder's own list member `(string | number | boolean)[]`. Before, it was the builder's own `string | number | boolean | (string | number | boolean)[]`, and it was required. Measured against the spec's rule, that refused two shapes an authored row takes:
15+
16+
- **Value omitted.** The spec's value is optional, because a value-less operator (`is_null`, `is_empty`, …) carries none. The builder's was required, so the authored `FilterGroup` from `@object-ui/types` was not assignable to the builder's group. The member is optional now.
17+
- **Value `null`.** The spec admits it; the builder's member did not. It does now.
18+
19+
The operator vocabulary needed no change: every spec operator id is a builder id.
20+
21+
Both cases also fix a field switch. Switching the field of a row with no value used to convert the absent value: on a number column it threw on `.trim()`, and on a text column it came back as the string `"undefined"`. A row with no value, or a `null` one, now keeps it through a field switch.
22+
23+
The `filter-builder` renderer now hands the authored `value` and `fields` to the builder with no cast. It declares `value` on its props, because a host that binds the node forwards the group it holds.
24+
25+
**Migration.** Code that reads `row.value` from a `FilterBuilderRow` (a host's `onChange` handler, for instance) handles `undefined` and `null` before using the value, or narrows first with an unset check (`value === undefined || value === null || value === ''`). Code that only writes rows needs no change.

‎.github/workflows/ci.yml‎

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,10 @@ jobs:
163163
crosspkg: ${{ github.event_name != 'merge_group' && steps.changes.outputs.crosspkg || 'true' }}
164164
# The objectui tree under `ui/` (objectstack#25677). Three outputs, read
165165
# together by the four `* UI` jobs at the bottom of this file:
166-
# ui — a path under ui/, or a root file the ui jobs build
167-
# from (the lockfile, the workspace, turbo.json, ci.yml);
166+
# ui — a path under ui/, a root file the ui jobs build from
167+
# (the lockfile, the workspace, turbo.json), or a root
168+
# the ui gates read (every root workflow, the prompts,
169+
# the objectui skill bundle; see the filter below);
168170
# ui_consumers — one of the seven packages objectui installs from this
169171
# tree changed, so the Console is compiled (Type Check
170172
# UI) and built (Build & E2E UI) against the new shape in
@@ -279,6 +281,7 @@ jobs:
279281
- 'tsconfig.json'
280282
- 'tsconfig.base.json'
281283
- 'tsconfig.test.base.json'
284+
- 'vitest.preset.mts'
282285
- '.github/workflows/ci.yml'
283286
# BUILD INPUTS outside every package (#21202): turbo.json itself,
284287
# its `globalDependencies`, and each `$TURBO_ROOT$/…` input of a
@@ -446,14 +449,29 @@ jobs:
446449
# narrower audits entry is left as the package that declared it
447450
# spelled it, exactly as the two narrower `skills/` entries were.
448451
- 'docs/**'
449-
# The objectui tree and the root files the ui jobs build from. A
450-
# change to the guard runs the guard, so ci.yml itself is listed.
452+
# The objectui tree, the root files the ui jobs build from, and the
453+
# three roots outside ui/ that the ui jobs' gates and tests read
454+
# (objectstack#25712):
455+
# - every root workflow, not ci.yml alone: Lint UI's pre-install
456+
# import graph check scans all of `.github/workflows/` for ui
457+
# scripts run before an install. ci.yml, the guard itself, is
458+
# one of them. Measured over the 211 merges of the 7 days before
459+
# this entry: 3 changed another workflow and no ui path;
460+
# - `.github/prompts/**`, where objectui's prompts moved in
461+
# objectstack#25957 and which Lint UI's prompt-keys check reads;
462+
# - `skills/objectui/**`, objectui's skill bundle since
463+
# objectstack#26097. Lint UI's skill gates (skills-paths,
464+
# skill-examples, skill-eval-tokens, doc-json,
465+
# shell-escape-residue) and Test UI's skill readers find it
466+
# through `ui/scripts/skill-bundle-root.mjs`.
451467
ui:
452468
- 'ui/**'
453469
- 'pnpm-lock.yaml'
454470
- 'pnpm-workspace.yaml'
455471
- 'turbo.json'
456-
- '.github/workflows/ci.yml'
472+
- '.github/workflows/**'
473+
- '.github/prompts/**'
474+
- 'skills/objectui/**'
457475
# The seven packages objectui installs from this tree (the set its
458476
# `.objectstack-pin` used to pack). A change here compiles and builds
459477
# the Console in the same PR — the check the retired Spec Shape Gate
@@ -3797,10 +3815,11 @@ jobs:
37973815
# when the tree moved under `ui/` (it reads only this repository's
37983816
# `.github/workflows/`), and which objectstack#25712 deleted with the
37993817
# rest of `ui/.github/workflows/`. They judge what objectui ships
3800-
# — `ui/content/docs/**`, `ui/skills/**`, the package READMEs and the
3818+
# — `ui/content/docs/**`, the skill bundle (`skills/objectui/**` at the
3819+
# repository root since objectstack#26097), the package READMEs and the
38013820
# documented component types — so the change most likely to trip them is
38023821
# a docs-only or skills-only PR, which the `filter` job's `ui` globs
3803-
# already include.
3822+
# include.
38043823
#
38053824
# Ported as that workflow ran them, in its order: the install-free gates
38063825
# here, before the install; the gates that read built `dist/*.d.ts` after
@@ -3883,6 +3902,18 @@ jobs:
38833902
if: ${{ !cancelled() }}
38843903
run: pnpm install --frozen-lockfile
38853904

3905+
# The spec liveness ledger cites the renderer in-repo, under ui/
3906+
# (objectstack#26150), so a ui/ change can delete a file a ledger row
3907+
# cites, move a cited line past the end of its file, drop an anchored
3908+
# symbol or stop naming the cited key. Spec Liveness Check does not run on
3909+
# a ui/-only pull request (its `paths:` are the spec side) and is not a
3910+
# required context, so the gate runs here too, where every ui/ change
3911+
# starts it (objectstack#25712 F26). It runs tsx over spec's sources and
3912+
# needs the install alone; about 3 s.
3913+
- name: Spec Liveness Check — Check the liveness ledger's citations, ui/ included
3914+
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
3915+
run: pnpm --filter @objectstack/spec check:liveness
3916+
38863917
- name: Build the @objectstack packages objectui consumes
38873918
run: |
38883919
pnpm exec turbo run build --concurrency=4 \

0 commit comments

Comments
 (0)