Skip to content

Commit 1361ff6

Browse files
committed
wip: changeset, translations, retirement pin fixture (#23488)
Claude-Session: https://claude.ai/code/session_01LFqdRsuM5nkqoJdHd5ztVk Co-authored-by: Claude <noreply@anthropic.com>
1 parent 43c50c8 commit 1361ff6

6 files changed

Lines changed: 58 additions & 7 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/plugin-security': minor
4+
'@objectstack/core': patch
5+
'@objectstack/platform-objects': patch
6+
---
7+
8+
feat(spec)!: retire the delegated-admin scope's `manageBindings` and `authorEnvironmentSets` — rights no door honoured (#23488)
9+
10+
**BREAKING** — `manageBindings` and `authorEnvironmentSets` on a permission set's `adminScope` (`AdminScopeSchema`, ADR-0090 D12) are now refused at parse. They named the right to bind sets to positions and the right to author environment permission sets, and they gated the `sys_position_permission_set` and `sys_permission_set` rows, which ADR-0131 D3 retired. A position's sets are part of its definition and a permission set is a definition; both are written through the metadata door, which admits a `manage_metadata` holder and reads no `adminScope`. So a delegate who "may bind" or "may author" was granted nothing. Delete both keys: a delegate's scope is placement, assignment and the allowlist.
11+
12+
Clause-②: yes (narrowing)
13+
14+
Measured before removal, at `cdc42eb49`: no production author set either key to `true` (the showcase wrote the default `false`; the control key `manageAssignments: true` has four production authors), and no door that writes a position or a permission set consulted either key. Their one reader was the delegated-admin gate's strict-containment comparison, which changes below.
15+
16+
## FROM → TO
17+
18+
| you wrote (17.7 and earlier) | write instead |
19+
| --- | --- |
20+
| `adminScope: { …, manageBindings: true \| false }` | nothing — delete the key |
21+
| `adminScope: { …, authorEnvironmentSets: true \| false }` | nothing — delete the key |
22+
| a delegate who re-composes what a position distributes, or authors a set's body | a `manage_metadata` holder writes the position or set definition through the metadata door (ADR-0131 D3); the delegate assigns people to positions and grants allowlisted sets inside its subtree |
23+
| reading `scopes[].manageBindings` / `scopes[].authorEnvironmentSets` from `GET /api/v1/security/my-delegable-scope` (`DelegableAdminScope`) | nothing — the report carries `manageAssignments` as the scope's one action right |
24+
25+
**The one-line fix:** delete `manageBindings` and `authorEnvironmentSets` from every `adminScope`.
26+
27+
**What an author who still writes either sees.** `tsc` fails at the authoring site (`AdminScope` types both keys as retired), and the parse — `definePermissionSet()`, `defineStack({ permissions })`, `PUT /api/v1/meta/permission/:name` — refuses it at its own path with the prescription:
28+
29+
> `permission.adminScope.manageBindings` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0090 D12) — nothing honoured it once the position-to-permission-set binding rows it gated were retired: a position's sets are part of its definition (ADR-0131 D3), and that definition is written through the metadata door, which admits a `manage_metadata` holder and reads no adminScope. Delete the key; a delegate's scope is placement, assignment and the allowlist — `businessUnit` with `includeSubtree`, `manageAssignments`, and `assignablePermissionSets`. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.
30+
31+
## The containment change (`@objectstack/plugin-security`)
32+
33+
The delegated-admin gate judges a delegate who hands out a set that itself carries an `adminScope` — a `sys_user_position` assignment of a position that distributes it, or a direct `sys_user_permission_set` grant — by **strict containment**. That comparison read both retired flags. It now reads `includeSubtree`, `manageAssignments` and the allowlist only:
34+
35+
- a delegate whose own scope set either flag `true` can **no longer** hand out a scope otherwise EQUAL to its own — the inert flag used to count as the strictly-larger axis and let that lateral copy through the equal-scope refusal. It is refused `403 PERMISSION_DENIED` (`… would hand out an adminScope not strictly contained by yours`);
36+
- a granted scope that set either flag `true` no longer counts as out-reaching a delegate that did not.
37+
38+
`DelegatedAdminGate.describeDelegableScope()` (and so `GET /api/v1/security/my-delegable-scope`) no longer reports either flag.
39+
40+
## The retirement kit
41+
42+
- **Two `retiredKey()` tombstones** on `AdminScopeSchema`, a `strictObject` — so each refusal carries its prescription and `tsc` fails first. `RETIRED_KEYS_BY_MAJOR[18]`: `security/AdminScope:manageBindings`, `security/AdminScope:authorEnvironmentSets`. `DelegableAdminScope` (`@objectstack/spec/contracts`) drops both fields.
43+
- **The D2 conversion `permission-admin-scope-binding-authoring-removed`** (protocol 18, retired from the load path) deletes both keys from every `permissions[].adminScope`, one notice per key, whatever they hold. Both keys carried the default `false`, so a 17.x parse materialized them in every declared scope; the rehydration seams replay this conversion over stored `permission` rows and built artifacts, and `os migrate meta --stored` lists the rows that carry them before the upgrade.
44+
- **The D3 entry `permission-admin-scope-binding-authoring-retired`** records the containment change above: the judgement the strip cannot make for the upgrader.
45+
- **`@objectstack/core`:** `permissionSetDefinitionFromRow` — the mapping `os migrate`'s legacy-row ceremony saves a `sys_permission_set` row through — drops both keys from the row's `admin_scope`. That save goes through `saveMetaItem`, which replays no conversion: measured, a 17.x row's body answered `422 INVALID_METADATA` at `adminScope.manageBindings`, and the same body without the keys saved.
46+
- **`@objectstack/platform-objects`:** the permission-set form's `adminScope` help text (all four locales) names the scope's live keys only.
47+
- **No deprecation window**, per the project's startup-stage posture.
48+
49+
⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for. Studio's two switches for these keys retire in an objectui follow-up.
50+
51+
<!-- adr-0087: registered permission-admin-scope-binding-authoring-removed, permission-admin-scope-binding-authoring-retired -->

‎packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2323,7 +2323,7 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
23232323
},
23242324
adminScope: {
23252325
label: "Admin Scope",
2326-
helpText: "Delegated administration (ADR-0090 D12): lets holders of this set administer one business-unit subtree. businessUnit is the subtree root (sys_business_unit.name) and is required, non-blank; includeSubtree defaults to true; manageAssignments, manageBindings and authorEnvironmentSets default to false; a delegate may hand out only the sets named in assignablePermissionSets. Leave empty for a set that delegates nothing."
2326+
helpText: "Delegated administration (ADR-0090 D12): lets holders of this set administer one business-unit subtree. businessUnit is the subtree root (sys_business_unit.name) and is required, non-blank; includeSubtree defaults to true; manageAssignments defaults to false and lets the holder assign people to positions, and grant sets directly, inside that subtree; a delegate may hand out only the sets named in assignablePermissionSets. Leave empty for a set that delegates nothing."
23272327
},
23282328
objects: {
23292329
label: "Objects",

‎packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2323,7 +2323,7 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
23232323
},
23242324
adminScope: {
23252325
label: "Ámbito de administración delegada",
2326-
helpText: "Administración delegada (ADR-0090 D12): permite a quienes tienen este conjunto administrar un subárbol de unidades de negocio. businessUnit es la raíz del subárbol (sys_business_unit.name), es obligatorio y no puede estar en blanco; includeSubtree vale true por defecto; manageAssignments, manageBindings y authorEnvironmentSets valen false por defecto; un delegado solo puede asignar los conjuntos nombrados en assignablePermissionSets. Déjalo vacío en un conjunto que no delega nada."
2326+
helpText: "Administración delegada (ADR-0090 D12): permite a quienes tienen este conjunto administrar un subárbol de unidades de negocio. businessUnit es la raíz del subárbol (sys_business_unit.name), es obligatorio y no puede estar en blanco; includeSubtree vale true por defecto; manageAssignments vale false por defecto y permite al titular asignar personas a puestos, y conceder conjuntos directamente, dentro de ese subárbol; un delegado solo puede asignar los conjuntos nombrados en assignablePermissionSets. Déjalo vacío en un conjunto que no delega nada."
23272327
},
23282328
objects: {
23292329
label: "Permisos de objeto",

‎packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2323,7 +2323,7 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
23232323
},
23242324
adminScope: {
23252325
label: "委任管理スコープ",
2326-
helpText: "委任管理(ADR-0090 D12): この権限セットの保持者が 1 つのビジネスユニットのサブツリーを管理できるようにします。businessUnit はサブツリーのルート(sys_business_unit.name)で、必須かつ空白不可です。includeSubtree の既定値は true、manageAssignments・manageBindings・authorEnvironmentSets の既定値は false です。委任先が付与できるのは assignablePermissionSets に挙げた権限セットだけです。何も委任しない権限セットでは空のままにしてください。"
2326+
helpText: "委任管理(ADR-0090 D12): この権限セットの保持者が 1 つのビジネスユニットのサブツリーを管理できるようにします。businessUnit はサブツリーのルート(sys_business_unit.name)で、必須かつ空白不可です。includeSubtree の既定値は true です。manageAssignments の既定値は false で、有効にすると保持者はそのサブツリー内でユーザーをポジションに割り当て、権限セットを直接付与できます。委任先が付与できるのは assignablePermissionSets に挙げた権限セットだけです。何も委任しない権限セットでは空のままにしてください。"
23272327
},
23282328
objects: {
23292329
label: "オブジェクト権限",

‎packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2323,7 +2323,7 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
23232323
},
23242324
adminScope: {
23252325
label: "委派管理范围",
2326-
helpText: "委派管理(ADR-0090 D12):让持有本权限集的人管理一个业务单元子树。businessUnit 是子树的根(sys_business_unit.name),必填且不能为空白;includeSubtree 默认为 true;manageAssignments、manageBindings 和 authorEnvironmentSets 默认为 false;被委派者只能分配 assignablePermissionSets 中列出的权限集。不委派任何管理权的权限集请留空。"
2326+
helpText: "委派管理(ADR-0090 D12):让持有本权限集的人管理一个业务单元子树。businessUnit 是子树的根(sys_business_unit.name),必填且不能为空白;includeSubtree 默认为 true;manageAssignments 默认为 false,开启后持有人可在该子树内把人员分配到岗位、并直接授予权限集;被委派者只能分配 assignablePermissionSets 中列出的权限集。不委派任何管理权的权限集请留空。"
23272327
},
23282328
objects: {
23292329
label: "对象权限",

‎packages/spec/src/security/admin-scope-binding-authoring-retirement.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ const SCOPE = {
5757
assignablePermissionSets: ['sales_user'],
5858
} as const;
5959

60-
/** A well-formed permission set carrying {@link SCOPE}. */
61-
const SET = { name: 'east_admin', label: 'East Admin', adminScope: SCOPE } as const;
60+
/** A well-formed permission set carrying {@link SCOPE} (`objects` is the set's one required key). */
61+
const SET = { name: 'east_admin', label: 'East Admin', objects: {}, adminScope: SCOPE } as const;
6262

6363
// Unanchored, because a thrown `ZodError`'s message is the JSON of its issues;
6464
// the key-first house convention is asserted on the issue message itself.
@@ -212,7 +212,7 @@ describe('adminScope binding/authoring retirement — the D2 conversion', () =>
212212
{ ...SET, name: 'b_two', adminScope: { ...SCOPE, authorEnvironmentSets: 7 } },
213213
// Already canonical, and a set with no scope: no notice, handed back by reference.
214214
{ ...SET, name: 'c_three' },
215-
{ name: 'd_four', label: 'Four' },
215+
{ name: 'd_four', label: 'Four', objects: {} },
216216
],
217217
};
218218
const { stack, notices } = collectConversionNotices(input, { includeRetired: true });

0 commit comments

Comments
 (0)