Skip to content

delegated_admin can issue invitations but can no longer read them via the data API — the #8095 narrowing lands on owner/admin only #8240

Description

@os-zhuang

Recording a consequence of #8095's narrowing that the ruling did not decide either way. Measured, not inferred.

What changed and what it left behind

#8095 ruled that sys_invitation read goes to owner/admin, plus a row-scope carve-out for the addressee. The implementation states the admin admission as an RLS policy in organization_admin domained by positions to the two built-in org-administration identities:

positions: [BUILTIN_IDENTITY_ORG_OWNER, BUILTIN_IDENTITY_ORG_ADMIN] // 'org_owner', 'org_admin'

delegated_admin normalizes to neither. mapMembershipRole (packages/spec/src/identity/eval-user.zod.ts) maps only owner / admin / member, and unknown values pass through unchanged — so the role reaches current_user.positions as the literal delegated_admin, matching no listed identity. It also does not receive organization_admin from the auto-grant: auto-org-admin-grant.ts tests roles.includes('owner') || roles.includes('admin') against the comma-split list, and ['delegated_admin'] contains neither as an element.

Net effect: a delegated_admin reads only invitations addressed to their own email.

Measured

Stock showcase boot, single posture, four personas against GET /api/v1/data/sys_invitation with two invitations in the org:

owner 200 total: 2 (both rows, with role + inviter_id)
member 200 total: 0
invitee 200 total: 1 (their own row)
delegated_admin 200 total: 0

Why it may matter

delegated_admin exists precisely as the invitation-issuing principal that is not an org admin (ADR-0105 D8 / #3697: better-auth grants invitation: ["create"] to owner/admin only, and under a wall-enforcing posture those two are auto-elevated to tenant admins, so the delegated grade is the one caller the scope-bounded issuance path has). It can create invitations it then cannot list. better-auth's own list-invitations route is owner/admin-gated too, so there is no second path back.

Also note docs/qa/platform-checklist/areas/identity-auth.json (the ADR-0105 D8 item) has steps phrased as "as the delegated_admin ... capture the created sys_invitation row via the data API" and "immediately read sys_invitation via the API and verify NO row was left behind". Those reads now return nothing for that persona. The item's preferred lane — the pinned packages/qa/dogfood/test/delegated-admin-invite.dogfood.test.ts — is unaffected, because it reads through ql.find under a system context. Left alone rather than edited: whether the checklist wording or the permission changes depends on the decision below.

The decision

Either:

No option is being taken here; #8095's PR ships A by default (the status quo of the ruling as written).

Filed from the #8095 implementation. Not assigned.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions