Skip to content

Commit 7d45c8e

Browse files
fix(plugin-security): the shipped sets stop copying the identity objects' declared row scopes (#24761)
Fixes #24484 Clause-②: no The follow-up that ruling E′ item 3 (comment 6250748677 on #24164) names, as #24340's body records it. `organization_admin` (and so the derived `organization_admin_no_bypass`), `member_default` and `viewer_readonly` no longer carry copies of the row scope that thirteen identity objects now declare as their own `defaultRowScope` (declared by #24341). The thirteen are the seven SCIM projection tables, `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`. The security plugin's collection applies an object's default wherever the caller's held sets carry no policy for that object and operation, so rows do not move. Explain now names the object's default (`via: 'object_default'`) where it named a set's copy, as triage (6261582931) recorded the ruling accepts. No schema, `via` value, object declaration, `core` code or `packages/spec` file changes. It runs after #24136 (landed as b27f8de), which edited the same file; this branch starts on the tree that holds it. ## Per-copy byte-identity readings (taken on `origin/main` 4a2ca57) Each shipped set's policy for each candidate object, compared field by field (`name`, `object`, `operation`, `using`, `check`, `positions`, `enabled`) with the object's declared `defaultRowScope` plus `object`. Sets read: `organization_admin`, `organization_admin_no_bypass`, `member_default`, `viewer_readonly`. | Family | Objects | Copies read | Identical | Disposition | |---|---|---|---|---| | SCIM | `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` | 28 | 28 | dropped | | credential | `sys_verification`, `sys_jwks` | 8 | 8 | dropped | | `_self`, `select` | `sys_account`, `sys_team_member`, `sys_oauth_access_token`, `sys_oauth_refresh_token` | 16 | 16 | dropped | | ledger | `sys_audit_log` (`sys_audit_log_org`) | 4 | 4 | **kept**, measured (below) | 56 of 56 copies were byte-identical. The only `'*'` policies in any shipped set are `member_default`'s `owner_only_writes` (`update`) and `owner_only_deletes` (`delete`), so after the drop each holder's `select` collection on the thirteen objects is empty and collects the default. On the head, the same reading finds only the four ledger copies left among the candidates, and the must-keep rows unchanged. ## What stays, and why - **Pointer 6280763190's must-keep list**, untouched: `sys_user_self` (`all`) in `member_default`, and every `all` 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` collections hold the `'*'` ownership floor, so the default is never collected there. Also kept, outside the pointer's may-drop list: `sys_user`'s two `select` policies in `organization_admin` and `viewer_readonly` (they differ from the merged `sys_user` declaration), and `viewer_readonly`'s `select` spellings of objects whose default is `all` (they differ by operation). - **`sys_audit_log_org`: kept, although it is byte-identical and on the pointer's may-drop list.** I measured it before dropping it. When no organization wall is enforced, `collectRLSPolicies` strips the platform's own tenant policies by provenance (`isPlatformTenantPolicy`). `platform-tenant-policies.ts` builds those provenance keys from the shipped sets' policies. With no set carrying the copy, the ledger's identical default is no longer recognised as the platform's. It survives the `single` strip as an authored tenant policy and narrows the ledger read. Ablation A3 below measures it under `single`: the viewer, both organization-admin variants and their agents went from all three ledger rows to one. `sys-audit-log-row-scope.test.ts` already pins the provenance half (the shipped copies are non-empty and each is a platform tenant policy). The helper's comment now says why the copy stays. ## Rows: the before/after served-ids run New pin: `packages/plugins/plugin-security/src/objects/shipped-sets-identity-default-row-scope.test.ts`, modelled on `default-row-scope-declarations.test.ts`. It runs on the real `SecurityPlugin` + `ObjectQL` + `SqlDriver`, with the real declarations and a security catalog of the shipped sets. It covers three deployment shapes: walled with the platform baseline, walled with **no** platform baseline (`fallbackPermissionSet: null`, each principal resolves only the set it names), and `single`. The personas are a `member_default` holder, a `viewer_readonly` holder, both organization-admin variants, a user in no organization, delegated MCP agents for the member, the viewer and the organization admin (ruling B, 6278784335), and the platform administrator as a control. Each persona's served ids are asserted against the ids written out per family, on all 14 touched objects. They are also asserted equal to what the same deployment serves with the dropped copies spread back into the sets, on those objects plus `sys_user` and the eight `all` objects. | Tree | Served-ids block (9 tests: 3 shapes x listed ids, copies restored, control) | After-state pins (explain, census, authored policy, no copy in any set) | |---|---|---| | `origin/main` 4a2ca57, sets unchanged | **9/9 pass** (plus the two keep pins and the private-object control) | 20 red, by design: the copies are still there | | head e12d958 | **9/9 pass** | all pass: 32/32 in the file | ## Explain moves, as ruled Pinned per family against the copies-restored tree: - SCIM: a member's record-grained explain of its own `sys_scim_user` row gives the rule `sys_scim_user_self` with `via: 'object_default'`. The tree with the copy gives `business_rls` and no `via`. The object-level read of `sys_scim_group` names `object default row scope 'sys_scim_group_none'` as its source, for all four set holders. - `_self`: the same for `sys_account` / `sys_account_self`. - credential: the object-level reads of `sys_verification` and `sys_jwks` name `sys_verification_none` / `sys_jwks_none`. The verdict is unchanged. - `sys_audit_log_org`: **not moved**, because the copy is kept (above). - Census: every human persona collects the object default on exactly the thirteen dropped objects' `select`, and on no other object or operation. With the copies restored, none. A delegated agent's own leg collects none (ruling B, whole). ## An authored policy still wins A member who also holds an application set authoring `sys_account` `id == null` reads no `sys_account` row, and its collection is exactly that policy. The control is the same member without the set, who reads its own row under the default. Measured change: on 4a2ca57 this holder read its own row, because `member_default`'s copy was OR-ed in. 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. This only ever narrows. No package or example in this tree authors a set policy on any of the thirteen objects (`git grep` over `packages`, `examples` and `apps`, tests excluded: 0 hits). ## Ablations (each pin red under the forbidden change, then restored) Mutations went through `scripts/ablation-replace.mjs`, which checks the anchor count, the replacement count and the blob change on disk. Restores used `git checkout HEAD -- PATH` and were proven by the blob hash equalling HEAD's and by an empty `git diff HEAD`. No build was involved: the pin imports the sets from source. All four ran on 48e8f6d. | Leg | Forbidden change | Red | Green, as expected | |---|---|---|---| | A1b | drop `member_default`'s `sys_oauth_application_self` (`all`) | keep pin (member write collection), census | served ids | | A2 | drop `member_default`'s `sys_user_self` (`all`) | keep pin (write collection), `sys_user_self` stays `all` | served ids | | A3 | drop `sys_audit_log_org` from all three sets | served ids under `single` (viewer, org admins and their agents: 3 ledger rows to 1), census, ledger-copy pin | served ids walled, both shapes | | A4b | re-add `sys_account_self` (`select`) to `member_default` | no-copy pin, `_self` explain pin, census, authored-policy pin | served ids | The first A1 and A4 attempts never ran. The tool refused both before any test, because their replacement overlapped their anchor. They were re-anchored as A1b and A4b. ## Also in this diff - **Existing pins restated** where they asserted the copies stay: `default-row-scope-declarations.test.ts` (the thirteen declare the literal written out and no set carries it; the behaviour-neutrality comparison covers the ten declarers the sets still carry; the census expects the thirteen `select` defaults), `object-default-row-scope.test.ts` (`sys_scim_user` moves out of its byte-for-byte block into a block showing the declaration is the scope), and `rbac-objects.test.ts` (the `member_default` roster). - **`private-credential-row-scope.test.ts`**: the header says where the scope now lives, and the `agent_x` persona (pointer 6280763190, last bullet) now has the shape its one producer emits: `userId` is the delegating member, `onBehalfOf.principalKind: 'human'`. `scim-projection-row-scope.test.ts` gets the same header correction, and its agent comment now states ruling B. - **Outside the claim's declared file surface, comment-only, declared here:** the 13 `packages/platform-objects/src/identity/*.object.ts` comments above each `defaultRowScope` said "the shipped sets still carry it". That is false after this change. Only `//` lines changed; no declaration byte moved, and the declaration pin reads each value. - **Deliberate correction of a pending release note:** `.changeset/24341-identity-objects-default-row-scope.md`. Three sentences stated that the shipped sets carry each policy, which this change makes false before that note ships. They now say each object's policy is either still carried or collected as the identical default. `check-empty-changeset` is red on purpose: its own text says a deliberate correction is confirmed on the PR, not routed around. **Please confirm this correction.** The other #24341 note sentences are about that change's own effect and stay true. - New changeset: `.changeset/24484-sets-stop-copying-identity-defaults.md`, `@objectstack/plugin-security` `patch`. ## Tests and gates (head e12d958) - `pnpm --filter @objectstack/plugin-security test`: 191 files, 3926 passed, 45 skipped, exit 0. - `pnpm --filter @objectstack/plugin-security typecheck` (tsc, scripts, test layer): exit 0. File-scoped `eslint --no-inline-config` on the seven touched plugin-security files: exit 0. - `pnpm --filter @objectstack/platform-objects test`: 1195 passed. `typecheck`: exit 0. - Closure build `pnpm --filter '@objectstack/plugin-security^...' build`: exit 0, run on the merged tree. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 67 families. Each was run locally with its exit captured before any pipe. 64 exited 0. `check-empty-changeset --base origin/main` exited 1: the deliberate correction above. `check:dual-build-cjs-loads` and `check:i18n` exited 3, PREREQUISITE NOT MET (they need a whole-repo build), so they are NOT MEASURED here and CI's `Lint & Repo Gates` measures them. `--ran` reconciliation: 67 accounted for, 65 run, 2 NOT MEASURED, 0 UNRUN. - `test:repo` was not run locally; CI's `Test Core` runs it. ## Acceptance notes - **Provenance reads the sets, not the declarations.** `platform-tenant-policies.ts` keys "this is the platform's tenant policy" off the shipped sets. The ledger's copy therefore cannot go until provenance also recognises an object's declared default. Dormant today, because the copy stays. Carrier: none. - Agents on `private` objects: a read ceiling's `'*'` does not reach `sys_verification`, `sys_jwks`, `sys_oauth_access_token` or `sys_oauth_refresh_token`, so the agent personas are refused there before any row scope applies. That is identical on both trees, and the pin lists it. --- _Generated by [Claude Code](https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 74f7815 commit 7d45c8e

22 files changed

Lines changed: 850 additions & 385 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.

‎packages/platform-objects/src/identity/sys-account.object.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,13 @@ export const SysAccount = ObjectSchema.create({
115115
},
116116

117117
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
118-
// plugin-security's shipped permission sets spread for it today (the `_self`
119-
// carve-outs in `default-permission-sets.ts`), moved here verbatim. Layer 0 is inert
120-
// on this table (no organization column), so Layer 1 silence means this scope, not no
121-
// filter. The security plugin's row-policy collection applies it when no set the
122-
// caller holds carries a row policy for the object and operation; the shipped sets
123-
// still carry it, so their holders read as before. An `extend` contribution cannot set
124-
// or loosen it (refused at registration).
118+
// plugin-security's shipped permission sets used to spread for it, moved here
119+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
120+
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
121+
// this scope, not no filter. The security plugin's row-policy collection applies it
122+
// when no set the caller holds carries a row policy for the object and operation —
123+
// so to every shipped set's holder, whose reads and explain name this scope. An
124+
// `extend` contribution cannot set or loosen it (refused at registration).
125125
defaultRowScope: {
126126
name: 'sys_account_self',
127127
operation: 'select',

‎packages/platform-objects/src/identity/sys-jwks.object.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ export const SysJwks = ObjectSchema.create({
4040
highlightFields: ['id', 'created_at', 'expires_at'],
4141

4242
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
43-
// plugin-security's shipped permission sets spread for it today
44-
// (`privateCredentialRowScope` in `default-permission-sets.ts`), moved here verbatim.
43+
// plugin-security's shipped permission sets used to spread for it, moved here
44+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
4545
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
4646
// this scope, not no filter. The security plugin's row-policy collection applies it
47-
// when no set the caller holds carries a row policy for the object and operation; the
48-
// shipped sets still carry it, so their holders read as before. An `extend`
49-
// contribution cannot set or loosen it (refused at registration).
47+
// when no set the caller holds carries a row policy for the object and operation —
48+
// so to every shipped set's holder, whose reads and explain name this scope. An
49+
// `extend` contribution cannot set or loosen it (refused at registration).
5050
defaultRowScope: {
5151
name: 'sys_jwks_none',
5252
operation: 'select',

‎packages/platform-objects/src/identity/sys-oauth-access-token.object.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ export const SysOauthAccessToken = ObjectSchema.create({
3939
highlightFields: ['client_id', 'user_id', 'expires_at'],
4040

4141
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
42-
// plugin-security's shipped permission sets spread for it today (the `_self`
43-
// carve-outs in `default-permission-sets.ts`), moved here verbatim. Layer 0 is inert
44-
// on this table (no organization column), so Layer 1 silence means this scope, not no
45-
// filter. The security plugin's row-policy collection applies it when no set the
46-
// caller holds carries a row policy for the object and operation; the shipped sets
47-
// still carry it, so their holders read as before. An `extend` contribution cannot set
48-
// or loosen it (refused at registration).
42+
// plugin-security's shipped permission sets used to spread for it, moved here
43+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
44+
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
45+
// this scope, not no filter. The security plugin's row-policy collection applies it
46+
// when no set the caller holds carries a row policy for the object and operation —
47+
// so to every shipped set's holder, whose reads and explain name this scope. An
48+
// `extend` contribution cannot set or loosen it (refused at registration).
4949
defaultRowScope: {
5050
name: 'sys_oauth_access_token_self',
5151
operation: 'select',

‎packages/platform-objects/src/identity/sys-oauth-refresh-token.object.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ export const SysOauthRefreshToken = ObjectSchema.create({
3838
highlightFields: ['client_id', 'user_id', 'expires_at'],
3939

4040
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
41-
// plugin-security's shipped permission sets spread for it today (the `_self`
42-
// carve-outs in `default-permission-sets.ts`), moved here verbatim. Layer 0 is inert
43-
// on this table (no organization column), so Layer 1 silence means this scope, not no
44-
// filter. The security plugin's row-policy collection applies it when no set the
45-
// caller holds carries a row policy for the object and operation; the shipped sets
46-
// still carry it, so their holders read as before. An `extend` contribution cannot set
47-
// or loosen it (refused at registration).
41+
// plugin-security's shipped permission sets used to spread for it, moved here
42+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
43+
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
44+
// this scope, not no filter. The security plugin's row-policy collection applies it
45+
// when no set the caller holds carries a row policy for the object and operation —
46+
// so to every shipped set's holder, whose reads and explain name this scope. An
47+
// `extend` contribution cannot set or loosen it (refused at registration).
4848
defaultRowScope: {
4949
name: 'sys_oauth_refresh_token_self',
5050
operation: 'select',

‎packages/platform-objects/src/identity/sys-scim-connection-binding.object.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ export const SysScimConnectionBinding = ObjectSchema.create({
5252
},
5353

5454
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
55-
// plugin-security's shipped permission sets spread for it today
56-
// (`scimProjectionRowScope` in `default-permission-sets.ts`), moved here verbatim.
55+
// plugin-security's shipped permission sets used to spread for it, moved here
56+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
5757
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
5858
// this scope, not no filter. The security plugin's row-policy collection applies it
59-
// when no set the caller holds carries a row policy for the object and operation; the
60-
// shipped sets still carry it, so their holders read as before. An `extend`
61-
// contribution cannot set or loosen it (refused at registration).
59+
// when no set the caller holds carries a row policy for the object and operation —
60+
// so to every shipped set's holder, whose reads and explain name this scope. An
61+
// `extend` contribution cannot set or loosen it (refused at registration).
6262
defaultRowScope: {
6363
name: 'sys_scim_connection_binding_none',
6464
operation: 'select',

‎packages/platform-objects/src/identity/sys-scim-group-member.object.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ export const SysScimGroupMember = ObjectSchema.create({
5858
},
5959

6060
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
61-
// plugin-security's shipped permission sets spread for it today
62-
// (`scimProjectionRowScope` in `default-permission-sets.ts`), moved here verbatim.
61+
// plugin-security's shipped permission sets used to spread for it, moved here
62+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
6363
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
6464
// this scope, not no filter. The security plugin's row-policy collection applies it
65-
// when no set the caller holds carries a row policy for the object and operation; the
66-
// shipped sets still carry it, so their holders read as before. An `extend`
67-
// contribution cannot set or loosen it (refused at registration).
65+
// when no set the caller holds carries a row policy for the object and operation —
66+
// so to every shipped set's holder, whose reads and explain name this scope. An
67+
// `extend` contribution cannot set or loosen it (refused at registration).
6868
defaultRowScope: {
6969
name: 'sys_scim_group_member_none',
7070
operation: 'select',

‎packages/platform-objects/src/identity/sys-scim-group.object.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ export const SysScimGroup = ObjectSchema.create({
5151
},
5252

5353
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
54-
// plugin-security's shipped permission sets spread for it today
55-
// (`scimProjectionRowScope` in `default-permission-sets.ts`), moved here verbatim.
54+
// plugin-security's shipped permission sets used to spread for it, moved here
55+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
5656
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
5757
// this scope, not no filter. The security plugin's row-policy collection applies it
58-
// when no set the caller holds carries a row policy for the object and operation; the
59-
// shipped sets still carry it, so their holders read as before. An `extend`
60-
// contribution cannot set or loosen it (refused at registration).
58+
// when no set the caller holds carries a row policy for the object and operation —
59+
// so to every shipped set's holder, whose reads and explain name this scope. An
60+
// `extend` contribution cannot set or loosen it (refused at registration).
6161
defaultRowScope: {
6262
name: 'sys_scim_group_none',
6363
operation: 'select',

‎packages/platform-objects/src/identity/sys-scim-identity-tombstone.object.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ export const SysScimIdentityTombstone = ObjectSchema.create({
4747
},
4848

4949
// [ADR-0095 D1, 2026-10-18 note] This object's default row scope: the one policy
50-
// plugin-security's shipped permission sets spread for it today
51-
// (`scimProjectionRowScope` in `default-permission-sets.ts`), moved here verbatim.
50+
// plugin-security's shipped permission sets used to spread for it, moved here
51+
// verbatim; the sets no longer carry a copy (#24164 ruling E′ item 3's follow-up).
5252
// Layer 0 is inert on this table (no organization column), so Layer 1 silence means
5353
// this scope, not no filter. The security plugin's row-policy collection applies it
54-
// when no set the caller holds carries a row policy for the object and operation; the
55-
// shipped sets still carry it, so their holders read as before. An `extend`
56-
// contribution cannot set or loosen it (refused at registration).
54+
// when no set the caller holds carries a row policy for the object and operation —
55+
// so to every shipped set's holder, whose reads and explain name this scope. An
56+
// `extend` contribution cannot set or loosen it (refused at registration).
5757
defaultRowScope: {
5858
name: 'sys_scim_identity_tombstone_self',
5959
operation: 'select',

0 commit comments

Comments
 (0)