Skip to content

Commit 91716b3

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/3278-retire-js-expression-dialect
# Conflicts: # content/docs/references/ui/app.mdx
2 parents f84d755 + 9dba636 commit 91716b3

331 files changed

Lines changed: 11674 additions & 2536 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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/runtime": patch
3+
---
4+
5+
**Extend the blessed `organizationId` org name to the action-body surface (follow-up to #3280).** Hooks now teach `ctx.user.organizationId` / `ctx.session.organizationId` as the blessed name for the caller's active org; action bodies — the sibling authoring surface that shares the same sandbox runner — were left behind: the REST dispatch path exposed only `ctx.user.tenantId` (the deprecated name) and no `ctx.session` at all, and the MCP `run_action` path exposed neither.
6+
7+
Both action-dispatch sites (`handleActions`, MCP `runAction`) now populate:
8+
9+
- **`ctx.user.organizationId`** — the blessed name (matches the `organization_id` column and `current_user.organizationId` in RLS); `ctx.user.tenantId` is kept as a deprecated alias with the identical value on the REST path.
10+
- **`ctx.session`** (`{ userId, organizationId, tenantId, roles? }`) — mirrors the hook `ctx.session` shape, `undefined` for a context-less / self-invoked call.
11+
12+
Action bodies execute trusted (the `ctx.engine` / `ctx.api` facade bypasses RLS/FLS), so a body that must scope by org has to read it from `ctx` — now under the same name a hook author uses. Additive and behavior-preserving; the objectstack-ui skill documents the action-body `ctx` and the `organizationId` read.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
ADR-0099 P0: land the probe-vs-carried-rung equivalence gate in the authz matrix (`authz-matrix-gate.test.ts`) — seeded-shape equivalence cells, two adversarial `KNOWN DIVERGENCE` pins (scoped `admin_full_access` grant; piecemeal platform-exclusive capability), the I2 nesting and I3 narrowing invariant cells, posture-blindness staging pins for the P1 flip, and the EXTERNAL dead-branch cell. Extracts the platform-admin capability probe as the exported pure `hasPlatformAdminCapability` (mechanical, behavior unchanged). Test-only gate; the ADR-0099 P1 flip lands behind it (#3211).
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
ADR-0099 P1 (#3211 M2): the Layer 0 cross-tenant exemption gate now reads the carried `ctx.posture` rung (#2956) as authoritative, with the platform-admin capability probe demoted to a fallback for resolver-less contexts (delegated-admin bridge, sharing service, `getReadFilter`). The read and write (insert/update post-image) tenant checks share one decision (`computeLayeredRlsFilter`), so they cannot drift. A probe↔rung disagreement logs a defect breadcrumb and enforces the narrower rung verdict.
6+
7+
**Behavior change (security narrowing, multi-org / `@objectstack/organizations` only):** a principal whose carried rung is not `PLATFORM_ADMIN` no longer crosses the tenant wall on private / platform-global / better-auth-managed objects, even when its resolved permission sets carry a platform-exclusive capability. Two shapes are affected: (a) a **scoped** `admin_full_access` grant (`sys_user_permission_set.organization_id` non-null), and (b) a custom set granting a platform capability (e.g. `studio.access`) piecemeal alongside a superuser bit. Both are now walled to their own org — the fail-safe direction (the carried rung is a strict subset of the probe). Single-org / env-per-database deployments are unaffected (Layer 0 is inert).
8+
9+
**Upgrade check:** before upgrading, scan `sys_user_permission_set` for `admin_full_access` rows with a non-null `organization_id`, and custom permission sets whose `systemPermissions` intersect `{manage_metadata, manage_platform_settings, studio.access, manage_users}`. To restore cross-tenant operator access for such a principal, grant the **unscoped** `admin_full_access` instead. The `[authz/ADR-0099]` warn log names any principal hitting the divergence at runtime.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
ADR-0099 P2′ (#3211 M3′): pin the two-axis Amendment in the authz matrix. The original P2 (collapse the Layer 1 tier onto posture) was rejected — Layer 1's tier input is the per-object super-bit, a per-principal × per-object delegation primitive posture cannot represent. New cells pin: seeded-face agreement (seeded super-bit holders are already ≥ TENANT_ADMIN), the load-bearing delegation cell (a MEMBER with a delegated per-object `viewAllRecords`/`modifyAllRecords` short-circuits Layer 1 yet stays walled by Layer 0 — the auditor pattern), invariant I7 (the scope axis never crosses a boundary posture has not opened), and the contrast that the bit is a real grantable capability, not conditionally inert. Test-only; zero behavior change.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/platform-objects': minor
3+
---
4+
5+
feat(platform-objects): surface phone number in the create_user result dialog
6+
7+
`sys_user`'s `create_user` action now declares `user.phoneNumber` in its
8+
`resultDialog.fields`, so admins creating phone-based accounts see the
9+
sign-in phone number alongside the email and temporary password. The
10+
create-user response carries `phoneNumber` only for phone-based users;
11+
objectui's ActionResultDialog skips declared fields whose path is absent
12+
from the payload, so email-only users see no extra row.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
'@objectstack/rest': patch
3+
'@objectstack/spec': minor
4+
---
5+
6+
fix(rest): gate the cross-object transactional batch by the same per-object API rules as single-record writes (#1604)
7+
8+
The `POST {basePath}/batch` route (issue #1604 / ADR-0034) wraps N cross-object
9+
create/update/delete ops in one engine transaction, but it skipped the
10+
per-object API-exposure gate every single-record route applies — an
11+
authenticated caller could write to an `apiEnabled: false` object, or run an
12+
operation outside an object's `apiMethods` whitelist, straight through the batch
13+
surface (ADR-0049 / #1889 — the same "declared ≠ enforced" hole closed for the
14+
generic write path in #3220 / #3213).
15+
16+
The route now:
17+
18+
- validates the body against a new `CrossObjectBatchRequestSchema`
19+
(`@objectstack/spec/api`, Zod-First) — a malformed op, an unknown action, or a
20+
missing `object` is a `400` instead of a `500`;
21+
- enforces `enable.apiEnabled` / `enable.apiMethods` for **every** op (metadata
22+
fetched once, each distinct `(object, action)` checked) BEFORE opening the
23+
transaction — `404 OBJECT_API_DISABLED` / `405 OBJECT_API_METHOD_NOT_ALLOWED`;
24+
- requires an `id` for `update` / `delete` (`400`);
25+
- rejects an unresolvable `{ $ref }` with `400 BATCH_UNRESOLVED_REF` instead of
26+
silently writing a `null` FK;
27+
- rejects an explicit `atomic: false` (`400 BATCH_NOT_ATOMIC`) rather than
28+
silently applying atomically — non-atomic per-object batches stay on
29+
`POST /data/:object/batch`.
30+
31+
`enforceApiAccess` is refactored to share the pure `apiAccessDenialFromEnable`
32+
check + a `loadObjectItems` helper with the batch route (single-record behavior
33+
unchanged). Adds `rest-batch-endpoint.test.ts` — the REST-boundary coverage
34+
ADR-0034 flagged as missing (commit, `$ref`, rollback surfacing, API-access
35+
denial, request validation).
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/core': minor
3+
'@objectstack/service-analytics': minor
4+
---
5+
6+
feat(analytics): scope a datetime date-bucket drill to the reference-tz midnight instants (#1752 follow-up)
7+
8+
Closes the one gap left by the initial #1752 change: a `datetime` date dimension
9+
bucketed under a **non-UTC reference timezone** previously fell back to a superset
10+
drill (its bucket boundary is that tz's midnight *instant*, which `YYYY-MM-DD`
11+
calendar bounds can't express).
12+
13+
- **`@objectstack/core`** adds `zonedDateStartToUtcMs(ymd, tz)` — the UTC instant
14+
at which a calendar day begins in a reference timezone (the inverse of
15+
`calendarPartsInTz`). DST-safe: the offset is read from the platform tz
16+
database via `Intl`, with a two-pass resolution for the rare offset-boundary
17+
case; an unset/`'UTC'`/invalid zone returns plain UTC midnight.
18+
- **`@objectstack/service-analytics`** now emits `drillRanges` bounds per the
19+
field's temporal type (ADR-0053): a `datetime` field → ISO **instant** bounds
20+
at the reference tz's midnight (works under any tz, incl. DST); a `date` field
21+
`YYYY-MM-DD` calendar bounds (tz-naive, exact under any tz). An unknown field
22+
type is still emitted only under UTC and omitted (superset) under a non-UTC tz.
23+
24+
No objectui change is needed — the client already forwards whatever bound values
25+
the server sends into the drill filter and the `filter[field][gte|lt]` URL.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@objectstack/driver-sql": patch
3+
---
4+
5+
fix(driver-sql): drop the vestigial `sqlite3` peerDependency — the SQLite path uses `better-sqlite3` (#3277)
6+
7+
`package.json` advertised `peerDependencies.sqlite3: "^5.0.0"`, but the driver never
8+
loads `sqlite3` at runtime. Every first-party SQLite construction site builds a
9+
`client: 'better-sqlite3'` Knex driver (`resolveSqliteDriver` in
10+
`@objectstack/service-datasource`, the datasource driver factory, and the whole
11+
driver test suite), and the README already tells consumers to `pnpm add better-sqlite3`.
12+
`better-sqlite3` is auto-provided as an `optionalDependency` (with the native → wasm →
13+
memory step-down of #2229 covering a failed native build), so the SQLite requirement is
14+
already satisfied without the consumer installing anything.
15+
16+
The stale `sqlite3` peer only misled: a consumer resolving peer deps could `pnpm add
17+
sqlite3` (never used) while believing they'd satisfied the SQLite requirement. Removing
18+
it aligns the declared contract with the code and the docs. The `sqlite3` string alias
19+
still maps to `better-sqlite3` in the driver factory and dialect detection, so
20+
`driver: 'sqlite3'` config keeps working — it just resolves to `better-sqlite3` like
21+
everything else.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): declare `summaryOperations` sub-fields in the Field metadata form (#3257)
6+
7+
`fieldForm` (the registered metadata form for editing a Field) previously
8+
declared `summaryOperations` as a bare `composite` with no sub-fields, so a
9+
protocol-driven renderer had to fall back to a raw JSON editor. It now declares
10+
the inner shape explicitly — `object` (`ref:object`), `function` (select),
11+
`field`, `relationshipField`, and `filter` (bound to `widget: 'filter-condition'`)
12+
— mirroring the `summaryOperations` Zod schema and surfacing the roll-up `filter`
13+
added in #1868. Also gates the block to `data.type == 'summary'`.
14+
15+
Small step toward #3257 (making the Studio field designer metadata-driven rather
16+
than hand-coded); the live objectui inspector already edits these fields.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): honor a hook body's declared `timeoutMs` so nested cross-object writes aren't clamped to 250ms (#1867)
6+
7+
Hook bodies run in the QuickJS sandbox with a default 250ms timeout. The runner
8+
folded that engine default straight into `Math.min(...)` when resolving the
9+
effective timeout, so it *always* dominated for hooks: a body that declared a
10+
larger `timeoutMs` (the spec permits up to 30_000ms — `ScriptBody.timeoutMs`) to
11+
give a legitimate nested write — "when a child changes, update the parent" —
12+
room to settle was silently clamped back to 250ms and killed mid-flight. The
13+
declared knob was never enforced.
14+
15+
The engine default is now a FALLBACK used only when no explicit timeout is
16+
supplied, not a hard ceiling. An explicit `body.timeoutMs` (and/or an enclosing
17+
hook/action timeout) is honored; when both are present the smaller wins. Bodies
18+
that declare nothing still get the 250ms hook / 5000ms action default, and a
19+
body may still LOWER its own timeout below the default.
20+
21+
This clears the last reliability blocker for nested cross-object writes from
22+
hooks — the sandbox crash itself (`memory access out of bounds`) was already
23+
fixed by the deferred-promise host-call model — so header/rollup fields no
24+
longer need denormalized, hand-maintained workarounds.

0 commit comments

Comments
 (0)