Skip to content

Commit f7cb52d

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24738-cors-default-no-foreign-credentialed-origin
2 parents 397622f + 46bb5e3 commit f7cb52d

36 files changed

Lines changed: 1795 additions & 453 deletions

‎.changeset/24341-identity-objects-default-row-scope.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ feat(platform-objects)!: the platform identity objects declare their default row
77

88
Clause-②: no (narrowing)
99

10-
Twenty-three platform objects now declare `defaultRowScope`, each with the row policy the shipped permission sets of `@objectstack/plugin-security` carry for it, moved verbatim: the seven SCIM projections (`sys_scim_user`, `sys_scim_subject`, `sys_scim_projection_grant`, `sys_scim_identity_tombstone`, `sys_scim_group`, `sys_scim_group_member`, `sys_scim_connection_binding`), `sys_verification` and `sys_jwks`, `sys_organization`, `sys_user`, `sys_session`, `sys_account`, `sys_team_member`, `sys_two_factor`, `sys_user_preference`, `sys_api_key`, `sys_device_code`, `sys_oauth_access_token`, `sys_oauth_refresh_token`, `sys_oauth_consent`, `sys_oauth_application` (`@objectstack/platform-objects`) and `sys_audit_log` (`@objectstack/plugin-audit`, which owns the ledger). `sys_user` is the one merge: the key holds one policy, so its two shipped policies are joined into `id == current_user.id || id in current_user.org_user_ids`, a `select` policy. The platform-global objects (`sys_metadata*`, `sys_job*`, `sys_migration*`, `sys_platform_setting`, `sys_presence`, `sys_flow_dispatch`) declare none.
10+
Twenty-three platform objects now declare `defaultRowScope`, each with the row policy the shipped permission sets of `@objectstack/plugin-security` carried for it, moved verbatim: the seven SCIM projections (`sys_scim_user`, `sys_scim_subject`, `sys_scim_projection_grant`, `sys_scim_identity_tombstone`, `sys_scim_group`, `sys_scim_group_member`, `sys_scim_connection_binding`), `sys_verification` and `sys_jwks`, `sys_organization`, `sys_user`, `sys_session`, `sys_account`, `sys_team_member`, `sys_two_factor`, `sys_user_preference`, `sys_api_key`, `sys_device_code`, `sys_oauth_access_token`, `sys_oauth_refresh_token`, `sys_oauth_consent`, `sys_oauth_application` (`@objectstack/platform-objects`) and `sys_audit_log` (`@objectstack/plugin-audit`, which owns the ledger). `sys_user` is the one merge: the key holds one policy, so its two shipped policies are joined into `id == current_user.id || id in current_user.org_user_ids`, a `select` policy. The platform-global objects (`sys_metadata*`, `sys_job*`, `sys_migration*`, `sys_platform_setting`, `sys_presence`, `sys_flow_dispatch`) declare none.
1111

12-
The security plugin's row-policy collection applies an object's declared default when the permission sets the caller holds carry no row policy for that object and operation. The shipped sets still carry the same policies, so a holder of them reads exactly what it read before.
12+
The security plugin's row-policy collection applies an object's declared default when the permission sets the caller holds carry no row policy for that object and operation. On each object and operation the shipped sets either still carry the same policy or, where they no longer carry a copy (the `@objectstack/plugin-security` entry names the thirteen), collect the object's identical default, so a holder of them reads exactly what it read before.
1313

1414
**BREAKING — what moves for consumers.** This narrows what a principal reads on these objects when its sets carry no row policy for them, shipped as `minor` under the launch-window convention for accept-set narrowings.
1515

@@ -19,7 +19,7 @@ The security plugin's row-policy collection applies an object's declared default
1919

2020
**Unchanged.**
2121

22-
- A deployment with the platform baseline (`member_default`, the default) serves every human exactly what it served before, on every one of the 23 objects and every operation: each human holds the baseline, and the baseline carries a policy for each object and operation. Measured on a booted composition with a member, a viewer, an organisation admin, a user in no organisation and the platform administrator, before and after the declarations.
22+
- A deployment with the platform baseline (`member_default`, the default) serves every human exactly what it served before, on every one of the 23 objects and every operation: each human holds the baseline, and on each object and operation the baseline either carries a policy or collects the object's identical default. Measured on a booted composition with a member, a viewer, an organisation admin, a user in no organisation and the platform administrator, before and after the declarations.
2323
- An MCP agent acting for a user reads the same rows, and gets the same explain output, as before. Its ceiling set carries no row policy, and the security plugin does not collect an object's declared default on a delegated principal's own side of the intersection (see the `@objectstack/plugin-security` entry), so the agent's rows stay bounded by its delegator's side alone. Measured on a booted composition, before and after the declarations, for the agents of a member, a viewer, an organisation admin and the platform administrator; the platform administrator's agent still reads every row.
2424
- The platform administrator's superuser bypass is decided before any row policy is collected.
2525
- No export, option or stored shape moves.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security): the shipped permission sets stop copying the row scope thirteen identity objects declare as their own default
6+
7+
Clause-②: no
8+
9+
`organization_admin` (and so `organization_admin_no_bypass`), `member_default` and `viewer_readonly` no longer carry their copies of the row policy that thirteen identity objects declare as their `defaultRowScope`. The thirteen are the seven SCIM projection tables (`sys_scim_user`, `sys_scim_subject`, `sys_scim_projection_grant`, `sys_scim_identity_tombstone`, `sys_scim_group`, `sys_scim_group_member`, `sys_scim_connection_binding`), `sys_verification` and `sys_jwks`, and the `select` self scope of `sys_account`, `sys_team_member`, `sys_oauth_access_token` and `sys_oauth_refresh_token`. Each copy was byte-identical to its object's declaration. The row-policy collection applies an object's declared default wherever the caller's held sets carry no policy for that object and operation, so every holder of the shipped sets reads exactly the rows it read before. That holds with the platform baseline and without it (`fallbackPermissionSet: null`), under the walled and the `single` posture, and for an MCP agent acting for a holder.
10+
11+
Explain now names the object's default (`via: 'object_default'`) where it named the set's policy on those thirteen objects.
12+
13+
**Kept in the sets.**
14+
15+
- `sys_user`'s two policies, and every `all`-spelled self scope (`sys_session`, `sys_two_factor`, `sys_user_preference`, `sys_api_key`, `sys_device_code`, `sys_oauth_consent`, `sys_oauth_application`, `sys_organization`). A member's `update` and `delete` collection holds the `'*'` ownership floor, so the object's default is never collected there, and these copies are what scope a member's writes to their own rows.
16+
- `sys_audit_log_org`. Where no organization wall is enforced, the platform's own tenant policies are stripped by provenance, and that provenance is read off these sets. The copy is what makes the ledger's identical default the platform's policy, so `single` keeps serving the ledger unscoped.
17+
18+
**If an application set authors a row policy for one of these thirteen objects**, that policy is used as authored and the object's default is not added beside it. Before, a holder who also held a shipped set had that set's copy OR-ed in, so an authored scope that left out the holder's own rows was widened back to them. Now the authored policy alone decides on that object and operation. Measured: no package or example in this repository authors such a policy.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
fix(rest): a `/data` create or update refused by a NOT NULL column names the field by its label, in the caller's language
6+
7+
Clause-②: no (message text only: the refusal's `status`, `code` and `fields[]` keys and codes do not move, and no accept set changes)
8+
9+
- **Before.** A field declared `storage: { notNull: true }` without `required` is refused by the database, not by the record validator. `PATCH /api/v1/data/OBJECT/ID` with `{ "unit_price": null }`, on a field labelled "Sales Price", answered `400 VALIDATION_FAILED` with `error` and `fields[0].message` both `"unit_price is required"`. The user saw the column key. The same field declared `required` was refused by the validator, by its label.
10+
- **Now.** `POST /data/:object` and `PATCH /data/:object/:id` name the field the way the record validator names its own `required` refusal: the catalog's `required` sentence, in the caller's locale, with the field's translated label, then its declared label. A `zh-CN` caller reads `销售价格不能为空`. For a field that declares `storage.notNull`, the `hint` says to send a value, or to omit the field when a default or a hook populates it. It no longer prescribes `required` or `os migrate`.
11+
- **Unchanged.** `status`, `code`, `fields[0].field` (the column key), `fields[0].code` (`required`), `object` and the key set. Drift keeps the drift hint: a field declared with neither `required` nor `storage.notNull` over a column that is still NOT NULL. A column the object does not declare keeps today's sentence. So does a refusal the driver attributes to another table, and any answer when the object or the i18n service cannot be read. The door makes the extra object read only for a NOT NULL refusal, and it is the by-name read the exposure gate already makes on every `/data` request.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/types': minor
3+
---
4+
5+
fix(types): a NOT NULL refusal's hint no longer tells the author to declare the field `required`, and the caller that holds the field map can name the field by its label
6+
7+
Clause-②: yes (widening: three additive exports, `notNullRefusalSite`, `relabelNotNullRefusal` and the type `NotNullRefusalSite`, consumed by `@objectstack/rest`'s `/data` doors; no accept set, status or code moves, and the refusal's `fields[]` keys and codes do not move)
8+
9+
- **The hint holds for both ways in.** `mapDataError` classifies a driver's NOT NULL refusal from the driver's text, which names the column and nothing else. Its hint used to lead with "provide it, or declare the field `required` in the object metadata". For a field declared `storage: { notNull: true }` without `required` (ADR-0113's engine-populated column, filled by a hook or a default), declaring `required` is the move ADR-0113 D1 / D2 rule out: it puts the demand back on the form. The hint now leads with "requires a value: provide one", and names drift (`os migrate`) only as the case where the object declares neither `required` nor `storage.notNull`.
10+
- **`notNullRefusalSite(answer)` and `relabelNotNullRefusal(answer, { message, declaresNotNull })`.** The first answers the column, and the table where the dialect names one (SQLite, Postgres), when `answer` is the NOT NULL refusal `mapDataError` built. It answers `undefined` for every other answer, including a record validator's own `required` refusal with the same `code`. The second replaces `error` and `fields[0].message` with the caller's sentence. For a field that declares `storage.notNull`, it also replaces the hint with one that names neither `required` nor `os migrate`. Every other key and value is unchanged. The classification itself still reads no field map.

‎docs/qa/platform-checklist/RUNNER.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,12 @@ contradicts it, and correct it here when it does.
458458

459459
- **A walled boot is a fixture of its own: `os serve` boots `group` and `isolated` once the
460460
walled prerequisites are provisioned, and the stock showcase provisions none of them.**
461-
18.0 refused a walled posture at boot; 18.x step 4 (#24580) removed that refusal, so
461+
18.0.0 boots a walled posture: under ruling record 6282882793 on the v18 epic it is
462+
published only after 18.x step 4 (#24580) has merged, and step 4 deleted the boot refusal
463+
(`walledPostureRefusal()` in `packages/cli/src/commands/serve.ts`). Of the builds published
464+
so far, only the `18.0.0-next.5` prerelease carries that refusal, as `main` did from
465+
`ae6bd1a6` until step 4: `next.0` to `next.3` predate it, and no `next.4` was published.
466+
So on 18.0.0
462467
`OS_TENANCY_POSTURE=group` or `isolated` (or the legacy `OS_MULTI_ORG_ENABLED=true`, which
463468
`resolveTenancyPosture()` in `packages/types/src/env.ts` reads as `isolated`) boots through
464469
`os serve`, and so `os dev` and `os start`. Each prerequisite below refuses the boot with a

0 commit comments

Comments
 (0)