Skip to content

Commit e65e154

Browse files
committed
Merge origin/main (post #3049 squash) into the stdio-gate branch
#3049's squash (4f8c2d1) is content-identical to this branch's base commit, so this merge only rebases the PR surface — the three-dot diff vs main now shows the #3055 gate alone. # Conflicts: # docs/adr/0097-declarative-connector-instances.md # packages/connectors/connector-mcp/src/mcp-provider.test.ts # packages/connectors/connector-mcp/src/mcp-provider.ts
2 parents 976a46d + 4f8c2d1 commit e65e154

65 files changed

Lines changed: 2473 additions & 145 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.

.changeset/console-fb35e4828fdb.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
"@objectstack/console": minor
3+
---
4+
5+
Console (objectui) refreshed to `fb35e4828fdb`. Frontend changes in this range:
6+
7+
- fix(data-objectstack): emit MutationEvents from batchTransaction and bulk so master-detail saves refresh bound views (#2584)
8+
- feat(dashboard-filters): #2578 item-5 enhancements — nested variable merging, metadata-aware default bindings, server-side optionsFrom distinct (#2590)
9+
- feat(fields+form+detail): file/image upload cells in inline line-item grids (#2360) (#2585)
10+
- feat(app-shell): visual filterBindings editor in the dashboard widget inspector (#2578) (#2586)
11+
- fix(detail): highlight strip lookup editor honors ObjectStack `reference` key (#2407) (#2587)
12+
- fix(app-shell): guard Studio Access pillar against silently discarding unsaved matrix edits (#2588)
13+
- feat(dashboard-filters): #2578 follow-ups — catalog examples, guide tutorial, i18n entries, spec-alignment cleanup (#2581)
14+
- fix(detail+fields+app-shell): ADR-0085 #2548 follow-ups — strip title dedupe, group icon/description, currency channel, approvals Bearer (#2577)
15+
- feat(dashboard): dashboard-level filters driving multiple charts (framework#2501) (#2576)
16+
- feat(page-header): metadata-driven multi-button record header (#2361) (#2574)
17+
18+
objectui range: `092bd859934f...fb35e4828fdb`
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/cli': minor
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
feat(cli): `os i18n extract` now emits action param keys (`o.<object>._actions.<action>.params.<param>.*`) so action-dialog forms are translatable (#3030)
7+
8+
The console client already resolves param labels, help text, placeholders and
9+
option labels from `o.<object>._actions.<action>.params.*`, but the extractor
10+
never walked `actions[].params`, so those keys were absent from generated
11+
bundles and dialogs like Setup → Create User rendered raw English under any
12+
locale. The extractor now emits:
13+
14+
- inline params → `label` / `helpText` / `placeholder` / `options.<value>`;
15+
- field-backed params (`{ field: '…' }`) → only when they carry a literal
16+
override (field translations already cover them at runtime);
17+
- both object actions and top-level (global) actions.
18+
19+
`@objectstack/platform-objects` regenerates its en/zh-CN/ja-JP/es-ES bundles
20+
with the new keys filled (user admin actions, sys_jwks fields, page variable
21+
forms). Re-running extract with `--merge` stays idempotent.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
'@objectstack/plugin-security': minor
4+
---
5+
6+
OWD posture is now enforced on the runtime write path (#3050). `metadata-protocol` gains the ADR-0094-addendum `registerAuthoringGate(type, gate)` seam — an awaited, throwing pre-persistence hook inside `saveMetaItem` (draft and publish-mode saves; environment writes only). `plugin-security` registers the `object` posture gate on it: an environment overlay of a packaged object may only TIGHTEN `sharingModel`/`externalSharingModel` (ADR-0086 D1 — closes the `OS_METADATA_WRITABLE=object` unvalidated-widening hole), and `externalSharingModel ≤ sharingModel` (ADR-0090 D11) is now rejected at save time instead of only by CLI lint. Write-path only — stored metadata keeps loading unchanged.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
'@objectstack/objectql': patch
4+
---
5+
6+
fix(security): exempt engine referential FK clears from the owner_id transfer guard (#3023)
7+
8+
Follow-up to the #3004 ownership-anchor guard. `owner_id` is a lookup to `sys_user`
9+
with the default `deleteBehavior: 'set_null'`, so deleting a `sys_user` makes
10+
`cascadeDeleteRelations` null `owner_id` on every dependent row. That cascade write
11+
re-entered the write middleware under the deleter's context, where the #3004 guard
12+
read the `owner_id = null` as a user-initiated disown and denied it — aborting the
13+
cascade mid-way (no transaction, so partial state) for any deleter without the
14+
transfer grant on the child object (e.g. a member clearing a `public_read_write`
15+
child that RLS would otherwise have allowed).
16+
17+
The cascade FK clear is engine-mandated referential integrity consequent to an
18+
already-authorized parent delete, not a user ownership change. `cascadeDeleteRelations`
19+
now tags the `set_null` write with a server-derived `__referentialFieldClear` context
20+
marker (set by the engine, never built from a request — same trust model as
21+
`__expandRead`), and the ownership-anchor guard skips when that marker is present.
22+
Ordinary user writes are unaffected; the marker cannot be forged from client input,
23+
so it can never slip a real ownership transfer past the guard.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
---
4+
5+
Localize activity summaries to the workspace default locale (#3039). Activity
6+
writers previously hardcoded English verbs and the object API name
7+
(`Created person_qualification "OC-00001"`). The writer now resolves the
8+
ADR-0053 `localization.locale` setting per write (memoized per tenant/user
9+
scope), renders the verb through new `messages.activityCreated/Updated/Deleted`
10+
i18n templates (en, zh-CN, ja-JP, es-ES shipped), and names the object by its
11+
localized label (`objects.{name}.label`) with fallback to the authored def
12+
label, then the API name. Missing i18n/settings services or bundle keys
13+
degrade to the previous English summaries.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(security): scope the bulk-write predicate guard to the caller's own filter, and dedupe pre-image reads (#3018 review follow-ups)
6+
7+
Two hardening follow-ups from the #3018 adversarial review.
8+
9+
**Predicate guard is now middleware-order-independent for writes.** #2982 made bulk
10+
`update`/`delete` carry an `opCtx.ast`, which brought them under the step-2.9
11+
anti-oracle predicate guard for the first time. That guard is documented to run
12+
against the *caller's own* predicate — RLS / sharing filters legitimately reference
13+
fields the caller cannot read (e.g. `owner_id`). But for a bulk write it inspected
14+
`opCtx.ast.where`, which a sibling middleware (`plugin-sharing`) may have already had
15+
an `owner_id` owner-match composed into — and the two middlewares' registration order
16+
is not contractually guaranteed. On an object whose `owner_id` is FLS-hidden, that
17+
could 403 a legitimate bulk write purely because the injected filter named the field.
18+
The guard now inspects `opCtx.options.where` (the caller's untouched predicate) for
19+
`update`/`delete`, so it can never mistake an injected owner/RLS filter for a caller
20+
probe, independent of middleware order. Reads are unchanged (the read seed is the
21+
caller's query verbatim and the guard runs before this middleware's own injection).
22+
23+
**Pre-image reads deduplicated.** The by-id "read the target row" pattern was inlined
24+
at ~5 gates with slightly divergent shapes; a single `readRowById` helper (fail-closed:
25+
missing engine / null id / thrown read → `null`, which always denies) now backs the
26+
provenance gates, and a memoized `getCallerPreImage` collapses the owner-anchor echo
27+
check (3.5) and the RLS `check` post-image (3.6) — which read the identical
28+
`(object, id, caller-context)` row — into one read per operation. No behavior change;
29+
the read shape can no longer drift across sites.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
'@objectstack/rest': patch
4+
'@objectstack/spec': patch
5+
---
6+
7+
fix(security): public-form submissions can no longer forge server-managed anchors (#3022)
8+
9+
The anonymous public-form surface (ADR-0056 Option A, `POST /forms/:slug/submit`)
10+
is authorized by the declaration-derived `publicFormGrant`, which short-circuits
11+
the security middleware BEFORE every write gate (CRUD, FLS, the owner anchor
12+
guard, the tenant CHECK). The only field-side defense was the route's
13+
declared-field allow-list — and a FormView with zero declared section fields
14+
fell back to merging the raw body wholesale, so an unauthenticated visitor
15+
could `POST owner_id=<victim>` (or `organization_id`, audit columns, `id`) and
16+
attach the record to another user or tenant — the #3004 insert-forge, with no
17+
credentials at all.
18+
19+
Server-managed anchors are now enforced on this surface at BOTH layers, from a
20+
single shared definition (`PUBLIC_FORM_SERVER_MANAGED_FIELDS`, new in
21+
`@objectstack/spec/security`):
22+
23+
- **Data layer (authoritative)** — the `publicFormGrant` branch in
24+
`@objectstack/plugin-security` strips `id` / `owner_id` / `organization_id` /
25+
`tenant_id` / audit columns / soft-delete state / `__search` from every row
26+
of a granted insert (batch included) before admitting the write, so the
27+
boundary holds no matter what any route lets through. Ownership stays NULL
28+
for object hooks / the first-admin bootstrap to assign, as for other
29+
anonymous-seeded rows.
30+
- **Route layer** — the submit allow-list excludes the same set
31+
unconditionally: an explicitly declared `owner_id` section field no longer
32+
passes, and the zero-declared-sections fallback keeps its documented
33+
all-fields behavior for business columns while refusing the managed set.
34+
The resolve route (`GET /forms/:slug`) drops the managed fields from the
35+
rendered sections and the embedded object schema so a form never collects a
36+
value the submit refuses, and `GET /forms/:slug/lookup/:field` refuses a
37+
`publicPicker` declared on a managed anchor (which would have opened
38+
anonymous `sys_user` search through `owner_id`).
39+
40+
Authenticated writes are unaffected — this is the anonymous-surface rule only;
41+
`owner_id` transfer semantics for signed-in callers stay governed by the
42+
transfer grant (#3004 / PR #3018).
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/cli': minor
4+
---
5+
6+
feat(spec,cli): enroll `view` in the liveness ledger (#2998 Track B)
7+
8+
`view` joins the `GOVERNED` set of the spec property-liveness gate — the
9+
rollout gap that let the objectui#1763/#2545 class of renderer/spec key drift
10+
survive undetected. New `packages/spec/liveness/view.json` classifies all 83
11+
walkable properties (75 ledger entries + framework overlay fields): the `list`
12+
and `form` containers are drilled one level via `children`.
13+
14+
Seeded from the 2026-06 viewschema audit and **re-verified against objectui
15+
HEAD** — four audit-era DEAD findings had since gone live and are classified
16+
from current reads (`form.submitBehavior`, `list.sharing.lockedBy`, list-path
17+
`ViewData` providers, and the post-ADR-0021 `list.chart` dataset shape — the
18+
audit's "chart renderers never migrated" headline is resolved). Final tally:
19+
68 live, 2 experimental (`form.buttons`/`form.defaults`, #2998 Track A
20+
awaiting objectui#2545), 5 dead (`list.responsive`, `list.performance`,
21+
`form.data`, `form.defaultSort`, `form.aria`). All misleading dead props
22+
carry `authorWarn` + `authorHint`.
23+
24+
The CLI's compile-time liveness lint gains `view` coverage
25+
(`TYPE_COLLECTIONS` + view containers labelled by `object`), so authoring a
26+
dead prop — e.g. a spec-valid `chart` list view that renders empty — now warns
27+
at `os build` with a corrective hint.

.objectui-sha

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
092bd859934f7bbc2654510aef912062c4c4f3a8
1+
fb35e4828fdb3b6ecc23761a82e9ec4a13890727

content/docs/data-modeling/queries.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,17 @@ the expanded filter; `fields` semantics, `searchableFields`, and drivers are unc
380380
(ADR-0097). Relevance ranking and typo tolerance remain Tier-2 (native FTS) and are not
381381
part of this.
382382

383+
**Coverage** — every object with a resolvable display/name field gets the companion,
384+
**including `sys_user`**: the people picker (which sends a plain `$search` against
385+
`sys_user`) finds users by pinyin with zero per-object or per-field configuration. Users
386+
created through any write path — sign-up, admin rename, engine writes — are searchable
387+
immediately.
388+
389+
**Pre-existing rows** are reconciled automatically: the first boot after the switch turns
390+
on runs a paged, idempotent backfill over every object that carries the companion column.
391+
For bulk imports that bypassed write hooks at runtime, `rebuildSearchCompanion` (exported
392+
by `@objectstack/plugin-pinyin-search`) recomputes the column on demand.
393+
383394
---
384395

385396
## Window Functions

0 commit comments

Comments
 (0)