diff --git a/.changeset/sys-user-role-help-posture-split.md b/.changeset/sys-user-role-help-posture-split.md new file mode 100644 index 00000000000..382107a4bec --- /dev/null +++ b/.changeset/sys-user-role-help-posture-split.md @@ -0,0 +1,18 @@ +--- +"@objectstack/platform-objects": patch +--- + +fix(platform-objects): `sys_user.role`'s help text names the platform-admin route that works on every tenancy posture, and keeps the unscoped grant `single`-only (#19875) + +The `role` field's `description` is authored metadata: it ships in the published bundle, is extracted into the `en` i18n bundle, and is the help text an administrator reads on the field in Setup. It said: + +> Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2) — grant platform-admin standing with an unscoped `admin_full_access` assignment in `sys_user_permission_set`. + +On a walled deployment (`OS_TENANCY_POSTURE` `group` or `isolated`) that remedy does nothing: since the walled legacy-anchor retirement, an unscoped `admin_full_access` row no longer confers platform-admin standing there. The only route there is the deployment's configured administrator list. The help text now reads: + +> Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2). To grant platform-admin standing, list the user's verified email in `OS_PLATFORM_OWNER_EMAIL`; under the `single` tenancy posture an unscoped `admin_full_access` assignment in `sys_user_permission_set` also confers it. + +This describes both anchors as they work today. The configured, email-verified address confers standing on every posture. The unscoped grant row still confers it under `single`, and nowhere else. No behaviour changes: this is a text correction only. + +- **`en.objects.generated.ts`** follows by regeneration (`pnpm i18n:extract`), not by hand. +- **The existing pin** on this description (`platform-objects.test.ts`) now also requires the text to name `OS_PLATFORM_OWNER_EMAIL` and to qualify the grant with `single`. Without that, restoring the unqualified sentence would pass every test. diff --git a/packages/platform-objects/src/apps/translations/en.objects.generated.ts b/packages/platform-objects/src/apps/translations/en.objects.generated.ts index 2424c705bc4..e5ef3ada4fb 100644 --- a/packages/platform-objects/src/apps/translations/en.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.objects.generated.ts @@ -35,7 +35,7 @@ export const enObjects: NonNullable = { }, role: { label: "Platform Role", - help: "Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2) — grant platform-admin standing with an unscoped `admin_full_access` assignment in `sys_user_permission_set`." + help: "Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2). To grant platform-admin standing, list the user's verified email in `OS_PLATFORM_OWNER_EMAIL`; under the `single` tenancy posture an unscoped `admin_full_access` assignment in `sys_user_permission_set` also confers it." }, banned: { label: "Banned", diff --git a/packages/platform-objects/src/identity/sys-user.object.ts b/packages/platform-objects/src/identity/sys-user.object.ts index 7f8718e29df..ca076aa169e 100644 --- a/packages/platform-objects/src/identity/sys-user.object.ts +++ b/packages/platform-objects/src/identity/sys-user.object.ts @@ -701,7 +701,7 @@ export const SysUser = ObjectSchema.create({ maxLength: 64, group: 'Admin', description: - 'Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2) — grant platform-admin standing with an unscoped `admin_full_access` assignment in `sys_user_permission_set`.', + 'Legacy better-auth role scalar (admin, user, …). ObjectStack no longer writes it (ADR-0068 D2). To grant platform-admin standing, list the user\'s verified email in `OS_PLATFORM_OWNER_EMAIL`; under the `single` tenancy posture an unscoped `admin_full_access` assignment in `sys_user_permission_set` also confers it.', }), banned: Field.boolean({ diff --git a/packages/platform-objects/src/platform-objects.test.ts b/packages/platform-objects/src/platform-objects.test.ts index ccaa6d83f9a..72709ae37dc 100644 --- a/packages/platform-objects/src/platform-objects.test.ts +++ b/packages/platform-objects/src/platform-objects.test.ts @@ -318,6 +318,13 @@ describe('@objectstack/platform-objects', () => { expect(role.description).not.toContain('Set Platform Role'); expect(role.description).toContain('sys_user_permission_set'); expect(role.description).toContain('admin_full_access'); + // #19875 — which route exists depends on the tenancy posture. Under a + // walled posture (`group` / `isolated`) the unscoped grant confers no + // platform-admin standing (#11663 L5), so the text must name the anchor + // that confers on EVERY posture and keep the grant clause `single`-only; + // an unqualified grant prescription silently does nothing on a walled rig. + expect(role.description).toContain('OS_PLATFORM_OWNER_EMAIL'); + expect(role.description).toContain('`single`'); }); });