diff --git a/apps/blocks/src/blocks/console-kit/console-kit-routes.ts b/apps/blocks/src/blocks/console-kit/console-kit-routes.ts index b03055b..651f5f8 100644 --- a/apps/blocks/src/blocks/console-kit/console-kit-routes.ts +++ b/apps/blocks/src/blocks/console-kit/console-kit-routes.ts @@ -18,7 +18,7 @@ export type ConsoleKitAppAccessRoute = | Readonly<{ feature: 'users'; screen: 'accepted-invites' }> | Readonly<{ feature: 'users'; screen: 'profiles' }> | Readonly<{ feature: 'users'; screen: 'profile'; profileId: string }> - | Readonly<{ feature: 'users'; screen: 'permissions' }> + | Readonly<{ feature: 'users'; screen: 'capabilities' }> | Readonly<{ feature: 'users'; screen: 'defaults' }>; type OrganizationRouteBase = Readonly<{ @@ -36,7 +36,7 @@ export type ConsoleKitOrganizationsRoute = | 'members' | 'invitations' | 'profiles' - | 'permissions' + | 'capabilities' | 'defaults' | 'hierarchy' | 'developer' diff --git a/apps/blocks/src/blocks/console-kit/constructive/constructive-adapter-utils.ts b/apps/blocks/src/blocks/console-kit/constructive/constructive-adapter-utils.ts index 027bf0d..a6af470 100644 --- a/apps/blocks/src/blocks/console-kit/constructive/constructive-adapter-utils.ts +++ b/apps/blocks/src/blocks/console-kit/constructive/constructive-adapter-utils.ts @@ -27,22 +27,22 @@ export function connectionNodes(value: unknown): Record[] { return nodes; } -export function hasEffectivePermission( +export function hasEffectiveCapability( membership: Record | undefined, - permissionRows: readonly Record[], - permissionName: string + capabilityRows: readonly Record[], + capabilityName: string ): boolean { - const effectiveMask = asString(membership?.permissions); - const requiredMask = asString(permissionRows.find( - (permission) => asString(permission.name) === permissionName + const effectiveMask = asString(membership?.capabilities); + const requiredMask = asString(capabilityRows.find( + (capability) => asString(capability.name) === capabilityName )?.bitstr); - return Boolean(requiredMask?.includes('1')) && permissionMaskIsSubset( + return Boolean(requiredMask?.includes('1')) && capabilityMaskIsSubset( requiredMask, effectiveMask ); } -export function permissionMaskIsSubset( +export function capabilityMaskIsSubset( requiredValue: unknown, effectiveValue: unknown ): boolean { diff --git a/apps/blocks/src/blocks/console-kit/constructive/constructive-adapters.test.ts b/apps/blocks/src/blocks/console-kit/constructive/constructive-adapters.test.ts index cffd0a8..942405e 100644 --- a/apps/blocks/src/blocks/console-kit/constructive/constructive-adapters.test.ts +++ b/apps/blocks/src/blocks/console-kit/constructive/constructive-adapters.test.ts @@ -256,7 +256,7 @@ function appAccessMutationTypes(): ConstructiveSchemaType[] { inputType('CreateAppGrantInput', { appGrant: 'AppGrantInput' }), inputType('AppGrantInput', { actorId: 'UUID', - permissions: 'BitString', + capabilities: 'BitString', isGrant: 'Boolean' }), inputType('CreateAppProfileGrantInput', { @@ -272,14 +272,14 @@ function appAccessMutationTypes(): ConstructiveSchemaType[] { }), inputType('AppProfileDefinitionGrantInput', { profileId: 'UUID', - permissionId: 'UUID', + capabilityId: 'UUID', isGrant: 'Boolean' }), - inputType('CreateAppPermissionDefaultGrantInput', { - appPermissionDefaultGrant: 'AppPermissionDefaultGrantInput' + inputType('CreateAppCapabilityDefaultGrantInput', { + appCapabilityDefaultGrant: 'AppCapabilityDefaultGrantInput' }), - inputType('AppPermissionDefaultGrantInput', { - permissionId: 'UUID', + inputType('AppCapabilityDefaultGrantInput', { + capabilityId: 'UUID', isGrant: 'Boolean' }), inputType('CreateAppProfileInput', { appProfile: 'AppProfileInput' }), @@ -640,19 +640,19 @@ describe('Constructive auth adapter RLS contract', () => { }); describe('Constructive users adapter RLS contract', () => { - it('separates lifecycle, governance, direct grants, effective permissions, and defaults', async () => { + it('separates lifecycle, governance, direct grants, effective capabilities, and defaults', async () => { const calls: GraphQLCall[] = []; const adminSchema = snapshot({ endpoint: 'admin', - queries: ['appMemberships', 'appPermissions', 'appPermissionDefaults'], + queries: ['appMemberships', 'appCapabilities', 'appCapabilityDefaults'], mutations: { updateAppMembership: 'UpdateAppMembershipInput' }, types: [ objectType('AppMembership', [ 'id', 'actorId', 'createdAt', 'isOwner', 'isAdmin', 'isActive', - 'isApproved', 'isVerified', 'isBanned', 'isDisabled', 'permissions', 'granted' + 'isApproved', 'isVerified', 'isBanned', 'isDisabled', 'capabilities', 'granted' ]), - objectType('AppPermission', ['id', 'name', 'description', 'bitnum', 'bitstr']), - objectType('AppPermissionDefault', ['id', 'permissions']), + objectType('AppCapability', ['id', 'name', 'description', 'bitnum', 'bitstr']), + objectType('AppCapabilityDefault', ['id', 'capabilities']), ...appAccessMutationTypes() ] }); @@ -679,7 +679,7 @@ describe('Constructive users adapter RLS contract', () => { isVerified: true, isBanned: false, isDisabled: false, - permissions: '0011', + capabilities: '0011', granted: '0000' }, { @@ -692,26 +692,26 @@ describe('Constructive users adapter RLS contract', () => { isVerified: false, isBanned: false, isDisabled: true, - permissions: '0011', + capabilities: '0011', granted: '0010' } ] } }; } - if (call.document.includes('appPermissions(first:')) { + if (call.document.includes('appCapabilities(first:')) { return { - appPermissions: { + appCapabilities: { nodes: [ { - id: 'permission-admin-members', + id: 'capability-admin-members', name: 'admin_members', description: 'Manage member lifecycle.', bitnum: 0, bitstr: '0001' }, { - id: 'permission-create-entity', + id: 'capability-create-entity', name: 'create_entity', description: 'Create application records.', bitnum: 1, @@ -721,10 +721,10 @@ describe('Constructive users adapter RLS contract', () => { } }; } - if (call.document.includes('appPermissionDefaults(first:')) { + if (call.document.includes('appCapabilityDefaults(first:')) { return { - appPermissionDefaults: { - nodes: [{ id: 'default-1', permissions: '0010' }] + appCapabilityDefaults: { + nodes: [{ id: 'default-1', capabilities: '0010' }] } }; } @@ -744,16 +744,16 @@ describe('Constructive users adapter RLS contract', () => { expect(loaded.resource).toMatchObject({ status: 'ready', data: { - defaultPermissionIds: ['permission-create-entity'], + defaultCapabilityIds: ['capability-create-entity'], members: [ { id: 'membership-actor', governance: { owner: true, admin: true }, lifecycle: { active: true }, - directPermissionIds: [], - effectivePermissionIds: [ - 'permission-admin-members', - 'permission-create-entity' + directCapabilityIds: [], + effectiveCapabilityIds: [ + 'capability-admin-members', + 'capability-create-entity' ] }, { @@ -765,16 +765,16 @@ describe('Constructive users adapter RLS contract', () => { disabled: true, active: false }, - directPermissionIds: ['permission-create-entity'], - effectivePermissionIds: [ - 'permission-admin-members', - 'permission-create-entity' + directCapabilityIds: ['capability-create-entity'], + effectiveCapabilityIds: [ + 'capability-admin-members', + 'capability-create-entity' ] } ], - permissions: [ - { id: 'permission-admin-members', name: 'admin_members', bit: 0 }, - { id: 'permission-create-entity', name: 'create_entity', bit: 1 } + capabilities: [ + { id: 'capability-admin-members', name: 'admin_members', bit: 0 }, + { id: 'capability-create-entity', name: 'create_entity', bit: 1 } ] } }); @@ -814,8 +814,8 @@ describe('Constructive users adapter RLS contract', () => { 'appInvites', 'appClaimedInvites', 'appProfiles', - 'appPermissions', - 'appPermissionDefaults' + 'appCapabilities', + 'appCapabilityDefaults' ], mutations: { createAppInvite: 'CreateAppInviteInput', @@ -827,7 +827,7 @@ describe('Constructive users adapter RLS contract', () => { createAppGrant: 'CreateAppGrantInput', createAppProfileGrant: 'CreateAppProfileGrantInput', createAppProfileDefinitionGrant: 'CreateAppProfileDefinitionGrantInput', - createAppPermissionDefaultGrant: 'CreateAppPermissionDefaultGrantInput', + createAppCapabilityDefaultGrant: 'CreateAppCapabilityDefaultGrantInput', createAppProfile: 'CreateAppProfileInput', updateAppProfile: 'UpdateAppProfileInput', deleteAppProfile: 'DeleteAppProfileInput' @@ -835,7 +835,7 @@ describe('Constructive users adapter RLS contract', () => { types: [ objectType('AppMembership', [ 'id', 'actorId', 'createdAt', 'isOwner', 'isAdmin', 'isActive', - 'isApproved', 'isVerified', 'isBanned', 'isDisabled', 'permissions', + 'isApproved', 'isVerified', 'isBanned', 'isDisabled', 'capabilities', 'granted', 'profileId' ]), objectType('AppInvite', [ @@ -844,10 +844,10 @@ describe('Constructive users adapter RLS contract', () => { ]), objectType('AppClaimedInvite', ['id', 'senderId', 'receiverId', 'createdAt']), objectType('AppProfile', [ - 'id', 'name', 'slug', 'description', 'permissions', 'isSystem', 'isDefault' + 'id', 'name', 'slug', 'description', 'capabilities', 'isSystem', 'isDefault' ]), - objectType('AppPermission', ['id', 'name', 'description', 'bitnum', 'bitstr']), - objectType('AppPermissionDefault', ['id', 'permissions']), + objectType('AppCapability', ['id', 'name', 'description', 'bitnum', 'bitstr']), + objectType('AppCapabilityDefault', ['id', 'capabilities']), ...appAccessMutationTypes() ] }); @@ -881,7 +881,7 @@ describe('Constructive users adapter RLS contract', () => { isVerified: true, isBanned: false, isDisabled: false, - permissions: '1111', + capabilities: '1111', granted: '0000', profileId: 'profile-operator' }, @@ -895,7 +895,7 @@ describe('Constructive users adapter RLS contract', () => { isVerified: true, isBanned: false, isDisabled: false, - permissions: '0101', + capabilities: '0101', granted: '0100', profileId: 'profile-operator' } @@ -946,7 +946,7 @@ describe('Constructive users adapter RLS contract', () => { id: 'profile-operator', name: 'Operator', slug: 'operator', - permissions: '0001', + capabilities: '0001', isSystem: false, isDefault: true }, @@ -954,7 +954,7 @@ describe('Constructive users adapter RLS contract', () => { id: 'profile-system', name: 'System', slug: 'system', - permissions: '1111', + capabilities: '1111', isSystem: true, isDefault: false } @@ -962,22 +962,22 @@ describe('Constructive users adapter RLS contract', () => { } }; } - if (call.document.includes('appPermissions(first:')) { + if (call.document.includes('appCapabilities(first:')) { return { - appPermissions: { + appCapabilities: { nodes: [ - { id: 'permission-members', name: 'admin_members', bitnum: 0, bitstr: '0001' }, - { id: 'permission-policy', name: 'admin_permissions', bitnum: 1, bitstr: '0010' }, - { id: 'permission-invites', name: 'create_invites', bitnum: 2, bitstr: '0100' }, - { id: 'permission-profiles', name: 'assign_profiles', bitnum: 3, bitstr: '1000' } + { id: 'capability-members', name: 'admin_members', bitnum: 0, bitstr: '0001' }, + { id: 'capability-policy', name: 'admin_capabilities', bitnum: 1, bitstr: '0010' }, + { id: 'capability-invites', name: 'create_invites', bitnum: 2, bitstr: '0100' }, + { id: 'capability-profiles', name: 'assign_profiles', bitnum: 3, bitstr: '1000' } ] } }; } - if (call.document.includes('appPermissionDefaults(first:')) { + if (call.document.includes('appCapabilityDefaults(first:')) { return { - appPermissionDefaults: { - nodes: [{ id: 'default-1', permissions: '0100' }] + appCapabilityDefaults: { + nodes: [{ id: 'default-1', capabilities: '0100' }] } }; } @@ -1027,13 +1027,13 @@ describe('Constructive users adapter RLS contract', () => { profiles: [ { id: 'profile-operator', - permissionIds: ['permission-members'], + capabilityIds: ['capability-members'], memberCount: 2, actionPolicy: { updateProfile: true, deleteProfile: true, setDefaultProfile: true, - setProfilePermission: true + setProfileCapability: true } }, { @@ -1042,11 +1042,11 @@ describe('Constructive users adapter RLS contract', () => { updateProfile: false, deleteProfile: false, setDefaultProfile: false, - setProfilePermission: false + setProfileCapability: false } } ], - defaultPermissionIds: ['permission-invites'], + defaultCapabilityIds: ['capability-invites'], inviteProfileIds: ['profile-operator', 'profile-system'] } }); @@ -1055,14 +1055,14 @@ describe('Constructive users adapter RLS contract', () => { assignInviteProfile: true, setOwner: true, setAdmin: true, - setDirectPermission: true, + setDirectCapability: true, setProfile: true, createProfile: true, updateProfile: true, deleteProfile: true, setDefaultProfile: true, - setProfilePermission: true, - setDefaultPermission: true, + setProfileCapability: true, + setDefaultCapability: true, cancelInvite: true, extendInvite: true }); @@ -1073,22 +1073,22 @@ describe('Constructive users adapter RLS contract', () => { }); await loaded.actions?.setOwner?.({ userId: 'user-2', owner: true }); await loaded.actions?.setAdmin?.({ userId: 'user-2', admin: true }); - await loaded.actions?.setDirectPermission?.({ + await loaded.actions?.setDirectCapability?.({ userId: 'user-2', - permissionId: 'permission-invites', + capabilityId: 'capability-invites', granted: false }); await loaded.actions?.setProfile?.({ membershipId: 'membership-member', profileId: 'profile-operator' }); - await loaded.actions?.setProfilePermission?.({ + await loaded.actions?.setProfileCapability?.({ profileId: 'profile-operator', - permissionId: 'permission-invites', + capabilityId: 'capability-invites', granted: true }); - await loaded.actions?.setDefaultPermission?.({ - permissionId: 'permission-members', + await loaded.actions?.setDefaultCapability?.({ + capabilityId: 'capability-members', granted: true }); await loaded.actions?.createProfile?.({ @@ -1123,7 +1123,7 @@ describe('Constructive users adapter RLS contract', () => { input: { appGrant: { actorId: 'user-2', - permissions: '0100', + capabilities: '0100', isGrant: false } } @@ -1144,17 +1144,17 @@ describe('Constructive users adapter RLS contract', () => { input: { appProfileDefinitionGrant: { profileId: 'profile-operator', - permissionId: 'permission-invites', + capabilityId: 'capability-invites', isGrant: true } } }); expect(calls.find((call) => - call.document.includes('ConsoleKitCreateAppPermissionDefaultGrant') + call.document.includes('ConsoleKitCreateAppCapabilityDefaultGrant') )?.variables).toEqual({ input: { - appPermissionDefaultGrant: { - permissionId: 'permission-members', + appCapabilityDefaultGrant: { + capabilityId: 'capability-members', isGrant: true } } @@ -1165,7 +1165,7 @@ describe('Constructive users adapter RLS contract', () => { const calls: GraphQLCall[] = []; const adminSchema = snapshot({ endpoint: 'admin', - queries: ['appMemberships', 'appInvites', 'appProfiles', 'appPermissions'], + queries: ['appMemberships', 'appInvites', 'appProfiles', 'appCapabilities'], mutations: { updateAppMembership: 'UpdateAppMembershipInput', createAppOwnerGrant: 'CreateAppOwnerGrantInput', @@ -1178,13 +1178,13 @@ describe('Constructive users adapter RLS contract', () => { types: [ objectType('AppMembership', [ 'id', 'actorId', 'isOwner', 'isAdmin', 'isActive', 'isApproved', - 'isVerified', 'isBanned', 'isDisabled', 'permissions', 'granted' + 'isVerified', 'isBanned', 'isDisabled', 'capabilities', 'granted' ]), objectType('AppInvite', ['id', 'email', 'senderId', 'inviteValid']), objectType('AppProfile', [ - 'id', 'name', 'slug', 'permissions', 'isSystem', 'isDefault' + 'id', 'name', 'slug', 'capabilities', 'isSystem', 'isDefault' ]), - objectType('AppPermission', ['id', 'name', 'bitstr']), + objectType('AppCapability', ['id', 'name', 'bitstr']), ...appAccessMutationTypes() ] }); @@ -1210,7 +1210,7 @@ describe('Constructive users adapter RLS contract', () => { isVerified: true, isBanned: false, isDisabled: false, - permissions: '0001', + capabilities: '0001', granted: '0000' }] } @@ -1244,7 +1244,7 @@ describe('Constructive users adapter RLS contract', () => { id: 'profile-mutable', name: 'Member', slug: 'member', - permissions: '0001', + capabilities: '0001', isSystem: false, isDefault: false }, @@ -1252,7 +1252,7 @@ describe('Constructive users adapter RLS contract', () => { id: 'profile-system', name: 'System', slug: 'system', - permissions: '0001', + capabilities: '0001', isSystem: true, isDefault: true } @@ -1260,10 +1260,10 @@ describe('Constructive users adapter RLS contract', () => { } }; } - if (call.document.includes('appPermissions(first:')) { + if (call.document.includes('appCapabilities(first:')) { return { - appPermissions: { - nodes: [{ id: 'permission-members', name: 'admin_members', bitstr: '0001' }] + appCapabilities: { + nodes: [{ id: 'capability-members', name: 'admin_members', bitstr: '0001' }] } }; } @@ -1316,14 +1316,14 @@ describe('Constructive organizations adapter RLS contract', () => { discovery: discovery({ admin: snapshot({ endpoint: 'admin', - queries: ['orgMemberships', 'appMemberships', 'appPermissions'], + queries: ['orgMemberships', 'appMemberships', 'appCapabilities'], types: [ objectType('OrgMembership', [ 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'isApproved', - 'isBanned', 'isDisabled', 'isReadOnly', 'permissions' + 'isBanned', 'isDisabled', 'isReadOnly', 'capabilities' ]), - objectType('AppMembership', ['actorId', 'isActive', 'permissions']), - objectType('AppPermission', ['name', 'bitstr']) + objectType('AppMembership', ['actorId', 'isActive', 'capabilities']), + objectType('AppCapability', ['name', 'bitstr']) ] }), auth: snapshot({ @@ -1386,7 +1386,7 @@ describe('Constructive organizations adapter RLS contract', () => { isBanned: false, isDisabled: false, isReadOnly: false, - permissions: '111' + capabilities: '111' })) } }; @@ -1394,13 +1394,13 @@ describe('Constructive organizations adapter RLS contract', () => { if (call.document.includes('appMemberships(first:')) { return { appMemberships: { - nodes: [{ actorId: 'user-1', isActive: true, permissions: '001' }] + nodes: [{ actorId: 'user-1', isActive: true, capabilities: '001' }] } }; } - if (call.document.includes('appPermissions(first:')) { + if (call.document.includes('appCapabilities(first:')) { return { - appPermissions: { nodes: [{ name: 'create_entity', bitstr: '001' }] } + appCapabilities: { nodes: [{ name: 'create_entity', bitstr: '001' }] } }; } if (call.document.includes('users(first:')) { @@ -1479,14 +1479,14 @@ describe('Constructive organizations adapter RLS contract', () => { discovery: discovery({ admin: snapshot({ endpoint: 'admin', - queries: ['orgMemberships', 'appMemberships', 'appPermissions'], + queries: ['orgMemberships', 'appMemberships', 'appCapabilities'], types: [ objectType('OrgMembership', [ 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'isApproved', - 'isBanned', 'isDisabled', 'isReadOnly', 'permissions' + 'isBanned', 'isDisabled', 'isReadOnly', 'capabilities' ]), - objectType('AppMembership', ['actorId', 'isActive', 'permissions']), - objectType('AppPermission', ['name', 'bitstr']) + objectType('AppMembership', ['actorId', 'isActive', 'capabilities']), + objectType('AppCapability', ['name', 'bitstr']) ] }), auth: snapshot({ @@ -1565,7 +1565,7 @@ describe('Constructive organizations adapter RLS contract', () => { isBanned: false, isDisabled: false, isReadOnly: false, - permissions: '111' + capabilities: '111' }] : [] } }; @@ -1573,13 +1573,13 @@ describe('Constructive organizations adapter RLS contract', () => { if (call.document.includes('appMemberships(first:')) { return { appMemberships: { - nodes: [{ actorId: 'user-1', isActive: true, permissions: '001' }] + nodes: [{ actorId: 'user-1', isActive: true, capabilities: '001' }] } }; } - if (call.document.includes('appPermissions(first:')) { + if (call.document.includes('appCapabilities(first:')) { return { - appPermissions: { nodes: [{ name: 'create_entity', bitstr: '001' }] } + appCapabilities: { nodes: [{ name: 'create_entity', bitstr: '001' }] } }; } if (call.document.includes('users(first:')) { @@ -1626,14 +1626,14 @@ describe('Constructive organizations adapter RLS contract', () => { discovery: discovery({ admin: snapshot({ endpoint: 'admin', - queries: ['orgMemberships', 'appMemberships', 'appPermissions'], + queries: ['orgMemberships', 'appMemberships', 'appCapabilities'], types: [ objectType('OrgMembership', [ 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'isApproved', - 'isBanned', 'isDisabled', 'isReadOnly', 'permissions' + 'isBanned', 'isDisabled', 'isReadOnly', 'capabilities' ]), - objectType('AppMembership', ['actorId', 'isActive', 'permissions']), - objectType('AppPermission', ['name', 'bitstr']) + objectType('AppMembership', ['actorId', 'isActive', 'capabilities']), + objectType('AppCapability', ['name', 'bitstr']) ] }), auth: snapshot({ @@ -1697,7 +1697,7 @@ describe('Constructive organizations adapter RLS contract', () => { isBanned: false, isDisabled: false, isReadOnly: false, - permissions: '111' + capabilities: '111' }] : [] } }; @@ -1705,13 +1705,13 @@ describe('Constructive organizations adapter RLS contract', () => { if (call.document.includes('appMemberships(first:')) { return { appMemberships: { - nodes: [{ actorId: 'user-1', isActive: true, permissions: '001' }] + nodes: [{ actorId: 'user-1', isActive: true, capabilities: '001' }] } }; } - if (call.document.includes('appPermissions(first:')) { + if (call.document.includes('appCapabilities(first:')) { return { - appPermissions: { nodes: [{ name: 'create_entity', bitstr: '001' }] } + appCapabilities: { nodes: [{ name: 'create_entity', bitstr: '001' }] } }; } if (call.document.includes('users(first:')) { @@ -1790,7 +1790,7 @@ describe('Constructive organizations adapter RLS contract', () => { 'orgInvites', 'orgProfiles', 'appMemberships', - 'appPermissions' + 'appCapabilities' ], mutations: { createOrgInvite: 'CreateOrgInviteInput', @@ -1801,12 +1801,12 @@ describe('Constructive organizations adapter RLS contract', () => { types: [ objectType('OrgMembership', [ 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'isApproved', - 'isBanned', 'isDisabled', 'isReadOnly', 'permissions', 'profileId' + 'isBanned', 'isDisabled', 'isReadOnly', 'capabilities', 'profileId' ]), objectType('OrgInvite', ['id', 'entityId', 'email', 'profileId']), objectType('OrgProfile', ['id', 'name', 'entityId']), - objectType('AppMembership', ['actorId', 'isActive', 'permissions']), - objectType('AppPermission', ['name', 'bitstr']), + objectType('AppMembership', ['actorId', 'isActive', 'capabilities']), + objectType('AppCapability', ['name', 'bitstr']), ...adminMutationTypes('Org') ] }); @@ -1868,13 +1868,13 @@ describe('Constructive organizations adapter RLS contract', () => { if (call.document.includes('ConsoleKitOrganizationAppMemberships')) { return { appMemberships: { - nodes: [{ actorId: 'user-1', isActive: true, permissions: '000' }] + nodes: [{ actorId: 'user-1', isActive: true, capabilities: '000' }] } }; } - if (call.document.includes('ConsoleKitOrganizationAppPermissions')) { + if (call.document.includes('ConsoleKitOrganizationAppCapabilities')) { return { - appPermissions: { nodes: [{ name: 'create_entity', bitstr: '001' }] } + appCapabilities: { nodes: [{ name: 'create_entity', bitstr: '001' }] } }; } return { createOrgInvite: { orgInvite: { id: 'invite-1' } } }; @@ -1918,7 +1918,7 @@ describe('Constructive organizations adapter RLS contract', () => { it('honors admin_members for one active organization without elevating an ordinary role', async () => { const adminSchema = snapshot({ endpoint: 'admin', - queries: ['orgMemberships', 'orgProfiles', 'orgPermissions'], + queries: ['orgMemberships', 'orgProfiles', 'orgCapabilities'], mutations: { createOrgInvite: 'CreateOrgInviteInput', updateOrgMembership: 'UpdateOrgMembershipInput', @@ -1927,11 +1927,11 @@ describe('Constructive organizations adapter RLS contract', () => { }, types: [ objectType('OrgMembership', [ - 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'permissions', + 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'capabilities', 'profileId' ]), objectType('OrgProfile', ['id', 'name', 'entityId']), - objectType('OrgPermission', ['name', 'bitstr']), + objectType('OrgCapability', ['name', 'bitstr']), ...adminMutationTypes('Org') ] }); @@ -1953,13 +1953,13 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: false, isAdmin: false, isActive: true, - permissions: '0001' + capabilities: '0001' }] }, orgProfiles: { nodes: [{ id: 'profile-1', name: 'Member', entityId: 'org-1' }] }, - orgPermissions: { + orgCapabilities: { nodes: [ { name: 'admin_members', bitstr: '0001' }, { name: 'create_invites', bitstr: '0010' }, @@ -1999,7 +1999,7 @@ describe('Constructive organizations adapter RLS contract', () => { }); const adminSchema = snapshot({ endpoint: 'admin', - queries: ['orgMemberships', 'orgProfiles', 'orgInvites', 'orgPermissions'], + queries: ['orgMemberships', 'orgProfiles', 'orgInvites', 'orgCapabilities'], mutations: { updateOrgMembership: 'UpdateOrgMembershipInput', createOrgProfileGrant: 'CreateOrgProfileGrantInput', @@ -2007,11 +2007,11 @@ describe('Constructive organizations adapter RLS contract', () => { }, types: [ objectType('OrgMembership', [ - 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'permissions' + 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'capabilities' ]), objectType('OrgProfile', ['id', 'name', 'entityId']), objectType('OrgInvite', ['id', 'entityId', 'email', 'senderId', 'inviteValid']), - objectType('OrgPermission', ['name', 'bitstr']), + objectType('OrgCapability', ['name', 'bitstr']), ...adminMutationTypes('Org') ] }); @@ -2036,7 +2036,7 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: false, isAdmin: false, isActive: true, - permissions: '0001' + capabilities: '0001' }, { id: 'membership-member-a', @@ -2045,7 +2045,7 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: false, isAdmin: false, isActive: true, - permissions: '0000' + capabilities: '0000' } ], pageInfo: { hasNextPage: false, endCursor: 'members-end' } @@ -2060,7 +2060,7 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: false, isAdmin: false, isActive: true, - permissions: '0000' + capabilities: '0000' }], pageInfo: { hasNextPage: true, endCursor: 'members-next' } } @@ -2088,11 +2088,11 @@ describe('Constructive organizations adapter RLS contract', () => { } }; } - if (call.document.includes('orgPermissions(first:')) { + if (call.document.includes('orgCapabilities(first:')) { return { - orgPermissions: { + orgCapabilities: { nodes: [{ name: 'admin_members', bitstr: '0001' }], - pageInfo: { hasNextPage: false, endCursor: 'permissions-end' } + pageInfo: { hasNextPage: false, endCursor: 'capabilities-end' } } }; } @@ -2186,7 +2186,7 @@ describe('Constructive organizations adapter RLS contract', () => { mutations: { deleteOrgInvite: 'DeleteOrgInviteInput' }, types: [ objectType('OrgMembership', [ - 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'permissions' + 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'capabilities' ]), objectType('OrgInvite', ['id', 'entityId', 'email', 'senderId', 'inviteValid']), ...adminMutationTypes('Org') @@ -2213,7 +2213,7 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: false, isAdmin, isActive: true, - permissions: '0000' + capabilities: '0000' }] } }; @@ -2291,11 +2291,11 @@ describe('Constructive organizations adapter RLS contract', () => { }, types: [ objectType('OrgMembership', [ - 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'permissions' + 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'capabilities' ]), // A profile connection without entityId cannot prove whether a row is // global or belongs to another organization visible to this actor. - objectType('OrgProfile', ['id', 'name', 'permissions']), + objectType('OrgProfile', ['id', 'name', 'capabilities']), ...adminMutationTypes('Org') ] }); @@ -2317,7 +2317,7 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: true, isAdmin: false, isActive: true, - permissions: '1111' + capabilities: '1111' }] } }; @@ -2325,7 +2325,7 @@ describe('Constructive organizations adapter RLS contract', () => { if (call.document.includes('orgProfiles(first:')) { return { orgProfiles: { - nodes: [{ id: 'profile-from-unknown-scope', name: 'Manager', permissions: '0011' }] + nodes: [{ id: 'profile-from-unknown-scope', name: 'Manager', capabilities: '0011' }] } }; } @@ -2368,9 +2368,9 @@ describe('Constructive organizations adapter RLS contract', () => { }, types: [ objectType('OrgMembership', [ - 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'permissions' + 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'capabilities' ]), - objectType('OrgProfile', ['id', 'name', 'entityId', 'permissions']), + objectType('OrgProfile', ['id', 'name', 'entityId', 'capabilities']), ...adminMutationTypes('Org') ] }); @@ -2392,7 +2392,7 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: true, isAdmin: false, isActive: true, - permissions: '1111' + capabilities: '1111' }] } }; @@ -2401,8 +2401,8 @@ describe('Constructive organizations adapter RLS contract', () => { return { orgProfiles: { nodes: [ - { id: 'profile-global', name: 'Manager', entityId: null, permissions: '0001' }, - { id: 'profile-org', name: 'Manager', entityId: 'org-1', permissions: '0011' } + { id: 'profile-global', name: 'Manager', entityId: null, capabilities: '0001' }, + { id: 'profile-org', name: 'Manager', entityId: 'org-1', capabilities: '0011' } ] } }; @@ -2443,23 +2443,23 @@ describe('Constructive organizations adapter RLS contract', () => { queries: [ 'orgMemberships', 'orgProfiles', - 'orgPermissions', + 'orgCapabilities', 'orgMembershipSettings' ], mutations: { createOrgInvite: 'CreateOrgInviteInput' }, types: [ objectType('OrgMembership', [ - 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'permissions' + 'id', 'actorId', 'entityId', 'isOwner', 'isAdmin', 'isActive', 'capabilities' ]), - objectType('OrgProfile', ['id', 'name', 'entityId', 'permissions']), - objectType('OrgPermission', ['name', 'bitstr']), + objectType('OrgProfile', ['id', 'name', 'entityId', 'capabilities']), + objectType('OrgCapability', ['name', 'bitstr']), objectType('OrgMembershipSetting', ['entityId', 'inviteProfileAssignmentMode']), ...adminMutationTypes('Org') ] }); const loadForMode = async ( - mode: 'strict' | 'permission_only' | 'subset_only' | undefined, - permissions: string, + mode: 'strict' | 'capability_only' | 'subset_only' | undefined, + capabilities: string, isActive = true ) => { const adapter = createConstructiveOrganizationsAdapter({ @@ -2480,7 +2480,7 @@ describe('Constructive organizations adapter RLS contract', () => { isOwner: false, isAdmin: false, isActive, - permissions + capabilities }] }, orgProfiles: { @@ -2489,17 +2489,17 @@ describe('Constructive organizations adapter RLS contract', () => { id: 'profile-subset', name: 'Member', entityId: 'org-1', - permissions: '0010' + capabilities: '0010' }, { id: 'profile-elevated', name: 'Owner', entityId: 'org-1', - permissions: '1000' + capabilities: '1000' } ] }, - orgPermissions: { + orgCapabilities: { nodes: [ { name: 'admin_members', bitstr: '0001' }, { name: 'create_invites', bitstr: '0010' }, @@ -2529,18 +2529,18 @@ describe('Constructive organizations adapter RLS contract', () => { }; const strict = await loadForMode('strict', '0111'); - const permissionOnly = await loadForMode('permission_only', '0111'); + const capabilityOnly = await loadForMode('capability_only', '0111'); const subsetOnly = await loadForMode('subset_only', '0011'); // A create-invites-only member cannot read orgMembershipSettings under the // stock RLS policy, even when the database mode would allow assignment. const missingSetting = await loadForMode(undefined, '0010'); - const inactive = await loadForMode('permission_only', '0111', false); + const inactive = await loadForMode('capability_only', '0111', false); expect(strict.resource).toMatchObject({ status: 'ready', data: { assignableInviteProfileIds: ['profile-subset'] } }); - expect(permissionOnly.resource).toMatchObject({ + expect(capabilityOnly.resource).toMatchObject({ status: 'ready', data: { assignableInviteProfileIds: ['profile-subset', 'profile-elevated'] } }); @@ -2728,9 +2728,9 @@ describe('Constructive organizations adapter RLS contract', () => { 'orgMemberships', 'orgProfiles', 'orgInvites', - 'orgPermissions', + 'orgCapabilities', 'appMemberships', - 'appPermissions' + 'appCapabilities' ], mutations: { createOrgInvite: 'CreateOrgInviteInput', @@ -2750,16 +2750,16 @@ describe('Constructive organizations adapter RLS contract', () => { 'isBanned', 'isDisabled', 'isReadOnly', - 'permissions', + 'capabilities', 'profileId' ]), - objectType('OrgProfile', ['id', 'name', 'entityId', 'permissions']), + objectType('OrgProfile', ['id', 'name', 'entityId', 'capabilities']), objectType('OrgInvite', [ 'id', 'entityId', 'email', 'senderId', 'inviteValid', 'profileId' ]), - objectType('OrgPermission', ['name', 'bitstr']), - objectType('AppMembership', ['actorId', 'isActive', 'permissions']), - objectType('AppPermission', ['name', 'bitstr']), + objectType('OrgCapability', ['name', 'bitstr']), + objectType('AppMembership', ['actorId', 'isActive', 'capabilities']), + objectType('AppCapability', ['name', 'bitstr']), ...adminMutationTypes('Org') ] }); @@ -2801,7 +2801,7 @@ describe('Constructive organizations adapter RLS contract', () => { isBanned: false, isDisabled: false, isReadOnly: false, - permissions: '1' + capabilities: '1' }] } }; @@ -2826,18 +2826,18 @@ describe('Constructive organizations adapter RLS contract', () => { } }; } - if (call.document.includes('orgPermissions(first:')) { - return { orgPermissions: { nodes: [{ name: 'admin_members', bitstr: '1' }] } }; + if (call.document.includes('orgCapabilities(first:')) { + return { orgCapabilities: { nodes: [{ name: 'admin_members', bitstr: '1' }] } }; } if (call.document.includes('appMemberships(first:')) { return { appMemberships: { - nodes: [{ actorId: 'user-1', isActive: true, permissions: '1' }] + nodes: [{ actorId: 'user-1', isActive: true, capabilities: '1' }] } }; } - if (call.document.includes('appPermissions(first:')) { - return { appPermissions: { nodes: [{ name: 'create_entity', bitstr: '1' }] } }; + if (call.document.includes('appCapabilities(first:')) { + return { appCapabilities: { nodes: [{ name: 'create_entity', bitstr: '1' }] } }; } if (call.document.includes('ConsoleKitOrganizationUsersPage')) { return { diff --git a/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.test.ts b/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.test.ts index 16ba9d9..e92a4ee 100644 --- a/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.test.ts +++ b/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.test.ts @@ -133,7 +133,7 @@ function runtime( ? { ok: false, errors: [{ - message: 'permission denied for table optional_organization_surface', + message: 'capability denied for table optional_organization_surface', extensions: { code: '42501' } }] } @@ -166,8 +166,8 @@ function organizationSchemas(): ConstructiveSchemaMap { 'orgMemberships', 'orgMemberProfiles', 'orgProfiles', - 'orgProfilePermissions', - 'orgPermissions', + 'orgProfileCapabilities', + 'orgCapabilities', 'orgMembershipSettings', 'orgMembershipDefaults', 'orgChartEdges', @@ -205,7 +205,7 @@ function organizationSchemas(): ConstructiveSchemaMap { 'isDisabled', 'isExternal', 'isReadOnly', - 'permissions', + 'capabilities', 'granted', 'profileId' ]), @@ -213,10 +213,10 @@ function organizationSchemas(): ConstructiveSchemaMap { 'id', 'membershipId', 'entityId', 'actorId', 'displayName', 'email', 'title', 'bio' ]), objectType('OrgProfile', [ - 'id', 'name', 'slug', 'description', 'entityId', 'permissions', 'isSystem', 'isDefault' + 'id', 'name', 'slug', 'description', 'entityId', 'capabilities', 'isSystem', 'isDefault' ]), - objectType('OrgProfilePermission', ['id', 'profileId', 'permissionId']), - objectType('OrgPermission', ['id', 'name', 'description', 'bitstr']), + objectType('OrgProfileCapability', ['id', 'profileId', 'capabilityId']), + objectType('OrgCapability', ['id', 'name', 'description', 'bitstr']), objectType('OrgMembershipSetting', [ 'id', 'entityId', @@ -278,7 +278,7 @@ function organizationSchemas(): ConstructiveSchemaMap { 'CreateOrgGrant', 'orgGrant', 'OrgGrantInput', - { entityId: 'UUID', actorId: 'UUID', permissions: 'BitString', isGrant: 'Boolean' } + { entityId: 'UUID', actorId: 'UUID', capabilities: 'BitString', isGrant: 'Boolean' } ), ...nestedMutationTypes( 'CreateOrgProfileGrant', @@ -290,7 +290,7 @@ function organizationSchemas(): ConstructiveSchemaMap { 'CreateOrgProfileDefinitionGrant', 'orgProfileDefinitionGrant', 'OrgProfileDefinitionGrantInput', - { profileId: 'UUID', permissionId: 'UUID', isGrant: 'Boolean' } + { profileId: 'UUID', capabilityId: 'UUID', isGrant: 'Boolean' } ), ...nestedMutationTypes( 'CreateOrgProfile', @@ -425,7 +425,7 @@ type OrganizationResponderOverride = (call: GraphQLCall) => unknown | undefined; function organizationMembership( id: string, actorId: string, - permissions: string, + capabilities: string, overrides: Readonly> = {} ): Record { return { @@ -440,8 +440,8 @@ function organizationMembership( isDisabled: false, isExternal: false, isReadOnly: false, - permissions, - granted: permissions, + capabilities, + granted: capabilities, ...overrides }; } @@ -479,7 +479,7 @@ function organizationResponder( isDisabled: false, isExternal: false, isReadOnly: false, - permissions: '1111', + capabilities: '1111', granted: '1111' }, { @@ -494,7 +494,7 @@ function organizationResponder( isDisabled: false, isExternal: false, isReadOnly: false, - permissions: '0001', + capabilities: '0001', granted: '0001', profileId: 'profile-1' }, @@ -510,7 +510,7 @@ function organizationResponder( isDisabled: false, isExternal: false, isReadOnly: false, - permissions: '0000', + capabilities: '0000', granted: '0000' }, { @@ -520,7 +520,7 @@ function organizationResponder( isOwner: false, isAdmin: false, isActive: true, - permissions: '0000' + capabilities: '0000' } ] } @@ -551,7 +551,7 @@ function organizationResponder( name: 'Member', description: 'Member access', entityId: 'org-1', - permissions: '0001', + capabilities: '0001', isSystem: false, isDefault: true }, @@ -559,7 +559,7 @@ function organizationResponder( id: 'profile-foreign', name: 'Foreign', entityId: 'org-foreign', - permissions: '1111', + capabilities: '1111', isSystem: false, isDefault: false } @@ -567,37 +567,37 @@ function organizationResponder( } }; } - if (call.document.includes('ConsoleKitOrganizationProfilePermissionsPage')) { + if (call.document.includes('ConsoleKitOrganizationProfileCapabilitiesPage')) { return { - orgProfilePermissions: { - nodes: [{ id: 'profile-permission-1', profileId: 'profile-1', permissionId: 'permission-1' }] + orgProfileCapabilities: { + nodes: [{ id: 'profile-capability-1', profileId: 'profile-1', capabilityId: 'capability-1' }] } }; } - if (call.document.includes('ConsoleKitOrganizationMembershipsPermissionsPage')) { + if (call.document.includes('ConsoleKitOrganizationMembershipsCapabilitiesPage')) { return { - orgPermissions: { + orgCapabilities: { nodes: [ { - id: 'permission-1', + id: 'capability-1', name: 'admin_members', description: 'Manage members', bitstr: '0001' }, { - id: 'permission-2', - name: 'admin_permissions', - description: 'Manage permissions', + id: 'capability-2', + name: 'admin_capabilities', + description: 'Manage capabilities', bitstr: '0010' }, { - id: 'permission-3', + id: 'capability-3', name: 'admin_invites', description: 'Manage invitations', bitstr: '0100' }, { - id: 'permission-4', + id: 'capability-4', name: 'manage_hierarchy', description: 'Manage hierarchy', bitstr: '1000' @@ -835,12 +835,12 @@ describe('Constructive organizations semantic mutation contract', () => { approveMember: true, grantAdmin: true, grantOwner: true, - grantPermission: true, + grantCapability: true, assignProfile: true, createAccessProfile: true, updateAccessProfile: true, deleteAccessProfile: true, - setProfilePermission: true, + setProfileCapability: true, updateMemberProfile: true, updateMembershipSettings: true, updateMembershipDefault: true @@ -865,10 +865,10 @@ describe('Constructive organizations semantic mutation contract', () => { actorId: 'user-member', isGrant: true }); - await loaded.actions?.setMemberPermission?.({ + await loaded.actions?.setMemberCapability?.({ organizationId: 'org-1', actorId: 'user-member', - permissions: '0001', + capabilities: '0001', isGrant: true }); await loaded.actions?.setMemberProfile?.({ @@ -877,10 +877,10 @@ describe('Constructive organizations semantic mutation contract', () => { profileId: 'profile-1', isGrant: true }); - await loaded.actions?.setProfilePermission?.({ + await loaded.actions?.setProfileCapability?.({ organizationId: 'org-1', profileId: 'profile-1', - permissionId: 'permission-2', + capabilityId: 'capability-2', isGrant: true }); await loaded.actions?.upsertMemberProfile?.({ @@ -941,7 +941,7 @@ describe('Constructive organizations semantic mutation contract', () => { orgGrant: { entityId: 'org-1', actorId: 'user-member', - permissions: '0001', + capabilities: '0001', isGrant: true } } @@ -960,7 +960,7 @@ describe('Constructive organizations semantic mutation contract', () => { input: { orgProfileDefinitionGrant: { profileId: 'profile-1', - permissionId: 'permission-2', + capabilityId: 'capability-2', isGrant: true } } @@ -1009,7 +1009,7 @@ describe('Constructive organizations semantic mutation contract', () => { }); }); - it('gates direct member grants by admin_members rather than admin_permissions', async () => { + it('gates direct member grants by admin_members rather than admin_capabilities', async () => { const memberAdminCalls: GraphQLCall[] = []; const memberAdmin = await loadedOrganizations(memberAdminCalls, { subjectId: 'user-member', @@ -1019,17 +1019,17 @@ describe('Constructive organizations semantic mutation contract', () => { ]) }); - expect(memberAdmin.loaded.policy?.grantPermission).toBe(true); + expect(memberAdmin.loaded.policy?.grantCapability).toBe(true); expect(memberAdmin.loaded.resource.status).toBe('ready'); if (memberAdmin.loaded.resource.status === 'ready') { expect(memberAdmin.loaded.resource.data.members.find( (member) => member.id === 'membership-third' - )?.actionPolicy?.grantPermission).toBe(true); + )?.actionPolicy?.grantCapability).toBe(true); } - await memberAdmin.loaded.actions?.setMemberPermission?.({ + await memberAdmin.loaded.actions?.setMemberCapability?.({ organizationId: 'org-1', actorId: 'user-third', - permissions: '0001', + capabilities: '0001', isGrant: true }); expect(mutationCall(memberAdminCalls, 'ConsoleKitCreateOrgGrant').variables).toEqual({ @@ -1037,21 +1037,21 @@ describe('Constructive organizations semantic mutation contract', () => { orgGrant: { entityId: 'org-1', actorId: 'user-third', - permissions: '0001', + capabilities: '0001', isGrant: true } } }); - const permissionAdmin = await loadedOrganizations([], { + const capabilityAdmin = await loadedOrganizations([], { subjectId: 'user-member', override: membershipOverride([ organizationMembership('membership-member', 'user-member', '0010'), organizationMembership('membership-third', 'user-third', '0000') ]) }); - expect(permissionAdmin.loaded.policy?.grantPermission).toBe(false); - expect(permissionAdmin.loaded.actions?.setMemberPermission).toBeUndefined(); + expect(capabilityAdmin.loaded.policy?.grantCapability).toBe(false); + expect(capabilityAdmin.loaded.actions?.setMemberCapability).toBeUndefined(); }); it('gates hierarchy writes by manage_hierarchy and rejects inactive actors', async () => { @@ -1376,10 +1376,10 @@ describe('Constructive organizations semantic mutation contract', () => { profileId: 'profile-foreign', isGrant: true })).rejects.toThrow('not in the current authorized resource'); - await expect(loaded.actions?.setProfilePermission?.({ + await expect(loaded.actions?.setProfileCapability?.({ organizationId: 'org-1', profileId: 'profile-1', - permissionId: 'permission-foreign', + capabilityId: 'capability-foreign', isGrant: true })).rejects.toThrow('not in the current authorized resource'); await expect(loaded.actions?.updateMembershipSettings?.({ diff --git a/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.ts b/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.ts index abe477d..0394123 100644 --- a/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.ts +++ b/apps/blocks/src/blocks/console-kit/constructive/organizations-adapter.ts @@ -9,7 +9,7 @@ import type { OrganizationMemberRowAction, OrganizationMembershipDefault, OrganizationMembershipSettings, - OrganizationPermission, + OrganizationCapability, OrganizationPrincipal, OrganizationsFeatureData, OrganizationsFeaturePackProps, @@ -37,11 +37,11 @@ import { asRecord, asString, expiresIn, - hasEffectivePermission, + hasEffectiveCapability, imageUrl, notifyConsoleAdapters, packAvailability, - permissionMaskIsSubset + capabilityMaskIsSubset } from './constructive-adapter-utils'; import { executeConstructiveConnectionQuery, @@ -212,7 +212,7 @@ const CREATED_OWNER_MEMBERSHIP_FIELDS = [ 'isBanned', 'isDisabled', 'isReadOnly', - 'permissions' + 'capabilities' ] as const; function createOrganizationMutation(userFields: readonly string[]): string { @@ -263,30 +263,30 @@ type OrganizationDirectorySelections = Readonly<{ memberships: readonly string[]; memberProfiles: readonly string[]; profiles: readonly string[]; - profilePermissions: readonly string[]; + profileCapabilities: readonly string[]; invites: readonly string[]; claimedInvites: readonly string[]; - permissions: readonly string[]; + capabilities: readonly string[]; settings: readonly string[]; defaults: readonly string[]; hierarchy: readonly string[]; appMemberships: readonly string[]; - appPermissions: readonly string[]; + appCapabilities: readonly string[]; }>; const EMPTY_ORGANIZATION_DIRECTORY_SELECTIONS: OrganizationDirectorySelections = { memberships: [], memberProfiles: [], profiles: [], - profilePermissions: [], + profileCapabilities: [], invites: [], claimedInvites: [], - permissions: [], + capabilities: [], settings: [], defaults: [], hierarchy: [], appMemberships: [], - appPermissions: [] + appCapabilities: [] }; function organizationUserFields( @@ -316,7 +316,7 @@ function adminDirectorySelections( 'isBanned', 'isDisabled', 'isReadOnly', - 'permissions', + 'capabilities', 'granted', 'profileId' ]) @@ -346,17 +346,17 @@ function adminDirectorySelections( 'slug', 'description', 'entityId', - 'permissions', + 'capabilities', 'isSystem', 'isDefault' ] ); const profiles = supports(options, 'admin', 'query', 'orgProfiles') ? profileFields : []; - const profilePermissions = supports(options, 'admin', 'query', 'orgProfilePermissions') - ? connectionSelection(schema, 'OrgProfilePermission', [ + const profileCapabilities = supports(options, 'admin', 'query', 'orgProfileCapabilities') + ? connectionSelection(schema, 'OrgProfileCapability', [ 'id', 'profileId', - 'permissionId' + 'capabilityId' ]) : []; const inviteFields = connectionSelection(schema, 'OrgInvite', [ @@ -386,14 +386,14 @@ function adminDirectorySelections( 'createdAt' ]) : []; - const permissionFields = connectionSelection( + const capabilityFields = connectionSelection( schema, - 'OrgPermission', + 'OrgCapability', ['id', 'name', 'description', 'bitstr'] ); - const permissions = supports(options, 'admin', 'query', 'orgPermissions') && - permissionFields.includes('name') && permissionFields.includes('bitstr') - ? permissionFields + const capabilities = supports(options, 'admin', 'query', 'orgCapabilities') && + capabilityFields.includes('name') && capabilityFields.includes('bitstr') + ? capabilityFields : []; const settingFields = connectionSelection( schema, @@ -431,30 +431,30 @@ function adminDirectorySelections( const appMembershipFields = connectionSelection(schema, 'AppMembership', [ 'actorId', 'isActive', - 'permissions' + 'capabilities' ]); const appMemberships = supports(options, 'admin', 'query', 'appMemberships') && - ['actorId', 'isActive', 'permissions'].every((field) => appMembershipFields.includes(field)) + ['actorId', 'isActive', 'capabilities'].every((field) => appMembershipFields.includes(field)) ? appMembershipFields : []; - const appPermissionFields = connectionSelection(schema, 'AppPermission', ['name', 'bitstr']); - const appPermissions = supports(options, 'admin', 'query', 'appPermissions') && - appPermissionFields.includes('name') && appPermissionFields.includes('bitstr') - ? appPermissionFields + const appCapabilityFields = connectionSelection(schema, 'AppCapability', ['name', 'bitstr']); + const appCapabilities = supports(options, 'admin', 'query', 'appCapabilities') && + appCapabilityFields.includes('name') && appCapabilityFields.includes('bitstr') + ? appCapabilityFields : []; return { memberships: membershipFields, memberProfiles, profiles, - profilePermissions, + profileCapabilities, invites, claimedInvites, - permissions, + capabilities, settings, defaults, hierarchy, appMemberships, - appPermissions + appCapabilities }; } @@ -465,7 +465,7 @@ function memberStatus(member: Record): string { return asBoolean(member.isActive) ? 'active' : 'inactive'; } -type InviteProfileAssignmentMode = 'strict' | 'permission_only' | 'subset_only'; +type InviteProfileAssignmentMode = 'strict' | 'capability_only' | 'subset_only'; const INVITE_PROFILE_MODE_LIMITATION: FeaturePackLimitation = { code: 'constructive.org-invite-profile-mode-unavailable', @@ -538,7 +538,7 @@ function inviteProfileAssignmentMode( const value = asString(rows.find( (row) => asString(row.entityId) === organizationId )?.inviteProfileAssignmentMode); - if (value === 'strict' || value === 'permission_only' || value === 'subset_only') { + if (value === 'strict' || value === 'capability_only' || value === 'subset_only') { return { mode: value, known: true }; } return { mode: 'strict', known: false }; @@ -551,7 +551,7 @@ function isOptionalReadDenied(cause: unknown): boolean { return code === '42501' || code === 'FORBIDDEN' || code === 'INSUFFICIENT_PRIVILEGE' || - /permission denied for (?:table|relation|schema)|insufficient privilege/iu.test(message); + /capability denied for (?:table|relation|schema)|insufficient privilege/iu.test(message); } async function loadOrganizations( @@ -566,7 +566,7 @@ async function loadOrganizations( cancelableInviteIds: ReadonlySet; policyLimitations: readonly FeaturePackLimitation[]; canManageActiveOrganization: boolean; - canManagePermissions: boolean; + canManageCapabilities: boolean; canManageHierarchy: boolean; canManageCredentials: boolean; profileScopeReadable: boolean; @@ -584,7 +584,7 @@ async function loadOrganizations( cancelableInviteIds: new Set(), policyLimitations: [], canManageActiveOrganization: false, - canManagePermissions: false, + canManageCapabilities: false, canManageHierarchy: false, canManageCredentials: false, profileScopeReadable: false, @@ -650,10 +650,10 @@ async function loadOrganizations( memberships, memberProfileRows, profileRows, - profilePermissionRows, + profileCapabilityRows, inviteRows, claimedInviteRows, - permissionRows, + capabilityRows, settingRows, defaultRows, hierarchyRows, @@ -661,7 +661,7 @@ async function loadOrganizations( principalEntityRows, principalRows, appMembershipRows, - appPermissionRows, + appCapabilityRows, userRows, applicationOrganizationRows, applicationMemberRows @@ -683,9 +683,9 @@ async function loadOrganizations( selections.profiles ), optionalAdminConnection( - 'ConsoleKitOrganizationProfilePermissionsPage', - 'orgProfilePermissions', - selections.profilePermissions + 'ConsoleKitOrganizationProfileCapabilitiesPage', + 'orgProfileCapabilities', + selections.profileCapabilities ), optionalAdminConnection( 'ConsoleKitOrganizationMembershipsInvitesPage', @@ -698,9 +698,9 @@ async function loadOrganizations( selections.claimedInvites ), optionalAdminConnection( - 'ConsoleKitOrganizationMembershipsPermissionsPage', - 'orgPermissions', - selections.permissions + 'ConsoleKitOrganizationMembershipsCapabilitiesPage', + 'orgCapabilities', + selections.capabilities ), optionalAdminConnection( 'ConsoleKitOrganizationMembershipsSettingsPage', @@ -741,9 +741,9 @@ async function loadOrganizations( selections.appMemberships ), optionalAdminConnection( - 'ConsoleKitOrganizationAppPermissionsPage', - 'appPermissions', - selections.appPermissions + 'ConsoleKitOrganizationAppCapabilitiesPage', + 'appCapabilities', + selections.appCapabilities ), userFields ? executeConstructiveConnectionQuery(runtime, 'auth', { @@ -793,7 +793,7 @@ async function loadOrganizations( const actorAppMembership = actorAppMemberships[0]; const canCreateOrganization = actorAppMemberships.length === 1 && asBoolean(actorAppMembership?.isActive) && - hasEffectivePermission(actorAppMembership, appPermissionRows, 'create_entity'); + hasEffectiveCapability(actorAppMembership, appCapabilityRows, 'create_entity'); const actorMemberships = memberships.filter( (membership) => asString(membership.actorId) === actorId ); @@ -894,16 +894,16 @@ async function loadOrganizations( const hasActiveOwnerRole = Boolean( hasActiveMembership && actorMembership && asBoolean(actorMembership.isOwner) ); - const hasNamedPermission = (permissionName: string) => hasActiveMembership && - hasEffectivePermission(actorMembership, permissionRows, permissionName); + const hasNamedCapability = (capabilityName: string) => hasActiveMembership && + hasEffectiveCapability(actorMembership, capabilityRows, capabilityName); const canManageActiveOrganization = !activeIsApplicationOrganization && ( - hasAdministrativeRole || hasNamedPermission('admin_members') + hasAdministrativeRole || hasNamedCapability('admin_members') ); - const canManagePermissions = !activeIsApplicationOrganization && ( - hasAdministrativeRole || hasNamedPermission('admin_permissions') + const canManageCapabilities = !activeIsApplicationOrganization && ( + hasAdministrativeRole || hasNamedCapability('admin_capabilities') ); const canManageHierarchy = !activeIsApplicationOrganization && ( - hasAdministrativeRole || hasNamedPermission('manage_hierarchy') + hasAdministrativeRole || hasNamedCapability('manage_hierarchy') ); const memberProfilesByMembership = new Map( memberProfileRows.flatMap((profile) => { @@ -953,8 +953,8 @@ async function loadOrganizations( isReadOnly: asBoolean(membership.isReadOnly), profileId: asString(membership.profileId) ?? undefined, profileName: profileNames.get(asString(membership.profileId) ?? ''), - directPermissions: asString(membership.granted) ?? undefined, - effectivePermissions: asString(membership.permissions) ?? undefined, + directCapabilities: asString(membership.granted) ?? undefined, + effectiveCapabilities: asString(membership.capabilities) ?? undefined, memberProfile: profile ? { id: asString(profile.id) ?? undefined, @@ -975,7 +975,7 @@ async function loadOrganizations( grantAdmin: hasActiveAdminRole && !targetIsOwner, grantOwner: canChangeOwner, assignProfile: canManageActiveOrganization, - grantPermission: canManageActiveOrganization, + grantCapability: canManageActiveOrganization, updateMemberProfile: canManageActiveOrganization || userId === actorId } }]; @@ -1032,7 +1032,7 @@ async function loadOrganizations( ? applicationMembers : managedMembers; const assignmentMode = inviteProfileAssignmentMode(settingRows, activeOrganizationId); - const hasAssignProfiles = hasNamedPermission('assign_profiles'); + const hasAssignProfiles = hasNamedCapability('assign_profiles'); const canAssignInviteProfiles = !activeIsApplicationOrganization && profileScopeReadable && ( hasAdministrativeRole || (hasActiveMembership && assignmentMode.mode === 'subset_only') || hasAssignProfiles @@ -1042,9 +1042,9 @@ async function loadOrganizations( const id = asString(profile.id); const entityId = asString(profile.entityId); const belongsToActiveOrganization = !entityId || entityId === activeOrganizationId; - const satisfiesSubset = assignmentMode.mode === 'permission_only' || permissionMaskIsSubset( - profile.permissions, - actorMembership?.permissions + const satisfiesSubset = assignmentMode.mode === 'capability_only' || capabilityMaskIsSubset( + profile.capabilities, + actorMembership?.capabilities ); if ( id && @@ -1090,24 +1090,24 @@ async function loadOrganizations( actionPolicy: { cancelInvite: canCancel } }]; }); - const permissionIdsByProfile = new Map(); - for (const row of profilePermissionRows) { + const capabilityIdsByProfile = new Map(); + for (const row of profileCapabilityRows) { const profileId = asString(row.profileId); - const permissionId = asString(row.permissionId); - if (!profileId || !permissionId) continue; - const existing = permissionIdsByProfile.get(profileId) ?? []; - existing.push(permissionId); - permissionIdsByProfile.set(profileId, existing); + const capabilityId = asString(row.capabilityId); + if (!profileId || !capabilityId) continue; + const existing = capabilityIdsByProfile.get(profileId) ?? []; + existing.push(capabilityId); + capabilityIdsByProfile.set(profileId, existing); } - const permissions: OrganizationPermission[] = permissionRows.flatMap((permission) => { - const id = asString(permission.id); - const name = asString(permission.name); - const bitstr = asString(permission.bitstr); + const capabilities: OrganizationCapability[] = capabilityRows.flatMap((capability) => { + const id = asString(capability.id); + const name = asString(capability.name); + const bitstr = asString(capability.bitstr); return id && name && bitstr ? [{ id, name, - description: asString(permission.description) ?? undefined, + description: asString(capability.description) ?? undefined, bitstr }] : []; @@ -1127,14 +1127,14 @@ async function loadOrganizations( name, slug: asString(profile.slug) ?? undefined, description: asString(profile.description) ?? undefined, - permissions: asString(profile.permissions) ?? '', - permissionIds: permissionIdsByProfile.get(id) ?? [], + capabilities: asString(profile.capabilities) ?? '', + capabilityIds: capabilityIdsByProfile.get(id) ?? [], isSystem, isDefault: asBoolean(profile.isDefault), actionPolicy: { - updateAccessProfile: canManagePermissions && profileScopeReadable && !isSystem, - deleteAccessProfile: canManagePermissions && profileScopeReadable && !isSystem, - setProfilePermission: canManagePermissions && profileScopeReadable && !isSystem + updateAccessProfile: canManageCapabilities && profileScopeReadable && !isSystem, + deleteAccessProfile: canManageCapabilities && profileScopeReadable && !isSystem, + setProfileCapability: canManageCapabilities && profileScopeReadable && !isSystem } }]; }); @@ -1321,7 +1321,7 @@ async function loadOrganizations( const owner = active && asBoolean(membership?.isOwner); const banned = asBoolean(membership?.isBanned); const canAdminAccount = active && Boolean( - membership && hasEffectivePermission(membership, permissionRows, 'admin_account') + membership && hasEffectiveCapability(membership, capabilityRows, 'admin_account') ); const canLeave = Boolean(membership) && !banned && ( !owner || @@ -1355,9 +1355,9 @@ async function loadOrganizations( profiles: selections.profiles.length > 0 && !deniedOptionalReads.has('admin:orgProfiles') ? profiles : undefined, - permissions: selections.permissions.length > 0 && - !deniedOptionalReads.has('admin:orgPermissions') - ? permissions + capabilities: selections.capabilities.length > 0 && + !deniedOptionalReads.has('admin:orgCapabilities') + ? capabilities : undefined, membershipSettings: deniedOptionalReads.has('admin:orgMembershipSettings') ? undefined @@ -1385,12 +1385,12 @@ async function loadOrganizations( cancelableInviteIds, policyLimitations, canManageActiveOrganization, - canManagePermissions, + canManageCapabilities, canManageHierarchy, canManageCredentials: hasAdministrativeRole, profileScopeReadable, canCreateInvites: !activeIsApplicationOrganization && ( - hasAdministrativeRole || hasNamedPermission('create_invites') + hasAdministrativeRole || hasNamedCapability('create_invites') ), canAssignInviteProfiles, canCreateOrganization, @@ -1416,7 +1416,7 @@ function isExactCreatedOwnerMembership( actorId: string, organizationId: string ): boolean { - const permissions = asString(membership.permissions); + const capabilities = asString(membership.capabilities); return Boolean(asString(membership.id)) && asString(membership.actorId) === actorId && asString(membership.entityId) === organizationId && @@ -1427,7 +1427,7 @@ function isExactCreatedOwnerMembership( membership.isBanned === false && membership.isDisabled === false && membership.isReadOnly === false && - Boolean(permissions && /^1+$/u.test(permissions)); + Boolean(capabilities && /^1+$/u.test(capabilities)); } function organizationProvisioningUsername(): string { @@ -1541,7 +1541,7 @@ export function createConstructiveOrganizationsAdapter( const knownPrincipalOrganizations = new Map(); const capabilities: readonly AtomicCapabilityId[] = [ 'organizations.memberships', - 'organizations.permissions', + 'organizations.capabilities', 'organizations.limits', 'organizations.profiles', 'organizations.hierarchy', @@ -1693,29 +1693,29 @@ export function createConstructiveOrganizationsAdapter( ['orgOwnerGrant'], { field: 'orgOwnerGrant', requiredFields: ['actorId', 'entityId', 'isGrant'] } ); - const canGrantPermission = loaded.data.members.some( - (member) => member.actionPolicy?.grantPermission + const canGrantCapability = loaded.data.members.some( + (member) => member.actionPolicy?.grantCapability ) && supportsConstructiveMutationInput( adminSchema, 'createOrgGrant', ['orgGrant'], { field: 'orgGrant', - requiredFields: ['actorId', 'entityId', 'permissions', 'isGrant'] + requiredFields: ['actorId', 'entityId', 'capabilities', 'isGrant'] } ); const canDefineProfile = loaded.data.profiles?.some( - (profile) => profile.actionPolicy?.setProfilePermission + (profile) => profile.actionPolicy?.setProfileCapability ) === true && supportsConstructiveMutationInput( adminSchema, 'createOrgProfileDefinitionGrant', ['orgProfileDefinitionGrant'], { field: 'orgProfileDefinitionGrant', - requiredFields: ['profileId', 'permissionId', 'isGrant'] + requiredFields: ['profileId', 'capabilityId', 'isGrant'] } ); - const canCreateProfile = loaded.canManagePermissions && + const canCreateProfile = loaded.canManageCapabilities && supportsConstructiveMutationInput( adminSchema, 'createOrgProfile', @@ -1814,9 +1814,9 @@ export function createConstructiveOrganizationsAdapter( .map((member) => member.userId)); const membersById = new Map(loaded.data.members.map((member) => [member.id, member])); const profileIds = new Set(loaded.data.profiles?.map((profile) => profile.id) ?? []); - const permissionIds = new Set(loaded.data.permissions?.map((permission) => permission.id) ?? []); - const permissionMasks = new Set( - loaded.data.permissions?.map((permission) => permission.bitstr) ?? [] + const capabilityIds = new Set(loaded.data.capabilities?.map((capability) => capability.id) ?? []); + const capabilityMasks = new Set( + loaded.data.capabilities?.map((capability) => capability.bitstr) ?? [] ); const hierarchyEdgeIds = new Set(loaded.data.hierarchy?.map((edge) => edge.id) ?? []); const apiKeyIds = new Set(loaded.data.apiKeys?.map((apiKey) => apiKey.id) ?? []); @@ -1893,7 +1893,7 @@ export function createConstructiveOrganizationsAdapter( grantAdmin: canGrantAdmin, grantOwner: canGrantOwner, assignProfile: canSetMemberProfile, - grantPermission: canGrantPermission, + grantCapability: canGrantCapability, updateMemberProfile: loaded.data.members.some( (member) => member.actionPolicy?.updateMemberProfile && ( member.memberProfile ? canUpdateMemberProfile : canCreateMemberProfile @@ -1902,7 +1902,7 @@ export function createConstructiveOrganizationsAdapter( createAccessProfile: canCreateProfile, updateAccessProfile: canUpdateProfile, deleteAccessProfile: canDeleteProfile, - setProfilePermission: canDefineProfile, + setProfileCapability: canDefineProfile, updateMembershipSettings: canUpdateSettings, updateMembershipDefault: canUpdateDefault, setHierarchyEdge: canManageHierarchy, @@ -2294,19 +2294,19 @@ export function createConstructiveOrganizationsAdapter( reload(); } : undefined, - setMemberPermission: canGrantPermission - ? async ({ organizationId, actorId, permissions, isGrant }) => { + setMemberCapability: canGrantCapability + ? async ({ organizationId, actorId, capabilities, isGrant }) => { assertActiveOrganization(organizationId); assertAuthorizedTarget(activeMemberActorIds, actorId, 'organization member'); - if (!permissionMasks.has(permissions)) { - throw new Error('The permission mask is not present in the visible catalog.'); + if (!capabilityMasks.has(capabilities)) { + throw new Error('The capability mask is not present in the visible catalog.'); } const member = loaded.data.members.find((candidate) => candidate.userId === actorId); if (!member) throw new Error('The organization member is no longer visible.'); - assertMemberAction(member.id, 'grantPermission'); + assertMemberAction(member.id, 'grantCapability'); await executeConstructiveGraphQL(runtime, 'admin', CREATE_PERMISSION_GRANT_MUTATION, { input: { - orgGrant: { entityId: organizationId, actorId, permissions, isGrant } + orgGrant: { entityId: organizationId, actorId, capabilities, isGrant } } }); reload(); @@ -2419,13 +2419,13 @@ export function createConstructiveOrganizationsAdapter( reload(); } : undefined, - setProfilePermission: canDefineProfile - ? async ({ organizationId, profileId, permissionId, isGrant }) => { + setProfileCapability: canDefineProfile + ? async ({ organizationId, profileId, capabilityId, isGrant }) => { assertActiveOrganization(organizationId); assertAuthorizedTarget(profileIds, profileId, 'organization access profile'); - assertAuthorizedTarget(permissionIds, permissionId, 'organization permission'); + assertAuthorizedTarget(capabilityIds, capabilityId, 'organization capability'); const profile = loaded.data.profiles?.find((candidate) => candidate.id === profileId); - if (profile?.actionPolicy?.setProfilePermission !== true) { + if (profile?.actionPolicy?.setProfileCapability !== true) { throw new Error('This access profile cannot be changed.'); } await executeConstructiveGraphQL( @@ -2434,7 +2434,7 @@ export function createConstructiveOrganizationsAdapter( CREATE_PROFILE_DEFINITION_GRANT_MUTATION, { input: { - orgProfileDefinitionGrant: { profileId, permissionId, isGrant } + orgProfileDefinitionGrant: { profileId, capabilityId, isGrant } } } ); diff --git a/apps/blocks/src/blocks/console-kit/constructive/users-adapter.ts b/apps/blocks/src/blocks/console-kit/constructive/users-adapter.ts index 7668c08..2641575 100644 --- a/apps/blocks/src/blocks/console-kit/constructive/users-adapter.ts +++ b/apps/blocks/src/blocks/console-kit/constructive/users-adapter.ts @@ -4,7 +4,7 @@ import type { AppClaimedInvite, AppInvite, AppMember, - AppPermission, + AppCapability, UsersFeatureData, UsersFeaturePackProps } from '../../feature-packs/users/users-feature-pack'; @@ -23,11 +23,11 @@ import { asRecord, asString, expiresIn, - hasEffectivePermission, + hasEffectiveCapability, imageUrl, notifyConsoleAdapters, packAvailability, - permissionMaskIsSubset + capabilityMaskIsSubset } from './constructive-adapter-utils'; import { executeConstructiveConnectionQuery, @@ -79,11 +79,11 @@ const CREATE_PROFILE_DEFINITION_GRANT_MUTATION = /* GraphQL */ ` `; const CREATE_PERMISSION_DEFAULT_GRANT_MUTATION = /* GraphQL */ ` - mutation ConsoleKitCreateAppPermissionDefaultGrant( - $input: CreateAppPermissionDefaultGrantInput! + mutation ConsoleKitCreateAppCapabilityDefaultGrant( + $input: CreateAppCapabilityDefaultGrantInput! ) { - createAppPermissionDefaultGrant(input: $input) { - appPermissionDefaultGrant { id } + createAppCapabilityDefaultGrant(input: $input) { + appCapabilityDefaultGrant { id } } } `; @@ -165,7 +165,7 @@ type AppAccessSelections = Readonly<{ invitations: readonly string[]; acceptedInvites: readonly string[]; profiles: readonly string[]; - permissions: readonly string[]; + capabilities: readonly string[]; defaults: readonly string[]; users: readonly string[]; emails: readonly string[]; @@ -188,7 +188,7 @@ function adminAccessSelections( 'isVerified', 'isBanned', 'isDisabled', - 'permissions', + 'capabilities', 'granted', 'profileId' ]); @@ -242,7 +242,7 @@ function adminAccessSelections( 'name', 'slug', 'description', - 'permissions', + 'capabilities', 'isSystem', 'isDefault', 'createdAt', @@ -253,20 +253,20 @@ function adminAccessSelections( ? profileFields : []; - const permissionFields = connectionSelection(adminSchema, 'AppPermission', [ + const capabilityFields = connectionSelection(adminSchema, 'AppCapability', [ 'id', 'name', 'description', 'bitnum', 'bitstr' ]); - const permissions = supports(options, 'admin', 'query', 'appPermissions') && - permissionFields.includes('id') && permissionFields.includes('name') && - permissionFields.includes('bitstr') - ? permissionFields + const capabilities = supports(options, 'admin', 'query', 'appCapabilities') && + capabilityFields.includes('id') && capabilityFields.includes('name') && + capabilityFields.includes('bitstr') + ? capabilityFields : []; - const defaultFields = connectionSelection(adminSchema, 'AppPermissionDefault', [ - 'id', 'permissions' + const defaultFields = connectionSelection(adminSchema, 'AppCapabilityDefault', [ + 'id', 'capabilities' ]); - const defaults = supports(options, 'admin', 'query', 'appPermissionDefaults') && - defaultFields.includes('permissions') + const defaults = supports(options, 'admin', 'query', 'appCapabilityDefaults') && + defaultFields.includes('capabilities') ? defaultFields : []; @@ -292,20 +292,20 @@ function adminAccessSelections( invitations, acceptedInvites, profiles, - permissions, + capabilities, defaults, users, emails }; } -function permissionIdsForMask( +function capabilityIdsForMask( mask: unknown, - permissionRows: readonly Record[] + capabilityRows: readonly Record[] ): string[] { - return permissionRows.flatMap((permission) => { - const id = asString(permission.id); - return id && permissionMaskIsSubset(permission.bitstr, mask) ? [id] : []; + return capabilityRows.flatMap((capability) => { + const id = asString(capability.id); + return id && capabilityMaskIsSubset(capability.bitstr, mask) ? [id] : []; }); } @@ -329,10 +329,10 @@ type LoadedAppAccess = Readonly<{ memberByActorId: ReadonlyMap; profileIds: ReadonlySet; mutableProfileIds: ReadonlySet; - permissionMasks: ReadonlyMap; + capabilityMasks: ReadonlyMap; ownedInviteIds: ReadonlySet; canManageMembers: boolean; - canManagePermissions: boolean; + canManageCapabilities: boolean; canCreateInvites: boolean; canAssignInviteProfiles: boolean; actorIsAdmin: boolean; @@ -353,10 +353,10 @@ async function loadAccess( memberByActorId: new Map(), profileIds: new Set(), mutableProfileIds: new Set(), - permissionMasks: new Map(), + capabilityMasks: new Map(), ownedInviteIds: new Set(), canManageMembers: false, - canManagePermissions: false, + canManageCapabilities: false, canCreateInvites: false, canAssignInviteProfiles: false, actorIsAdmin: false, @@ -397,7 +397,7 @@ async function loadAccess( invitationRows, acceptedInviteRows, profileRows, - permissionRows, + capabilityRows, defaultRows, userRows, emailRows @@ -428,14 +428,14 @@ async function loadAccess( ), optionalConnection( 'admin', - 'ConsoleKitAppAccessPermissionsPage', - 'appPermissions', - selections.permissions + 'ConsoleKitAppAccessCapabilitiesPage', + 'appCapabilities', + selections.capabilities ), optionalConnection( 'admin', - 'ConsoleKitAppAccessPermissionDefaultsPage', - 'appPermissionDefaults', + 'ConsoleKitAppAccessCapabilityDefaultsPage', + 'appCapabilityDefaults', selections.defaults ), optionalConnection( @@ -459,18 +459,18 @@ async function loadAccess( if (!emails.has(ownerId) || asBoolean(email.isPrimary)) emails.set(ownerId, value); } - const permissionMasks = new Map(); - const permissions: AppPermission[] = permissionRows.flatMap((permission) => { - const id = asString(permission.id); - const name = asString(permission.name); - const bitstr = asString(permission.bitstr); + const capabilityMasks = new Map(); + const capabilities: AppCapability[] = capabilityRows.flatMap((capability) => { + const id = asString(capability.id); + const name = asString(capability.name); + const bitstr = asString(capability.bitstr); if (!id || !name || !bitstr) return []; - permissionMasks.set(id, bitstr); + capabilityMasks.set(id, bitstr); return [{ id, name, - description: asString(permission.description) ?? undefined, - bit: typeof permission.bitnum === 'number' ? permission.bitnum : undefined + description: asString(capability.description) ?? undefined, + bit: typeof capability.bitnum === 'number' ? capability.bitnum : undefined }]; }); @@ -491,7 +491,7 @@ async function loadAccess( name, slug: asString(profile.slug) ?? undefined, description: asString(profile.description) ?? undefined, - permissionIds: permissionIdsForMask(profile.permissions, permissionRows), + capabilityIds: capabilityIdsForMask(profile.capabilities, capabilityRows), system: asBoolean(profile.isSystem), default: asBoolean(profile.isDefault), memberCount: profileMemberCounts.get(id) ?? 0 @@ -530,8 +530,8 @@ async function loadAccess( admin: asBoolean(membership.isAdmin) }, profile, - directPermissionIds: permissionIdsForMask(membership.granted, permissionRows), - effectivePermissionIds: permissionIdsForMask(membership.permissions, permissionRows), + directCapabilityIds: capabilityIdsForMask(membership.granted, capabilityRows), + effectiveCapabilityIds: capabilityIdsForMask(membership.capabilities, capabilityRows), joinedAt: asString(membership.createdAt) ?? undefined }]; }); @@ -588,22 +588,22 @@ async function loadAccess( const hasActiveMembership = asBoolean(actorMembership?.isActive); const actorIsOwner = hasActiveMembership && asBoolean(actorMembership?.isOwner); const actorIsAdmin = hasActiveMembership && asBoolean(actorMembership?.isAdmin); - const hasNamedPermission = (permissionName: string) => hasActiveMembership && - hasEffectivePermission(actorMembership, permissionRows, permissionName); - const canManageMembers = actorIsAdmin || hasNamedPermission('admin_members'); - const canManagePermissions = actorIsAdmin || hasNamedPermission('admin_permissions'); - const canCreateInvites = actorIsAdmin || hasNamedPermission('create_invites'); - const canAssignInviteProfiles = actorIsAdmin || hasNamedPermission('assign_profiles'); + const hasNamedCapability = (capabilityName: string) => hasActiveMembership && + hasEffectiveCapability(actorMembership, capabilityRows, capabilityName); + const canManageMembers = actorIsAdmin || hasNamedCapability('admin_members'); + const canManageCapabilities = actorIsAdmin || hasNamedCapability('admin_capabilities'); + const canCreateInvites = actorIsAdmin || hasNamedCapability('create_invites'); + const canAssignInviteProfiles = actorIsAdmin || hasNamedCapability('assign_profiles'); const inviteProfileIds = profiles.flatMap((profile) => { const source = profileRows.find((row) => asString(row.id) === profile.id); const allowed = hasActiveMembership && ( - actorIsAdmin || permissionMaskIsSubset(source?.permissions, actorMembership?.permissions) + actorIsAdmin || capabilityMaskIsSubset(source?.capabilities, actorMembership?.capabilities) ); return allowed ? [profile.id] : []; }); - const defaultPermissionIds = selections.defaults.length > 0 && selections.permissions.length > 0 - ? permissionIdsForMask(defaultRows[0]?.permissions, permissionRows) + const defaultCapabilityIds = selections.defaults.length > 0 && selections.capabilities.length > 0 + ? capabilityIdsForMask(defaultRows[0]?.capabilities, capabilityRows) : undefined; return { @@ -612,8 +612,8 @@ async function loadAccess( ...(selections.invitations.length > 0 ? { invitations } : {}), ...(selections.acceptedInvites.length > 0 ? { acceptedInvites } : {}), ...(selections.profiles.length > 0 ? { profiles } : {}), - ...(selections.permissions.length > 0 ? { permissions } : {}), - ...(defaultPermissionIds ? { defaultPermissionIds } : {}), + ...(selections.capabilities.length > 0 ? { capabilities } : {}), + ...(defaultCapabilityIds ? { defaultCapabilityIds } : {}), ...(selections.profiles.length > 0 ? { inviteProfileIds } : {}) }, membershipRows, @@ -625,10 +625,10 @@ async function loadAccess( mutableProfileIds: new Set( profiles.filter((profile) => !profile.system).map((profile) => profile.id) ), - permissionMasks, + capabilityMasks, ownedInviteIds, canManageMembers, - canManagePermissions, + canManageCapabilities, canCreateInvites, canAssignInviteProfiles, actorIsAdmin, @@ -683,7 +683,7 @@ export function createConstructiveUsersAdapter( const capabilities: readonly AtomicCapabilityId[] = [ 'users.directory', 'users.memberships', - 'users.permissions', + 'users.capabilities', 'users.profiles', 'users.invites' ]; @@ -722,12 +722,12 @@ export function createConstructiveUsersAdapter( 'appAdminGrant', ['actorId', 'isGrant'] ); - const canSetDirectPermission = directory.canManageMembers && - directory.permissionMasks.size > 0 && supportsObjectMutation( + const canSetDirectCapability = directory.canManageMembers && + directory.capabilityMasks.size > 0 && supportsObjectMutation( adminSchema, 'createAppGrant', 'appGrant', - ['actorId', 'permissions', 'isGrant'] + ['actorId', 'capabilities', 'isGrant'] ); const canSetProfile = directory.canManageMembers && directory.profileIds.size > 0 && supportsObjectMutation( @@ -736,24 +736,24 @@ export function createConstructiveUsersAdapter( 'appProfileGrant', ['membershipId', 'profileId', 'isGrant'] ); - const canComposeProfiles = directory.canManagePermissions && - directory.profileIds.size > 0 && directory.permissionMasks.size > 0 && + const canComposeProfiles = directory.canManageCapabilities && + directory.profileIds.size > 0 && directory.capabilityMasks.size > 0 && supportsObjectMutation( adminSchema, 'createAppProfileDefinitionGrant', 'appProfileDefinitionGrant', - ['profileId', 'permissionId', 'isGrant'] + ['profileId', 'capabilityId', 'isGrant'] ); - const canSetDefaultPermission = directory.canManagePermissions && - directory.permissionMasks.size > 0 && - directory.data.defaultPermissionIds !== undefined && supportsObjectMutation( + const canSetDefaultCapability = directory.canManageCapabilities && + directory.capabilityMasks.size > 0 && + directory.data.defaultCapabilityIds !== undefined && supportsObjectMutation( adminSchema, - 'createAppPermissionDefaultGrant', - 'appPermissionDefaultGrant', - ['permissionId', 'isGrant'] + 'createAppCapabilityDefaultGrant', + 'appCapabilityDefaultGrant', + ['capabilityId', 'isGrant'] ); - const canCreateProfile = directory.canManagePermissions && supportsObjectMutation( + const canCreateProfile = directory.canManageCapabilities && supportsObjectMutation( adminSchema, 'createAppProfile', 'appProfile', @@ -765,7 +765,7 @@ export function createConstructiveUsersAdapter( 'appProfile', ['description'] ); - const canUpdateProfile = directory.canManagePermissions && + const canUpdateProfile = directory.canManageCapabilities && directory.mutableProfileIds.size > 0 && supportsConstructiveMutationInput( adminSchema, 'updateAppProfile', @@ -778,14 +778,14 @@ export function createConstructiveUsersAdapter( ['id', 'appProfilePatch'], { field: 'appProfilePatch', requiredFields: ['description'] } ); - const canSetDefaultProfile = directory.canManagePermissions && + const canSetDefaultProfile = directory.canManageCapabilities && directory.mutableProfileIds.size > 0 && supportsConstructiveMutationInput( adminSchema, 'updateAppProfile', ['id', 'appProfilePatch'], { field: 'appProfilePatch', requiredFields: ['isDefault'] } ); - const canDeleteProfile = directory.canManagePermissions && + const canDeleteProfile = directory.canManageCapabilities && directory.mutableProfileIds.size > 0 && supportsConstructiveMutationInput( adminSchema, 'deleteAppProfile', @@ -838,7 +838,7 @@ export function createConstructiveUsersAdapter( ), setAdmin: canSetAdmin && !member.governance.owner, setProfile: canSetProfile, - setDirectPermission: canSetDirectPermission + setDirectCapability: canSetDirectCapability } })); const profiles = directory.data.profiles?.map((profile): AppAccessProfile => ({ @@ -847,7 +847,7 @@ export function createConstructiveUsersAdapter( updateProfile: canUpdateProfile && !profile.system, deleteProfile: canDeleteProfile && !profile.system, setDefaultProfile: canSetDefaultProfile && !profile.system, - setProfilePermission: canComposeProfiles && !profile.system + setProfileCapability: canComposeProfiles && !profile.system } })); const data: UsersFeatureData = { @@ -857,7 +857,7 @@ export function createConstructiveUsersAdapter( }; const hasVisibleRows = data.members.length > 0 || Boolean(data.invitations?.length) || Boolean(data.acceptedInvites?.length) || - Boolean(data.profiles?.length) || Boolean(data.permissions?.length); + Boolean(data.profiles?.length) || Boolean(data.capabilities?.length); return { resource: hasVisibleRows @@ -874,13 +874,13 @@ export function createConstructiveUsersAdapter( setOwner: canSetOwner, setAdmin: canSetAdmin, setProfile: canSetProfile, - setDirectPermission: canSetDirectPermission, + setDirectCapability: canSetDirectCapability, createProfile: canCreateProfile, updateProfile: canUpdateProfile, deleteProfile: canDeleteProfile, setDefaultProfile: canSetDefaultProfile, - setProfilePermission: canComposeProfiles, - setDefaultPermission: canSetDefaultPermission, + setProfileCapability: canComposeProfiles, + setDefaultCapability: canSetDefaultCapability, cancelInvite: canDeleteInvite, extendInvite: canUpdateInvite }, @@ -973,19 +973,19 @@ export function createConstructiveUsersAdapter( reload(); } : undefined, - setDirectPermission: canSetDirectPermission - ? async ({ userId, permissionId, granted }) => { + setDirectCapability: canSetDirectCapability + ? async ({ userId, capabilityId, granted }) => { assertAuthorizedTarget(directory.actorIds, userId, 'app member'); assertAuthorizedTarget( - new Set(directory.permissionMasks.keys()), - permissionId, - 'app permission' + new Set(directory.capabilityMasks.keys()), + capabilityId, + 'app capability' ); await executeConstructiveGraphQL(runtime, 'admin', CREATE_DIRECT_GRANT_MUTATION, { input: { appGrant: { actorId: userId, - permissions: directory.permissionMasks.get(permissionId), + capabilities: directory.capabilityMasks.get(capabilityId), isGrant: granted } } @@ -1071,17 +1071,17 @@ export function createConstructiveUsersAdapter( reload(); } : undefined, - setProfilePermission: canComposeProfiles - ? async ({ profileId, permissionId, granted }) => { + setProfileCapability: canComposeProfiles + ? async ({ profileId, capabilityId, granted }) => { assertAuthorizedTarget( directory.mutableProfileIds, profileId, 'mutable app access profile' ); assertAuthorizedTarget( - new Set(directory.permissionMasks.keys()), - permissionId, - 'app permission' + new Set(directory.capabilityMasks.keys()), + capabilityId, + 'app capability' ); await executeConstructiveGraphQL( runtime, @@ -1091,7 +1091,7 @@ export function createConstructiveUsersAdapter( input: { appProfileDefinitionGrant: { profileId, - permissionId, + capabilityId, isGrant: granted } } @@ -1100,12 +1100,12 @@ export function createConstructiveUsersAdapter( reload(); } : undefined, - setDefaultPermission: canSetDefaultPermission - ? async ({ permissionId, granted }) => { + setDefaultCapability: canSetDefaultCapability + ? async ({ capabilityId, granted }) => { assertAuthorizedTarget( - new Set(directory.permissionMasks.keys()), - permissionId, - 'app permission' + new Set(directory.capabilityMasks.keys()), + capabilityId, + 'app capability' ); await executeConstructiveGraphQL( runtime, @@ -1113,8 +1113,8 @@ export function createConstructiveUsersAdapter( CREATE_PERMISSION_DEFAULT_GRANT_MUTATION, { input: { - appPermissionDefaultGrant: { - permissionId, + appCapabilityDefaultGrant: { + capabilityId, isGrant: granted } } diff --git a/apps/blocks/src/blocks/feature-packs/feature-pack-interactions.test.tsx b/apps/blocks/src/blocks/feature-packs/feature-pack-interactions.test.tsx index 72a0ed3..8395416 100644 --- a/apps/blocks/src/blocks/feature-packs/feature-pack-interactions.test.tsx +++ b/apps/blocks/src/blocks/feature-packs/feature-pack-interactions.test.tsx @@ -271,7 +271,7 @@ describe('feature-pack interaction policy', () => { profiles: [{ id: 'profile-admin', name: 'Administrator', - permissionIds: [] + capabilityIds: [] }], inviteProfileIds: ['profile-admin'] } @@ -366,8 +366,8 @@ describe('feature-pack interaction policy', () => { profiles: [{ id: 'profile-admin', name: 'Administrator', - permissions: '', - permissionIds: [], + capabilities: '', + capabilityIds: [], isSystem: false, isDefault: false }], @@ -443,7 +443,7 @@ describe('feature-pack interaction policy', () => { const clipboardDescriptor = Object.getOwnPropertyDescriptor(navigator, 'clipboard'); const writeText = vi.fn() .mockResolvedValueOnce(undefined) - .mockRejectedValueOnce(new Error('Clipboard permission denied')); + .mockRejectedValueOnce(new Error('Clipboard capability denied')); try { Object.defineProperty(navigator, 'clipboard', { @@ -642,7 +642,7 @@ describe('feature-pack interaction policy', () => { profiles: [{ id: 'profile-admin', name: 'Administrator', - permissionIds: [] + capabilityIds: [] }], inviteProfileIds: ['profile-admin'] } @@ -666,7 +666,7 @@ describe('feature-pack interaction policy', () => { profiles: [{ id: 'profile-admin', name: 'Administrator', - permissionIds: [] + capabilityIds: [] }], inviteProfileIds: ['profile-admin'] } @@ -717,8 +717,8 @@ describe('feature-pack interaction policy', () => { profiles: [{ id: 'profile-admin', name: 'Administrator', - permissions: '', - permissionIds: [], + capabilities: '', + capabilityIds: [], isSystem: false, isDefault: false }], @@ -789,8 +789,8 @@ describe('feature-pack interaction policy', () => { active: true }, governance: { owner: false, admin: false }, - directPermissionIds: [], - effectivePermissionIds: [], + directCapabilityIds: [], + effectiveCapabilityIds: [], actionPolicy: { setDisabled: true } }] } @@ -821,8 +821,8 @@ describe('feature-pack interaction policy', () => { status: 'ready', data: { profiles: [ - { id: 'profile-member', name: 'Member', permissionIds: [] }, - { id: 'profile-admin', name: 'Admin', permissionIds: [] } + { id: 'profile-member', name: 'Member', capabilityIds: [] }, + { id: 'profile-admin', name: 'Admin', capabilityIds: [] } ], members: [{ id: 'membership-1', @@ -838,8 +838,8 @@ describe('feature-pack interaction policy', () => { }, governance: { owner: false, admin: false }, profile: { id: 'profile-member', name: 'Member' }, - directPermissionIds: [], - effectivePermissionIds: [], + directCapabilityIds: [], + effectiveCapabilityIds: [], actionPolicy: { setProfile: true } }] } @@ -903,8 +903,8 @@ describe('feature-pack interaction policy', () => { active: true }, governance: { owner: true, admin: true }, - directPermissionIds: [], - effectivePermissionIds: [] + directCapabilityIds: [], + effectiveCapabilityIds: [] }] } }} @@ -964,8 +964,8 @@ describe('feature-pack interaction policy', () => { email: 'ada@example.com', lifecycle: { approved: true, verified: true, banned: false, disabled: false, active: true }, governance: { owner: true, admin: true }, - directPermissionIds: [], - effectivePermissionIds: [] + directCapabilityIds: [], + effectiveCapabilityIds: [] }, { id: 'membership-2', @@ -974,8 +974,8 @@ describe('feature-pack interaction policy', () => { email: 'grace@example.com', lifecycle: { approved: true, verified: true, banned: false, disabled: false, active: true }, governance: { owner: false, admin: false }, - directPermissionIds: [], - effectivePermissionIds: [] + directCapabilityIds: [], + effectiveCapabilityIds: [] } ] } @@ -1065,8 +1065,8 @@ describe('feature-pack interaction policy', () => { profiles: [{ id: 'profile-admin', name: 'Administrator', - permissions: '', - permissionIds: [], + capabilities: '', + capabilityIds: [], isSystem: false, isDefault: false }], @@ -1205,8 +1205,8 @@ describe('feature-pack interaction policy', () => { email: 'ada@example.com', lifecycle: { approved: true, verified: true, banned: false, disabled: false, active: true }, governance: { owner: true, admin: true }, - directPermissionIds: [], - effectivePermissionIds: [] + directCapabilityIds: [], + effectiveCapabilityIds: [] }] } } @@ -1353,8 +1353,8 @@ describe('feature-pack interaction policy', () => { profiles: [{ id: 'profile-support', name: 'Support', - permissions: '', - permissionIds: [], + capabilities: '', + capabilityIds: [], isSystem: false, isDefault: false }], diff --git a/apps/blocks/src/blocks/feature-packs/organizations/feature-pack.json b/apps/blocks/src/blocks/feature-packs/organizations/feature-pack.json index d92fdac..13f5e5b 100644 --- a/apps/blocks/src/blocks/feature-packs/organizations/feature-pack.json +++ b/apps/blocks/src/blocks/feature-packs/organizations/feature-pack.json @@ -7,7 +7,7 @@ "endpoints": { "required": [], "optional": ["admin", "auth", "billing", "data"] }, "capabilities": { "required": ["organizations.memberships"], - "optional": ["organizations.permissions", "organizations.limits", "organizations.profiles", "organizations.hierarchy", "organizations.invites"] + "optional": ["organizations.capabilities", "organizations.limits", "organizations.profiles", "organizations.hierarchy", "organizations.invites"] }, "metadata": { "requiredMetaSections": ["tables", "fields", "constraints", "relations", "query", "scope"], diff --git a/apps/blocks/src/blocks/feature-packs/organizations/organizations-access-panels.tsx b/apps/blocks/src/blocks/feature-packs/organizations/organizations-access-panels.tsx index f7a9886..d63b6d1 100644 --- a/apps/blocks/src/blocks/feature-packs/organizations/organizations-access-panels.tsx +++ b/apps/blocks/src/blocks/feature-packs/organizations/organizations-access-panels.tsx @@ -60,7 +60,7 @@ import type { OrganizationAccessProfile, OrganizationMember, OrganizationMembershipDefault, - OrganizationPermission, + OrganizationCapability, OrganizationsFeatureActions, OrganizationsFeaturePackProps } from './organizations-contracts'; @@ -152,7 +152,7 @@ function AccessProfileDialog({ {profile ? `Edit ${profile.name}` : 'Create an access profile'} - Profiles bundle organization permissions so access remains understandable as the team grows. + Profiles bundle organization capabilities so access remains understandable as the team grows. @@ -213,7 +213,7 @@ function DeleteAccessProfileAction({ Delete {profile.name}? - Members assigned to this profile lose its bundled permissions. Direct grants remain unchanged. + Members assigned to this profile lose its bundled capabilities. Direct grants remain unchanged. {error ?

{error}

: null} @@ -241,17 +241,17 @@ function DeleteAccessProfileAction({ export function OrganizationProfilesPanel({ organizationId, profiles, - permissions, + capabilities, actions, policy, focusedProfileId, onError }: AccessPanelProps & Readonly<{ profiles: readonly OrganizationAccessProfile[]; - permissions: readonly OrganizationPermission[]; + capabilities: readonly OrganizationCapability[]; focusedProfileId?: string; }>) { - const [pendingPermission, setPendingPermission] = React.useState(); + const [pendingCapability, setPendingCapability] = React.useState(); const canCreateProfile = canPerform(policy, 'createAccessProfile') && Boolean(actions?.createAccessProfile); const focusedProfileRef = React.useRef(null); const focusedProfilePresent = Boolean( @@ -265,25 +265,25 @@ export function OrganizationProfilesPanel({ element?.scrollIntoView?.({ block: 'nearest' }); }, [focusedProfileId, focusedProfilePresent]); - const setPermission = async ( + const setCapability = async ( profile: OrganizationAccessProfile, - permissionId: string, + capabilityId: string, isGrant: boolean ) => { - if (!actions?.setProfilePermission) return; - const pendingKey = `${profile.id}:${permissionId}`; - setPendingPermission(pendingKey); + if (!actions?.setProfileCapability) return; + const pendingKey = `${profile.id}:${capabilityId}`; + setPendingCapability(pendingKey); try { - await actions.setProfilePermission({ + await actions.setProfileCapability({ organizationId, profileId: profile.id, - permissionId, + capabilityId, isGrant }); } catch (cause) { - report(cause, 'The profile permission could not be changed.', onError); + report(cause, 'The profile capability could not be changed.', onError); } finally { - setPendingPermission(undefined); + setPendingCapability(undefined); } }; @@ -311,7 +311,7 @@ export function OrganizationProfilesPanel({ No access profiles {canCreateProfile - ? 'Create a profile to bundle organization permissions.' + ? 'Create a profile to bundle organization capabilities.' : 'No access profiles are visible, and this session cannot create one.'} @@ -319,10 +319,10 @@ export function OrganizationProfilesPanel({ ) : (
{profiles.map((profile) => { - const assigned = new Set(profile.permissionIds); - const canSet = canPerform(policy, 'setProfilePermission') && - profile.actionPolicy?.setProfilePermission === true && - Boolean(actions?.setProfilePermission); + const assigned = new Set(profile.capabilityIds); + const canSet = canPerform(policy, 'setProfileCapability') && + profile.actionPolicy?.setProfileCapability === true && + Boolean(actions?.setProfileCapability); return (
- {permissions.length > 0 ? ( + {capabilities.length > 0 ? ( - {permissions.map((permission) => { - const key = `${profile.id}:${permission.id}`; + {capabilities.map((capability) => { + const key = `${profile.id}:${capability.id}`; return ( void setPermission( + onCheckedChange={(checked) => void setCapability( profile, - permission.id, + capability.id, checked === true )} />
- {permission.name} - {permission.description ? ( - {permission.description} + {capability.name} + {capability.description ? ( + {capability.description} ) : null}
@@ -402,7 +402,7 @@ export function OrganizationProfilesPanel({
) : (

- The organization permission catalog is unavailable. + The organization capability catalog is unavailable.

)} @@ -469,7 +469,7 @@ function OrganizationGovernanceControl({ {granting ? `${label} access takes effect across this organization. Constructive applies the tenant’s configured authorization and step-up policy before accepting the grant.` - : `This removes the ${label.toLowerCase()} grant while preserving the member’s account, membership, profile, and direct permissions.`} + : `This removes the ${label.toLowerCase()} grant while preserving the member’s account, membership, profile, and direct capabilities.`} @@ -495,14 +495,14 @@ export function OrganizationMemberAccessDialog({ organizationId, member, profiles, - permissions, + capabilities, actions, policy, onError }: AccessPanelProps & Readonly<{ member: OrganizationMember; profiles: readonly OrganizationAccessProfile[]; - permissions: readonly OrganizationPermission[]; + capabilities: readonly OrganizationCapability[]; }>) { const [open, setOpen] = React.useState(false); const [pending, setPending] = React.useState(); @@ -639,7 +639,7 @@ export function OrganizationMemberAccessDialog({ {profiles.length > 0 ? (
-

Access profile

Profiles are the primary way to assign organization permissions.

+

Access profile

Profiles are the primary way to assign organization capabilities.

- Permission and subset - Permission only + Capability and subset + Capability only Subset only diff --git a/apps/blocks/src/blocks/feature-packs/users/feature-pack.json b/apps/blocks/src/blocks/feature-packs/users/feature-pack.json index 2aa160d..754a1f3 100644 --- a/apps/blocks/src/blocks/feature-packs/users/feature-pack.json +++ b/apps/blocks/src/blocks/feature-packs/users/feature-pack.json @@ -2,12 +2,12 @@ "schemaVersion": 1, "id": "users", "title": "App access", - "description": "Application member governance, lifecycle controls, profiles, permission grants, and invitations.", + "description": "Application member governance, lifecycle controls, profiles, capability grants, and invitations.", "dependencies": [], "endpoints": { "required": ["admin"], "optional": ["auth"] }, "capabilities": { "required": ["users.directory", "users.memberships"], - "optional": ["users.permissions", "users.profiles", "users.invites"] + "optional": ["users.capabilities", "users.profiles", "users.invites"] }, "metadata": { "requiredMetaSections": [], diff --git a/apps/blocks/src/blocks/feature-packs/users/users-console-module.tsx b/apps/blocks/src/blocks/feature-packs/users/users-console-module.tsx index 5864757..2fdd26b 100644 --- a/apps/blocks/src/blocks/feature-packs/users/users-console-module.tsx +++ b/apps/blocks/src/blocks/feature-packs/users/users-console-module.tsx @@ -19,7 +19,7 @@ export const usersCapabilityDiscovery = { rules: [ { capability: 'users.directory', endpoint: 'auth', operation: 'query', fields: ['users'] }, { capability: 'users.memberships', endpoint: 'admin', operation: 'query', fields: ['appMemberships'] }, - { capability: 'users.permissions', endpoint: 'admin', operation: 'query', fields: ['appPermissions'] }, + { capability: 'users.capabilities', endpoint: 'admin', operation: 'query', fields: ['appCapabilities'] }, { capability: 'users.profiles', endpoint: 'admin', operation: 'query', fields: ['appProfiles'] }, { capability: 'users.invites', endpoint: 'admin', operation: 'query', fields: ['appInvites'] } ] diff --git a/apps/blocks/src/blocks/feature-packs/users/users-feature-pack.tsx b/apps/blocks/src/blocks/feature-packs/users/users-feature-pack.tsx index d62fbf9..ee92800 100644 --- a/apps/blocks/src/blocks/feature-packs/users/users-feature-pack.tsx +++ b/apps/blocks/src/blocks/feature-packs/users/users-feature-pack.tsx @@ -118,7 +118,7 @@ export type UsersSection = | 'invitations' | 'accepted-invites' | 'profiles' - | 'permissions' + | 'capabilities' | 'defaults'; const sectionTriggerClass = cn( @@ -147,7 +147,7 @@ export type AppMemberGovernance = Readonly<{ admin: boolean; }>; -export type AppPermission = Readonly<{ +export type AppCapability = Readonly<{ id: string; name: string; description?: string; @@ -159,12 +159,12 @@ export type AppAccessProfile = Readonly<{ name: string; slug?: string; description?: string; - permissionIds: readonly string[]; + capabilityIds: readonly string[]; system?: boolean; default?: boolean; memberCount?: number; actionPolicy?: FeatureActionPolicy< - 'updateProfile' | 'deleteProfile' | 'setDefaultProfile' | 'setProfilePermission' + 'updateProfile' | 'deleteProfile' | 'setDefaultProfile' | 'setProfileCapability' >; }>; @@ -177,8 +177,8 @@ export type AppMember = Readonly<{ lifecycle: AppMemberLifecycle; governance: AppMemberGovernance; profile?: Readonly<{ id: string; name: string }>; - directPermissionIds: readonly string[]; - effectivePermissionIds: readonly string[]; + directCapabilityIds: readonly string[]; + effectiveCapabilityIds: readonly string[]; joinedAt?: string; actionPolicy?: FeatureActionPolicy< | 'setApproved' @@ -188,7 +188,7 @@ export type AppMember = Readonly<{ | 'setOwner' | 'setAdmin' | 'setProfile' - | 'setDirectPermission' + | 'setDirectCapability' >; }>; @@ -219,8 +219,8 @@ export type UsersFeatureData = Readonly<{ invitations?: readonly AppInvite[]; acceptedInvites?: readonly AppClaimedInvite[]; profiles?: readonly AppAccessProfile[]; - permissions?: readonly AppPermission[]; - defaultPermissionIds?: readonly string[]; + capabilities?: readonly AppCapability[]; + defaultCapabilityIds?: readonly string[]; inviteProfileIds?: readonly string[]; }>; @@ -234,13 +234,13 @@ export type UsersFeatureAction = | 'setOwner' | 'setAdmin' | 'setProfile' - | 'setDirectPermission' + | 'setDirectCapability' | 'createProfile' | 'updateProfile' | 'deleteProfile' | 'setDefaultProfile' - | 'setProfilePermission' - | 'setDefaultPermission' + | 'setProfileCapability' + | 'setDefaultCapability' | 'cancelInvite' | 'extendInvite'; @@ -253,9 +253,9 @@ export type UsersFeatureActions = Readonly<{ setOwner?: (input: { userId: string; owner: boolean }) => FeatureActionResult; setAdmin?: (input: { userId: string; admin: boolean }) => FeatureActionResult; setProfile?: (input: { membershipId: string; profileId?: string }) => FeatureActionResult; - setDirectPermission?: (input: { + setDirectCapability?: (input: { userId: string; - permissionId: string; + capabilityId: string; granted: boolean; }) => FeatureActionResult; createProfile?: (input: { @@ -271,13 +271,13 @@ export type UsersFeatureActions = Readonly<{ }) => FeatureActionResult; deleteProfile?: (input: { profileId: string }) => FeatureActionResult; setDefaultProfile?: (input: { profileId: string }) => FeatureActionResult; - setProfilePermission?: (input: { + setProfileCapability?: (input: { profileId: string; - permissionId: string; + capabilityId: string; granted: boolean; }) => FeatureActionResult; - setDefaultPermission?: (input: { - permissionId: string; + setDefaultCapability?: (input: { + capabilityId: string; granted: boolean; }) => FeatureActionResult; cancelInvite?: (input: { inviteId: string }) => FeatureActionResult; @@ -476,7 +476,7 @@ type AppMemberRowAction = | 'setOwner' | 'setAdmin' | 'setProfile' - | 'setDirectPermission'; + | 'setDirectCapability'; function MemberActions({ member, @@ -627,8 +627,8 @@ function MemberActions({ ? `Revoke app admin from ${member.name}?` : `Grant app admin to ${member.name}?`, description: member.governance.admin - ? 'The member will keep only profile and direct permissions.' - : 'App admins receive every application permission.', + ? 'The member will keep only profile and direct capabilities.' + : 'App admins receive every application capability.', confirmLabel: member.governance.admin ? 'Revoke app admin' : 'Grant app admin', destructive: member.governance.admin, run: () => actions.setAdmin!({ @@ -700,14 +700,14 @@ function MemberActions({ function MemberAccessDialog({ member, profiles, - permissions, + capabilities, policy, actions, onError }: Readonly<{ member: AppMember; profiles: readonly AppAccessProfile[]; - permissions: readonly AppPermission[]; + capabilities: readonly AppCapability[]; policy?: UsersFeaturePackProps['policy']; actions?: UsersFeatureActions; onError?: UsersFeaturePackProps['onError']; @@ -719,10 +719,10 @@ function MemberAccessDialog({ const rowPolicy = member.actionPolicy; const canSetProfile = canPerform(policy, 'setProfile') && canPerform(rowPolicy, 'setProfile') && Boolean(actions?.setProfile); - const canSetDirectPermission = canPerform(policy, 'setDirectPermission') && - canPerform(rowPolicy, 'setDirectPermission') && Boolean(actions?.setDirectPermission); - const directPermissionIds = new Set(member.directPermissionIds); - const effectivePermissionIds = new Set(member.effectivePermissionIds); + const canSetDirectCapability = canPerform(policy, 'setDirectCapability') && + canPerform(rowPolicy, 'setDirectCapability') && Boolean(actions?.setDirectCapability); + const directCapabilityIds = new Set(member.directCapabilityIds); + const effectiveCapabilityIds = new Set(member.effectiveCapabilityIds); const run = async (key: string, action: () => FeatureActionResult, fallback: string) => { setPendingKey(key); @@ -746,7 +746,7 @@ function MemberAccessDialog({ Access for {member.name} - Profiles and direct grants are separate inputs. Effective permissions show the result enforced by the backend. + Profiles and direct grants are separate inputs. Effective capabilities show the result enforced by the backend. @@ -786,42 +786,42 @@ function MemberAccessDialog({
-

Direct permission overrides

+

Direct capability overrides

These grants are independent from the selected profile.

- {permissions.length > 0 ? ( + {capabilities.length > 0 ? ( - {permissions.map((permission) => { - const checked = directPermissionIds.has(permission.id); + {capabilities.map((capability) => { + const checked = directCapabilityIds.has(capability.id); return ( void run( - `direct-${permission.id}`, - () => actions!.setDirectPermission!({ + `direct-${capability.id}`, + () => actions!.setDirectCapability!({ userId: member.userId, - permissionId: permission.id, + capabilityId: capability.id, granted: nextChecked === true }), - 'The direct permission could not be changed.' + 'The direct capability could not be changed.' )} />
- - {permission.name} + + {capability.name} - {permission.description ? ( - {permission.description} + {capability.description ? ( + {capability.description} ) : null}
@@ -830,24 +830,24 @@ function MemberAccessDialog({
) : (

- The permission catalog is unavailable for this tenant. + The capability catalog is unavailable for this tenant.

)}
-

Effective permissions

+

Effective capabilities

This read-only result includes profile, direct, admin, and owner access.

- {permissions.filter((permission) => effectivePermissionIds.has(permission.id)).map( - (permission) => {permission.name} + {capabilities.filter((capability) => effectiveCapabilityIds.has(capability.id)).map( + (capability) => {capability.name} )} - {effectivePermissionIds.size === 0 ? ( - No effective permissions + {effectiveCapabilityIds.size === 0 ? ( + No effective capabilities ) : null}
@@ -866,7 +866,7 @@ function MembersDirectory({ query, onClearSearch, profiles, - permissions, + capabilities, focusedMemberId, policy, actions, @@ -876,7 +876,7 @@ function MembersDirectory({ query: string; onClearSearch: () => void; profiles: readonly AppAccessProfile[]; - permissions: readonly AppPermission[]; + capabilities: readonly AppCapability[]; focusedMemberId?: string; policy?: UsersFeaturePackProps['policy']; actions?: UsersFeatureActions; @@ -954,12 +954,12 @@ function MembersDirectory({ {member.governance.owner ? Owner : null} {!member.governance.owner && member.governance.admin ? Admin : null} - {(profiles.length > 0 || permissions.length > 0) ? ( + {(profiles.length > 0 || capabilities.length > 0) ? ( @@ -1264,7 +1264,7 @@ function ProfileFormDialog({ {profile ? `Edit ${profile.name}` : 'Create an access profile'} - Profiles bundle permissions. Application ownership and admin governance stay separate. + Profiles bundle capabilities. Application ownership and admin governance stay separate. @@ -1319,14 +1319,14 @@ function ProfileFormDialog({ function ProfileRow({ profile, - permissions, + capabilities, policy, actions, focused, onError }: Readonly<{ profile: AppAccessProfile; - permissions: readonly AppPermission[]; + capabilities: readonly AppCapability[]; policy?: UsersFeaturePackProps['policy']; actions?: UsersFeatureActions; focused?: boolean; @@ -1341,9 +1341,9 @@ function ProfileRow({ canPerform(profilePolicy, 'deleteProfile') && Boolean(actions?.deleteProfile); const canSetDefault = canPerform(policy, 'setDefaultProfile') && canPerform(profilePolicy, 'setDefaultProfile') && Boolean(actions?.setDefaultProfile); - const canCompose = canPerform(policy, 'setProfilePermission') && - canPerform(profilePolicy, 'setProfilePermission') && Boolean(actions?.setProfilePermission); - const profilePermissionIds = new Set(profile.permissionIds); + const canCompose = canPerform(policy, 'setProfileCapability') && + canPerform(profilePolicy, 'setProfileCapability') && Boolean(actions?.setProfileCapability); + const profileCapabilityIds = new Set(profile.capabilityIds); const fieldId = React.useId(); const focusedProfileRef = React.useRef(null); @@ -1384,7 +1384,7 @@ function ProfileRow({ {profile.description ?? profile.slug ?? 'No description'}

- {profile.permissionIds.length} {profile.permissionIds.length === 1 ? 'permission' : 'permissions'} + {profile.capabilityIds.length} {profile.capabilityIds.length === 1 ? 'capability' : 'capabilities'} {profile.memberCount !== undefined ? ` · ${profile.memberCount} members` : ''}

@@ -1418,7 +1418,7 @@ function ProfileRow({ Delete {profile.name}? - Members assigned to this profile may lose profile-based permissions. This cannot be undone. + Members assigned to this profile may lose profile-based capabilities. This cannot be undone. {error ?

{error}

: null} @@ -1441,34 +1441,34 @@ function ProfileRow({ ) : null} - {permissions.length > 0 ? ( + {capabilities.length > 0 ? ( - {permissions.map((permission) => { - const checked = profilePermissionIds.has(permission.id); + {capabilities.map((capability) => { + const checked = profileCapabilityIds.has(capability.id); return ( void run( - `permission-${permission.id}`, - () => actions!.setProfilePermission!({ + `capability-${capability.id}`, + () => actions!.setProfileCapability!({ profileId: profile.id, - permissionId: permission.id, + capabilityId: capability.id, granted: nextChecked === true }), - 'The profile permission could not be changed.' + 'The profile capability could not be changed.' )} />
- {permission.name} - {permission.description ? {permission.description} : null} + {capability.name} + {capability.description ? {capability.description} : null}
); @@ -1482,14 +1482,14 @@ function ProfileRow({ function ProfilesDirectory({ profiles, - permissions, + capabilities, focusedProfileId, policy, actions, onError }: Readonly<{ profiles: readonly AppAccessProfile[]; - permissions: readonly AppPermission[]; + capabilities: readonly AppCapability[]; focusedProfileId?: string; policy?: UsersFeaturePackProps['policy']; actions?: UsersFeatureActions; @@ -1503,7 +1503,7 @@ function ProfilesDirectory({

Access profiles

- Named permission bundles for application members. Owner and admin grants remain separate. + Named capability bundles for application members. Owner and admin grants remain separate.

{canCreate && actions?.createProfile ? ( @@ -1518,7 +1518,7 @@ function ProfilesDirectory({ focused={profile.id === focusedProfileId} key={profile.id} onError={onError} - permissions={permissions} + capabilities={capabilities} policy={policy} profile={profile} /> @@ -1529,7 +1529,7 @@ function ProfilesDirectory({ No access profiles - Create a reusable permission bundle for application members. + Create a reusable capability bundle for application members. {canCreate && actions?.createProfile ? ( @@ -1540,14 +1540,14 @@ function ProfilesDirectory({ ); } -function PermissionsCatalog({ permissions }: Readonly<{ permissions: readonly AppPermission[] }>) { - if (permissions.length === 0) { +function CapabilitiesCatalog({ capabilities }: Readonly<{ capabilities: readonly AppCapability[] }>) { + if (capabilities.length === 0) { return ( - No application permissions - This tenant does not expose a permission catalog. + No application capabilities + This tenant does not expose a capability catalog. ); @@ -1557,9 +1557,9 @@ function PermissionsCatalog({ permissions }: Readonly<{ permissions: readonly Ap
- Permission + Capability Description Bit - {permissions.map((permission) => ( - + {capabilities.map((capability) => ( + - {permission.name} + {capability.name} - {permission.description ?? 'No description'} + {capability.description ?? 'No description'} - Bit {permission.bit ?? '—'} - {permission.bit ?? '—'} + Bit {capability.bit ?? '—'} + {capability.bit ?? '—'} ))} @@ -1594,34 +1594,34 @@ function PermissionsCatalog({ permissions }: Readonly<{ permissions: readonly Ap ); } -function PermissionDefaults({ - permissions, - defaultPermissionIds, +function CapabilityDefaults({ + capabilities, + defaultCapabilityIds, policy, actions, onError }: Readonly<{ - permissions: readonly AppPermission[]; - defaultPermissionIds: readonly string[]; + capabilities: readonly AppCapability[]; + defaultCapabilityIds: readonly string[]; policy?: UsersFeaturePackProps['policy']; actions?: UsersFeatureActions; onError?: UsersFeaturePackProps['onError']; }>) { const [pendingId, setPendingId] = React.useState(); const [error, setError] = React.useState(); - const canUpdate = canPerform(policy, 'setDefaultPermission') && - Boolean(actions?.setDefaultPermission); - const defaults = new Set(defaultPermissionIds); + const canUpdate = canPerform(policy, 'setDefaultCapability') && + Boolean(actions?.setDefaultCapability); + const defaults = new Set(defaultCapabilityIds); const fieldId = React.useId(); - const update = async (permissionId: string, granted: boolean) => { - if (!actions?.setDefaultPermission) return; - setPendingId(permissionId); + const update = async (capabilityId: string, granted: boolean) => { + if (!actions?.setDefaultCapability) return; + setPendingId(capabilityId); setError(undefined); try { - await actions.setDefaultPermission({ permissionId, granted }); + await actions.setDefaultCapability({ capabilityId, granted }); } catch (cause) { - reportActionError(cause, 'The default permission could not be changed.', setError, onError); + reportActionError(cause, 'The default capability could not be changed.', setError, onError); } finally { setPendingId(undefined); } @@ -1630,34 +1630,34 @@ function PermissionDefaults({ return (
-

New-member permission defaults

+

New-member capability defaults

- These permissions apply to new application memberships. Existing members keep their current grants. + These capabilities apply to new application memberships. Existing members keep their current grants.

- {permissions.length > 0 ? ( + {capabilities.length > 0 ? ( - {permissions.map((permission) => { - const checked = defaults.has(permission.id); + {capabilities.map((capability) => { + const checked = defaults.has(capability.id); return ( void update( - permission.id, + capability.id, nextChecked === true )} />
- {permission.name} - {permission.description ? {permission.description} : null} + {capability.name} + {capability.description ? {capability.description} : null}
); @@ -1665,7 +1665,7 @@ function PermissionDefaults({
) : (

- Permission defaults require the application permission catalog. + Capability defaults require the application capability catalog.

)} {error ?

{error}

: null} @@ -1719,7 +1719,7 @@ export function UsersFeaturePack({ > {(data) => { const profiles = data.profiles ?? []; - const permissions = data.permissions ?? []; + const capabilities = data.capabilities ?? []; const invitations = data.invitations ?? []; const acceptedInvites = data.acceptedInvites ?? []; const members = data.members.filter((member) => { @@ -1740,10 +1740,10 @@ export function UsersFeaturePack({ if (data.profiles !== undefined) { sections.push({ id: 'profiles', label: 'Profiles', count: profiles.length }); } - if (data.permissions !== undefined) { - sections.push({ id: 'permissions', label: 'Permissions', count: permissions.length }); + if (data.capabilities !== undefined) { + sections.push({ id: 'capabilities', label: 'Capabilities', count: capabilities.length }); } - if (data.defaultPermissionIds !== undefined) { + if (data.defaultCapabilityIds !== undefined) { sections.push({ id: 'defaults', label: 'Defaults' }); } const requestedSection = controlledSection ?? internalSection; @@ -1856,7 +1856,7 @@ export function UsersFeaturePack({ members={members} onClearSearch={() => setQuery('')} onError={onError} - permissions={permissions} + capabilities={capabilities} policy={policy} profiles={profiles} query={query} @@ -1883,25 +1883,25 @@ export function UsersFeaturePack({ ) : null} - {data.permissions !== undefined ? ( - - + {data.capabilities !== undefined ? ( + + ) : null} - {data.defaultPermissionIds !== undefined ? ( + {data.defaultCapabilityIds !== undefined ? ( - diff --git a/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-canvas.tsx b/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-canvas.tsx index bebb174..4c2651a 100644 --- a/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-canvas.tsx +++ b/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-canvas.tsx @@ -298,8 +298,8 @@ export function FeaturePackShowcaseCanvas({ recordAction(`setAdmin('${userId}', ${admin})`), setProfile: ({ membershipId, profileId }) => recordAction(`setProfile('${membershipId}', '${profileId ?? ''}')`), - setDirectPermission: ({ userId, permissionId, granted }) => - recordAction(`setDirectPermission('${userId}', '${permissionId}', ${granted})`), + setDirectCapability: ({ userId, capabilityId, granted }) => + recordAction(`setDirectCapability('${userId}', '${capabilityId}', ${granted})`), createProfile: ({ name, slug }) => recordAction(`createProfile('${name}', '${slug}')`), updateProfile: ({ profileId, name, slug }) => @@ -307,10 +307,10 @@ export function FeaturePackShowcaseCanvas({ deleteProfile: ({ profileId }) => recordAction(`deleteProfile('${profileId}')`), setDefaultProfile: ({ profileId }) => recordAction(`setDefaultProfile('${profileId}')`), - setProfilePermission: ({ profileId, permissionId, granted }) => - recordAction(`setProfilePermission('${profileId}', '${permissionId}', ${granted})`), - setDefaultPermission: ({ permissionId, granted }) => - recordAction(`setDefaultPermission('${permissionId}', ${granted})`), + setProfileCapability: ({ profileId, capabilityId, granted }) => + recordAction(`setProfileCapability('${profileId}', '${capabilityId}', ${granted})`), + setDefaultCapability: ({ capabilityId, granted }) => + recordAction(`setDefaultCapability('${capabilityId}', ${granted})`), cancelInvite: ({ inviteId }) => recordAction(`cancelInvite('${inviteId}')`), extendInvite: ({ inviteId }) => recordAction(`extendInvite('${inviteId}')`), }} @@ -325,13 +325,13 @@ export function FeaturePackShowcaseCanvas({ setOwner: true, setAdmin: true, setProfile: true, - setDirectPermission: true, + setDirectCapability: true, createProfile: true, updateProfile: true, deleteProfile: true, setDefaultProfile: true, - setProfilePermission: true, - setDefaultPermission: true, + setProfileCapability: true, + setDefaultCapability: true, cancelInvite: true, extendInvite: true, }} @@ -357,8 +357,8 @@ export function FeaturePackShowcaseCanvas({ recordAction(`setMemberOwner('${organizationId}', '${actorId}', ${isGrant})`), setMemberProfile: ({ organizationId, membershipId, profileId, isGrant }) => recordAction(`setMemberProfile('${organizationId}', '${membershipId}', '${profileId}', ${isGrant})`), - setMemberPermission: ({ organizationId, actorId, permissions, isGrant }) => - recordAction(`setMemberPermission('${organizationId}', '${actorId}', '${permissions}', ${isGrant})`), + setMemberCapability: ({ organizationId, actorId, capabilities, isGrant }) => + recordAction(`setMemberCapability('${organizationId}', '${actorId}', '${capabilities}', ${isGrant})`), removeMember: ({ organizationId, membershipId }) => recordAction(`removeMember('${organizationId}', '${membershipId}')`), cancelInvite: ({ organizationId, inviteId }) => @@ -378,7 +378,7 @@ export function FeaturePackShowcaseCanvas({ grantAdmin: true, grantOwner: true, assignProfile: true, - grantPermission: true, + grantCapability: true, removeMember: true, cancelInvite: true, }} diff --git a/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-resources.ts b/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-resources.ts index 65d7717..d49c76a 100644 --- a/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-resources.ts +++ b/apps/blocks/src/components/feature-pack-showcase/feature-pack-showcase-resources.ts @@ -103,21 +103,21 @@ export const FEATURE_PACK_SHOWCASE_AUTH_ACCOUNT: AuthAccountData = { }; export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { - permissions: [ + capabilities: [ { - id: 'permission_admin_members', + id: 'capability_admin_members', name: 'admin_members', description: 'Manage application member lifecycle and access grants.', bit: 2, }, { - id: 'permission_create_invites', + id: 'capability_create_invites', name: 'create_invites', description: 'Create invitations to this application.', bit: 4, }, { - id: 'permission_create_entity', + id: 'capability_create_entity', name: 'create_entity', description: 'Create application-owned records.', bit: 8, @@ -129,10 +129,10 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { name: 'Operator', slug: 'operator', description: 'Manage members and day-to-day application records.', - permissionIds: [ - 'permission_admin_members', - 'permission_create_invites', - 'permission_create_entity', + capabilityIds: [ + 'capability_admin_members', + 'capability_create_invites', + 'capability_create_entity', ], default: true, memberCount: 1, @@ -140,7 +140,7 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { updateProfile: true, deleteProfile: true, setDefaultProfile: true, - setProfilePermission: true, + setProfileCapability: true, }, }, { @@ -148,17 +148,17 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { name: 'Contributor', slug: 'contributor', description: 'Create records without membership administration access.', - permissionIds: ['permission_create_entity'], + capabilityIds: ['capability_create_entity'], memberCount: 1, actionPolicy: { updateProfile: true, deleteProfile: true, setDefaultProfile: true, - setProfilePermission: true, + setProfileCapability: true, }, }, ], - defaultPermissionIds: ['permission_create_entity'], + defaultCapabilityIds: ['capability_create_entity'], inviteProfileIds: ['profile_operator', 'profile_contributor'], members: [ { @@ -175,11 +175,11 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { }, governance: { owner: true, admin: true }, profile: { id: 'profile_operator', name: 'Operator' }, - directPermissionIds: [], - effectivePermissionIds: [ - 'permission_admin_members', - 'permission_create_invites', - 'permission_create_entity', + directCapabilityIds: [], + effectiveCapabilityIds: [ + 'capability_admin_members', + 'capability_create_invites', + 'capability_create_entity', ], joinedAt: 'Jan 12, 2025', actionPolicy: { @@ -190,7 +190,7 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { setOwner: false, setAdmin: false, setProfile: true, - setDirectPermission: true, + setDirectCapability: true, }, }, { @@ -207,11 +207,11 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { }, governance: { owner: false, admin: true }, profile: { id: 'profile_operator', name: 'Operator' }, - directPermissionIds: [], - effectivePermissionIds: [ - 'permission_admin_members', - 'permission_create_invites', - 'permission_create_entity', + directCapabilityIds: [], + effectiveCapabilityIds: [ + 'capability_admin_members', + 'capability_create_invites', + 'capability_create_entity', ], joinedAt: 'Mar 8, 2025', actionPolicy: { @@ -222,7 +222,7 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { setOwner: true, setAdmin: true, setProfile: true, - setDirectPermission: true, + setDirectCapability: true, }, }, { @@ -239,8 +239,8 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { }, governance: { owner: false, admin: false }, profile: { id: 'profile_contributor', name: 'Contributor' }, - directPermissionIds: ['permission_create_invites'], - effectivePermissionIds: ['permission_create_invites', 'permission_create_entity'], + directCapabilityIds: ['capability_create_invites'], + effectiveCapabilityIds: ['capability_create_invites', 'capability_create_entity'], joinedAt: 'Jun 18, 2025', actionPolicy: { setApproved: true, @@ -250,7 +250,7 @@ export const FEATURE_PACK_SHOWCASE_USERS: UsersFeatureData = { setOwner: true, setAdmin: true, setProfile: true, - setDirectPermission: true, + setDirectCapability: true, }, }, ], @@ -325,7 +325,7 @@ export const FEATURE_PACK_SHOWCASE_ORGANIZATIONS: OrganizationsFeatureData = { actionPolicy: { grantOwner: true, assignProfile: true, - grantPermission: true, + grantCapability: true, updateMemberProfile: true, }, }, @@ -354,7 +354,7 @@ export const FEATURE_PACK_SHOWCASE_ORGANIZATIONS: OrganizationsFeatureData = { grantAdmin: true, grantOwner: true, assignProfile: true, - grantPermission: true, + grantCapability: true, updateMemberProfile: true, }, }, @@ -383,7 +383,7 @@ export const FEATURE_PACK_SHOWCASE_ORGANIZATIONS: OrganizationsFeatureData = { grantAdmin: true, grantOwner: true, assignProfile: true, - grantPermission: true, + grantCapability: true, updateMemberProfile: true, }, }, @@ -403,37 +403,37 @@ export const FEATURE_PACK_SHOWCASE_ORGANIZATIONS: OrganizationsFeatureData = { actionPolicy: { cancelInvite: true }, }, ], - permissions: [ - { id: 'org_permission_members', name: 'Manage members', bitstr: '01' }, - { id: 'org_permission_billing', name: 'View billing', bitstr: '10' }, + capabilities: [ + { id: 'org_capability_members', name: 'Manage members', bitstr: '01' }, + { id: 'org_capability_billing', name: 'View billing', bitstr: '10' }, ], profiles: [ { id: 'org_profile_admin', name: 'Administrator', description: 'Manages organization members and billing.', - permissions: '11', - permissionIds: ['org_permission_members', 'org_permission_billing'], + capabilities: '11', + capabilityIds: ['org_capability_members', 'org_capability_billing'], isSystem: false, isDefault: false, actionPolicy: { updateAccessProfile: true, deleteAccessProfile: true, - setProfilePermission: true, + setProfileCapability: true, }, }, { id: 'org_profile_member', name: 'Member', description: 'Standard organization access.', - permissions: '00', - permissionIds: [], + capabilities: '00', + capabilityIds: [], isSystem: false, isDefault: true, actionPolicy: { updateAccessProfile: true, deleteAccessProfile: true, - setProfilePermission: true, + setProfileCapability: true, }, }, ], diff --git a/apps/blocks/src/feature-packs/capabilities.ts b/apps/blocks/src/feature-packs/capabilities.ts index c605015..fa24c1d 100644 --- a/apps/blocks/src/feature-packs/capabilities.ts +++ b/apps/blocks/src/feature-packs/capabilities.ts @@ -15,11 +15,11 @@ export const ATOMIC_CAPABILITY_IDS = [ 'auth.devices', 'users.directory', 'users.memberships', - 'users.permissions', + 'users.capabilities', 'users.profiles', 'users.invites', 'organizations.memberships', - 'organizations.permissions', + 'organizations.capabilities', 'organizations.limits', 'organizations.profiles', 'organizations.hierarchy', @@ -63,13 +63,13 @@ export const CAPABILITY_IDS_BY_FEATURE_PACK = { users: [ 'users.directory', 'users.memberships', - 'users.permissions', + 'users.capabilities', 'users.profiles', 'users.invites' ], organizations: [ 'organizations.memberships', - 'organizations.permissions', + 'organizations.capabilities', 'organizations.limits', 'organizations.profiles', 'organizations.hierarchy', diff --git a/apps/blocks/src/feature-packs/catalog.ts b/apps/blocks/src/feature-packs/catalog.ts index e7e9ee0..3e8c828 100644 --- a/apps/blocks/src/feature-packs/catalog.ts +++ b/apps/blocks/src/feature-packs/catalog.ts @@ -87,7 +87,7 @@ export const USERS_FEATURE_PACK = { id: 'users', title: 'App access', description: - 'Application member governance, lifecycle controls, profiles, permission grants, and invitations.', + 'Application member governance, lifecycle controls, profiles, capability grants, and invitations.', dependencies: [] as FeaturePackManifestV1['dependencies'], endpoints: { required: ['admin'], @@ -99,7 +99,7 @@ export const USERS_FEATURE_PACK = { 'users.memberships' ], optional: [ - 'users.permissions', + 'users.capabilities', 'users.profiles', 'users.invites' ] @@ -121,7 +121,7 @@ export const ORGANIZATIONS_FEATURE_PACK = { capabilities: { required: ['organizations.memberships'], optional: [ - 'organizations.permissions', + 'organizations.capabilities', 'organizations.limits', 'organizations.profiles', 'organizations.hierarchy', diff --git a/apps/blocks/src/lib/feature-packs.ts b/apps/blocks/src/lib/feature-packs.ts index dfe87ec..acc21db 100644 --- a/apps/blocks/src/lib/feature-packs.ts +++ b/apps/blocks/src/lib/feature-packs.ts @@ -248,23 +248,23 @@ export const FEATURE_PACK_DOCS = [ title: 'App access', exportName: 'UsersFeaturePack', description: - 'Application member governance, lifecycle controls, access profiles, permission grants, and invitations.', + 'Application member governance, lifecycle controls, access profiles, capability grants, and invitations.', endpoints: 'admin, optional auth', dependencies: [], resource: 'FeaturePackResource', actions: [ 'invite({ recipient, profileId? }) / cancelInvite / extendInvite', 'setApproved / setVerified / setBanned / setDisabled', - 'setOwner / setAdmin / setProfile / setDirectPermission', + 'setOwner / setAdmin / setProfile / setDirectCapability', 'profile CRUD / profile composition / new-member defaults', ], whenToUse: [ - 'Use App access when administrators need to govern access to one application, including invitations, membership lifecycle, ownership, administration, profiles, and permission grants.', + 'Use App access when administrators need to govern access to one application, including invitations, membership lifecycle, ownership, administration, profiles, and capability grants.', 'Use the Authentication feature pack for personal credentials and sessions, or Organizations when membership must be scoped to a selected tenant.', ], usage: { description: - 'Pass members and any discovered invitation, profile, permission, default, or audit sections through resource. Grant each administrative operation through policy and provide the matching host action.', + 'Pass members and any discovered invitation, profile, capability, default, or audit sections through resource. Grant each administrative operation through policy and provide the matching host action.', example: `import { UsersFeaturePack } from '@/blocks/feature-packs/users/users-feature-pack'; ', behavior: - 'Supplies members and optional invitations, accepted-invite history, profiles, permissions, defaults, and their loading, empty, error, or ready state.', + 'Supplies members and optional invitations, accepted-invite history, profiles, capabilities, defaults, and their loading, empty, error, or ready state.', }, { name: 'policy / actions', @@ -359,8 +359,8 @@ export const FEATURE_PACK_DOCS = [ 'createOrganization / selectOrganization / updateOrganization / leaveOrganization / deleteOrganization', 'inviteMember / cancelInvite', 'updateMemberLifecycle / removeMember / setMemberAdmin / setMemberOwner', - 'setMemberProfile / setMemberPermission / upsertMemberProfile', - 'createAccessProfile / updateAccessProfile / deleteAccessProfile / setProfilePermission', + 'setMemberProfile / setMemberCapability / upsertMemberProfile', + 'createAccessProfile / updateAccessProfile / deleteAccessProfile / setProfileCapability', 'updateMembershipDefault / updateMembershipSettings', 'setHierarchyEdge / removeHierarchyEdge', 'createOrganizationPrincipal / revokeOrganizationPrincipal', @@ -387,15 +387,15 @@ export const FEATURE_PACK_DOCS = [ state: { title: 'Tenant, section, and authorization state', description: - 'Use defaultSection when the page can remember its own organization-management section. Pass section and onSectionChange when routing or application state owns the selection. The resource keeps the visible organization directory and active tenant data in one snapshot; optional data controls invitations, profiles, permissions, defaults, hierarchy, and developer sections, while membership settings extend the tenant settings section when readable.', + 'Use defaultSection when the page can remember its own organization-management section. Pass section and onSectionChange when routing or application state owns the selection. The resource keeps the visible organization directory and active tenant data in one snapshot; optional data controls invitations, profiles, capabilities, defaults, hierarchy, and developer sections, while membership settings extend the tenant settings section when readable.', actionGuidance: 'A pack policy grant, matching callback, and any row policy enable each action; PostgreSQL privileges and RLS remain authoritative after the control is shown. Refresh the resource after a successful mutation, treat reusable invitation tokens as secrets, and expose new API keys only for the response that creates them. Because create_org_principal returns user_id while public reads may omit its organization scope, Console Kit must fail closed and surface a limitation for an unscopable keyless principal rather than guessing the only visible organization.', }, surfaces: [ 'Organization selection and creation, plus tenant identity settings, leave, and deletion controls.', - 'Members with governance, lifecycle status, access-profile assignment, direct permission exceptions, and organization member profiles.', + 'Members with governance, lifecycle status, access-profile assignment, direct capability exceptions, and organization member profiles.', 'Invitations by email, SMS, or reusable link with profile assignment, expiry, reusable claim limits and counts, token copying, cancellation, and accepted-invite history.', - 'Access-profile creation, editing, deletion, and permission grants alongside a readable permission catalog.', + 'Access-profile creation, editing, deletion, and capability grants alongside a readable capability catalog.', 'Membership defaults and tenant-wide membership settings for approval, inheritance, external access, invitation assignment, and limit allocation.', 'Organization hierarchy edges with position metadata and cycle-safe creation and removal controls.', 'Developer credentials with service-principal creation and revocation, one-time API-key issuance, and key revocation.', @@ -425,7 +425,7 @@ export const FEATURE_PACK_DOCS = [ name: 'resource', type: 'FeaturePackResource', behavior: - 'Supplies organizations and active tenant context, members, invitations and claim history, access profiles, permissions, defaults, settings, hierarchy, principals, API keys, and any fail-closed limitations.', + 'Supplies organizations and active tenant context, members, invitations and claim history, access profiles, capabilities, defaults, settings, hierarchy, principals, API keys, and any fail-closed limitations.', }, { name: 'policy / actions', @@ -437,7 +437,7 @@ export const FEATURE_PACK_DOCS = [ name: 'section / defaultSection / onSectionChange', type: 'OrganizationsSection / callback', behavior: - 'Uses either a controlled section or an initial selection across members, invitations, profiles, permissions, defaults, hierarchy, settings, and developer credentials, then reports section changes to the host.', + 'Uses either a controlled section or an initial selection across members, invitations, profiles, capabilities, defaults, hierarchy, settings, and developer credentials, then reports section changes to the host.', }, { name: 'focusedMemberId / focusedInvitationId / focusedProfileId', diff --git a/apps/blocks/src/lib/source-blocks.ts b/apps/blocks/src/lib/source-blocks.ts index 519b394..3ac077d 100644 --- a/apps/blocks/src/lib/source-blocks.ts +++ b/apps/blocks/src/lib/source-blocks.ts @@ -111,7 +111,7 @@ export default function RootLayout({ description: 'Sheets scopes TanStack Query state by database, endpoint, and a stable non-secret identity. Its modular Zustand store owns interaction state, while renamed and composite primary-key objects remain intact across reads and writes.', actionGuidance: - 'Treat _meta as capability evidence, not permission. The host supplies the active session and reports errors, while PostgreSQL privileges and RLS authorize every query and mutation at the tenant endpoint.', + 'Treat _meta as capability evidence, not capability. The host supplies the active session and reports errors, while PostgreSQL privileges and RLS authorize every query and mutation at the tenant endpoint.', }, composition: [ 'The installed source owns the grid, editors, selection, draft rows, commands, feedback, and metadata-aware row identity.', @@ -253,7 +253,7 @@ export function DatabaseSchema({ description: 'Each mounted editor owns an isolated modular Zustand store for its active tab, table and field selection, and presentation preferences. TanStack Query owns server state; a matching dataState can reuse a host-owned schema query boundary.', actionGuidance: - 'The adapter receives explicit control-plane scope and operation context. Keep endpoints, generated SDKs, credentials, permission evidence, destructive confirmations, and business workflows in the host; PostgreSQL permissions remain authoritative.', + 'The adapter receives explicit control-plane scope and operation context. Keep endpoints, generated SDKs, credentials, capability evidence, destructive confirmations, and business workflows in the host; PostgreSQL capabilities remain authoritative.', }, composition: [ 'The installed source owns schema navigation, editors, diagrams, validation, mutation intent, loading states, and accessible interaction.', diff --git a/packages/data/src/__tests__/fixtures.ts b/packages/data/src/__tests__/fixtures.ts index 01c2027..b05755a 100644 --- a/packages/data/src/__tests__/fixtures.ts +++ b/packages/data/src/__tests__/fixtures.ts @@ -462,7 +462,7 @@ export const filterFixtures: Record = { and: [{ isActive: { equalTo: true } }, { age: { greaterThan: 18 } }], or: [ { role: { equalTo: "admin" } }, - { permissions: { contains: ["write"] } }, + { capabilities: { contains: ["write"] } }, ], }, @@ -599,7 +599,7 @@ export const errorFixtures = { ], }, - graphqlPermissionError: { + graphqlCapabilityError: { errors: [ { message: 'Access denied for field "sensitiveData"', diff --git a/packages/data/src/__tests__/policy-provisioning.test.ts b/packages/data/src/__tests__/policy-provisioning.test.ts index 1e44f9e..20294e0 100644 --- a/packages/data/src/__tests__/policy-provisioning.test.ts +++ b/packages/data/src/__tests__/policy-provisioning.test.ts @@ -33,7 +33,7 @@ describe('policy-provisioning', () => { expect( sanitizePolicyData({ entity_field: 'owner_id', - permission: '', + capability: '', extra: undefined, fields: [], is_admin: false, diff --git a/packages/data/src/policy-provisioning.ts b/packages/data/src/policy-provisioning.ts index 2e12ebf..250a580 100644 --- a/packages/data/src/policy-provisioning.ts +++ b/packages/data/src/policy-provisioning.ts @@ -5,7 +5,7 @@ export const MEMBERSHIP_TYPES = { } as const; export type PolicyFieldType = 'string' | 'boolean' | 'integer' | 'uuid' | 'uuid[]'; -export type PolicyFieldComponent = 'table-select' | 'membership-type-select' | 'permission-select'; +export type PolicyFieldComponent = 'table-select' | 'membership-type-select' | 'capability-select'; export type PolicyProvisioningCategory = 'has-module' | 'needs-fields' | 'needs-table' | 'no-fields'; export interface PolicyFieldOverride { @@ -102,9 +102,9 @@ export const POLICY_PROVISIONING_CONFIG: Record { +export function fetchAppCapabilitiesQuery(): Promise { return Promise.reject(new Error('Direct generated fetch functions are available only in the shadcn registry build')); } -export function fetchOrgPermissionsQuery(): Promise { +export function fetchOrgCapabilitiesQuery(): Promise { return Promise.reject(new Error('Direct generated fetch functions are available only in the shadcn registry build')); } export function useAdminSdkClient() { const { adapter, scope } = useSchemaBuilder(); return { - fetchAppPermissionsQuery: (variables: SchemaBuilderVariables, signal?: AbortSignal) => - adapter.policies.appPermissions(variables, { scope, signal }) as Promise, - fetchOrgPermissionsQuery: (variables: SchemaBuilderVariables, signal?: AbortSignal) => - adapter.policies.orgPermissions(variables, { scope, signal }) as Promise + fetchAppCapabilitiesQuery: (variables: SchemaBuilderVariables, signal?: AbortSignal) => + adapter.policies.appCapabilities(variables, { scope, signal }) as Promise, + fetchOrgCapabilitiesQuery: (variables: SchemaBuilderVariables, signal?: AbortSignal) => + adapter.policies.orgCapabilities(variables, { scope, signal }) as Promise }; } diff --git a/packages/schema-builder/src/policies/index.ts b/packages/schema-builder/src/policies/index.ts index 8bf2f69..c0168d0 100644 --- a/packages/schema-builder/src/policies/index.ts +++ b/packages/schema-builder/src/policies/index.ts @@ -1,6 +1,6 @@ export { PoliciesView } from '../schema/schema-builder-policies/components/table-editor/policies'; export { TablePolicyConfigCard } from '../schema/schema-builder-policies/components/policies/policy-config-card'; -export { usePermissions } from '../schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-permissions'; +export { useCapabilities } from '../schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-capabilities'; export { useBatchCreateTableGrants, useCreateTableGrant diff --git a/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-config.ts b/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-config.ts index 4f8217c..a93050f 100644 --- a/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-config.ts +++ b/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-config.ts @@ -86,8 +86,8 @@ export const POLICY_TYPE_UI_CONFIG: Record = { }, permission: { type: 'string', - component: 'permission-select', - label: 'Required Permission', + component: 'capability-select', + label: 'Required Capability', description: paramOf(NodeTypes.AuthzEntityMembership)?.permission?.description, }, is_admin: { @@ -132,8 +132,8 @@ export const POLICY_TYPE_UI_CONFIG: Record = { fieldOverrides: { permission: { type: 'string', - component: 'permission-select', - label: 'Required Permission', + component: 'capability-select', + label: 'Required Capability', description: paramOf(NodeTypes.AuthzAppMembership)?.permission?.description, }, is_admin: { @@ -328,8 +328,8 @@ export const POLICY_TYPE_UI_CONFIG: Record = { }, permission: { type: 'string', - component: 'permission-select', - label: 'Required Permission', + component: 'capability-select', + label: 'Required Capability', description: paramOf(NodeTypes.AuthzRelatedEntityMembership)?.permission?.description, }, is_admin: { diff --git a/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-types.ts b/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-types.ts index c17cc63..2670f9c 100644 --- a/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-types.ts +++ b/packages/schema-builder/src/schema/schema-builder-core/components/policies/policy-types.ts @@ -127,10 +127,10 @@ export interface CreateTableWithPoliciesInput { /** * Shared policy data that applies to all operations. * Contains field references, table references, and structural params. - * Per-op params (permission, is_admin, is_owner) are taken from each operation. + * Per-op params (capability, is_admin, is_owner) are taken from each operation. */ sharedPolicyData: Record; - /** Per-operation configs (roleName, isPermissive, per-op policyData like permission) */ + /** Per-operation configs (roleName, isPermissive, per-op policyData like capability) */ operations: CrudPolicyConfigs; /** Which operations to create policies for. Defaults to all 4 if not specified. */ enabledOperations?: CrudOperation[]; @@ -152,7 +152,7 @@ export type FieldType = 'string' | 'boolean' | 'integer' | 'uuid' | 'uuid[]'; /** * Specialized form component override. Takes priority over type-based resolution. */ -export type FieldComponent = 'table-select' | 'membership-type-select' | 'permission-select'; +export type FieldComponent = 'table-select' | 'membership-type-select' | 'capability-select'; /** * Field override for customizing how a policy parameter is displayed in the form. @@ -264,7 +264,7 @@ export type FormFieldType = | 'field-multi-select' | 'table-select' | 'membership-type-select' - | 'permission-select' + | 'capability-select' | 'boolean' | 'number'; diff --git a/packages/schema-builder/src/schema/schema-builder-core/context/block-config.tsx b/packages/schema-builder/src/schema/schema-builder-core/context/block-config.tsx index 19ff1ee..f5f6a96 100644 --- a/packages/schema-builder/src/schema/schema-builder-core/context/block-config.tsx +++ b/packages/schema-builder/src/schema/schema-builder-core/context/block-config.tsx @@ -26,7 +26,7 @@ import { fetchUniqueConstraintsQuery, } from '@/generated/schema-builder'; import { fetchUsersQuery } from '@/generated/auth/hooks'; -import { fetchAppPermissionsQuery, fetchOrgPermissionsQuery } from '@/generated/admin/hooks'; +import { fetchAppCapabilitiesQuery, fetchOrgCapabilitiesQuery } from '@/generated/admin/hooks'; export interface SchemaBuilderScope { orgId: string; @@ -299,5 +299,5 @@ export function useAuthSdkClient() { } export function useAdminSdkClient() { - return { fetchAppPermissionsQuery, fetchOrgPermissionsQuery }; + return { fetchAppCapabilitiesQuery, fetchOrgCapabilitiesQuery }; } diff --git a/packages/schema-builder/src/schema/schema-builder-core/lib/data/policy-provisioning.ts b/packages/schema-builder/src/schema/schema-builder-core/lib/data/policy-provisioning.ts index fe32bf7..285406a 100644 --- a/packages/schema-builder/src/schema/schema-builder-core/lib/data/policy-provisioning.ts +++ b/packages/schema-builder/src/schema/schema-builder-core/lib/data/policy-provisioning.ts @@ -7,7 +7,7 @@ const MEMBERSHIP_TYPES = { } as const; export type PolicyFieldType = 'string' | 'boolean' | 'integer' | 'uuid' | 'uuid[]'; -export type PolicyFieldComponent = 'table-select' | 'membership-type-select' | 'permission-select'; +export type PolicyFieldComponent = 'table-select' | 'membership-type-select' | 'capability-select'; export type PolicyProvisioningCategory = 'has-module' | 'needs-fields' | 'needs-table' | 'no-fields'; export interface PolicyFieldOverride { @@ -75,9 +75,9 @@ export const POLICY_PROVISIONING_CONFIG: Record; }) { - const { data: permissions, isLoading } = usePermissions(); + const { data: capabilities, isLoading } = useCapabilities(); const membershipType = formData?.membership_type as number | null | undefined; const isAppLevel = membershipType === MEMBERSHIP_TYPES.APP; - const permissionsList = isAppLevel ? permissions?.appPermissions || [] : permissions?.membershipPermissions || []; + const capabilitiesList = isAppLevel ? capabilities?.appCapabilities || [] : capabilities?.membershipCapabilities || []; return ( ); - case 'permission-select': + case 'capability-select': return ( - 0 ? accessParts.join(' / ') : 'access'; const C = mode === 'dark' ? AuthzAppMembershipDark : AuthzAppMembershipLight; return ; diff --git a/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-diagram/diagrams.tsx b/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-diagram/diagrams.tsx index 55141ea..ff294ee 100644 --- a/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-diagram/diagrams.tsx +++ b/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-diagram/diagrams.tsx @@ -158,13 +158,13 @@ export function MembershipDiagram({ tableName, config }: DiagramProps) { const theme = useResolvedTheme('AuthzAppMembership'); const scopeLabel = 'App'; const isAdmin = config.is_admin as boolean; - const permission = config.permission as string; + const capability = config.capability as string; const accessParts: string[] = []; if (isAdmin) accessParts.push('Admin'); - if (permission) accessParts.push(permission); + if (capability) accessParts.push(capability); const accessLabel = accessParts.length > 0 ? accessParts.join(' / ') : 'access'; - const hasFilled = isAdmin || !!permission; + const hasFilled = isAdmin || !!capability; const scopeW = 80; const scopeH = 56; diff --git a/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-hooks.ts b/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-hooks.ts index 0c695e5..d9ed609 100644 --- a/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-hooks.ts +++ b/packages/schema-builder/src/schema/schema-builder-policies/components/policies/policy-hooks.ts @@ -235,8 +235,8 @@ export function hasRequiredFieldsMissing( for (const field of fields) { // Booleans are always valid (false is a valid value) if (field.type === 'boolean') continue; - // Permission selects are optional - if (field.type === 'permission-select') continue; + // Capability selects are optional + if (field.type === 'capability-select') continue; const value = data[field.key]; const isEmpty = diff --git a/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-capabilities.ts b/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-capabilities.ts new file mode 100644 index 0000000..75daf37 --- /dev/null +++ b/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-capabilities.ts @@ -0,0 +1,72 @@ +/** + * Hook for fetching app and membership capabilities + * Tier 4 wrapper: Uses SDK hooks + cache invalidation + */ +import { useQuery } from '@tanstack/react-query'; +import { + schemaBuilderQueryKey, + useAdminSdkClient, + useSchemaBuilderRuntime, +} from '@/blocks/schema/schema-builder-core/context/block-config'; + +export interface CapabilityNode { + bitnum: number | null; + bitstr: string | null; + description: string | null; + id: string; + name: string; +} + +export type AppCapability = CapabilityNode; +export type MembershipCapability = CapabilityNode; + +export const capabilitiesQueryKeys = { + all: ['capabilities'] as const, +}; + +export interface UseCapabilitiesOptions { + enabled?: boolean; +} + +export function useCapabilities(options: UseCapabilitiesOptions = {}) { + const isEnabled = options.enabled !== false; + const { scope } = useSchemaBuilderRuntime(); + const { fetchAppCapabilitiesQuery, fetchOrgCapabilitiesQuery } = useAdminSdkClient(); + + return useQuery<{ appCapabilities: AppCapability[]; membershipCapabilities: MembershipCapability[] }>({ + queryKey: schemaBuilderQueryKey(scope, 'policies', 'capabilities'), + queryFn: async () => { + // Fetch both capability types in parallel using SDK fetch functions + const [appResult, orgResult] = await Promise.all([ + fetchAppCapabilitiesQuery({ selection: { fields: { id: true, name: true, bitnum: true, bitstr: true, description: true }, orderBy: ['NAME_ASC'] } }), + fetchOrgCapabilitiesQuery({ selection: { fields: { id: true, name: true, bitnum: true, bitstr: true, description: true }, orderBy: ['NAME_ASC'] } }), + ]); + + const appCapabilities: AppCapability[] = (appResult.appCapabilities?.nodes ?? []).map((node) => ({ + id: node.id ?? '', + name: node.name ?? '', + bitnum: node.bitnum ?? null, + bitstr: node.bitstr ?? null, + description: node.description ?? null, + })); + + const membershipCapabilities: MembershipCapability[] = (orgResult.orgCapabilities?.nodes ?? []).map((node) => ({ + id: node.id ?? '', + name: node.name ?? '', + bitnum: node.bitnum ?? null, + bitstr: node.bitstr ?? null, + description: node.description ?? null, + })); + + return { + appCapabilities, + membershipCapabilities, + }; + }, + enabled: isEnabled, + staleTime: 5 * 60 * 1000, + refetchOnMount: isEnabled, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + }); +} diff --git a/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-permissions.ts b/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-permissions.ts deleted file mode 100644 index 12b18fb..0000000 --- a/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/policies/use-permissions.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Hook for fetching app and membership permissions - * Tier 4 wrapper: Uses SDK hooks + cache invalidation - */ -import { useQuery } from '@tanstack/react-query'; -import { - schemaBuilderQueryKey, - useAdminSdkClient, - useSchemaBuilderRuntime, -} from '@/blocks/schema/schema-builder-core/context/block-config'; - -export interface PermissionNode { - bitnum: number | null; - bitstr: string | null; - description: string | null; - id: string; - name: string; -} - -export type AppPermission = PermissionNode; -export type MembershipPermission = PermissionNode; - -export const permissionsQueryKeys = { - all: ['permissions'] as const, -}; - -export interface UsePermissionsOptions { - enabled?: boolean; -} - -export function usePermissions(options: UsePermissionsOptions = {}) { - const isEnabled = options.enabled !== false; - const { scope } = useSchemaBuilderRuntime(); - const { fetchAppPermissionsQuery, fetchOrgPermissionsQuery } = useAdminSdkClient(); - - return useQuery<{ appPermissions: AppPermission[]; membershipPermissions: MembershipPermission[] }>({ - queryKey: schemaBuilderQueryKey(scope, 'policies', 'permissions'), - queryFn: async () => { - // Fetch both permission types in parallel using SDK fetch functions - const [appResult, orgResult] = await Promise.all([ - fetchAppPermissionsQuery({ selection: { fields: { id: true, name: true, bitnum: true, bitstr: true, description: true }, orderBy: ['NAME_ASC'] } }), - fetchOrgPermissionsQuery({ selection: { fields: { id: true, name: true, bitnum: true, bitstr: true, description: true }, orderBy: ['NAME_ASC'] } }), - ]); - - const appPermissions: AppPermission[] = (appResult.appPermissions?.nodes ?? []).map((node) => ({ - id: node.id ?? '', - name: node.name ?? '', - bitnum: node.bitnum ?? null, - bitstr: node.bitstr ?? null, - description: node.description ?? null, - })); - - const membershipPermissions: MembershipPermission[] = (orgResult.orgPermissions?.nodes ?? []).map((node) => ({ - id: node.id ?? '', - name: node.name ?? '', - bitnum: node.bitnum ?? null, - bitstr: node.bitstr ?? null, - description: node.description ?? null, - })); - - return { - appPermissions, - membershipPermissions, - }; - }, - enabled: isEnabled, - staleTime: 5 * 60 * 1000, - refetchOnMount: isEnabled, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }); -} diff --git a/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-create-table.ts b/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-create-table.ts index ef214c7..a0f6ac3 100644 --- a/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-create-table.ts +++ b/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-create-table.ts @@ -1,6 +1,6 @@ /** * Hook for creating a table - * Tier 4 wrapper: adds auto-grant permissions and table selection + * Tier 4 wrapper: adds auto-grant capabilities and table selection */ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useCreateTableMutation } from '@/generated/schema-builder'; @@ -57,7 +57,7 @@ export function useCreateTable() { throw new Error('Failed to create table'); } - // Step 2: Auto-grant basic permissions to make table immediately usable + // Step 2: Auto-grant basic capabilities to make table immediately usable // Default strategy: Grant all CRUD operations to authenticated users (no RLS) // This ensures the table is accessible via the application GraphQL endpoint if (input.databaseId && table.name) { @@ -71,9 +71,9 @@ export function useCreateTable() { } catch (error) { // Log warning but don't fail table creation console.warn( - `⚠️ Table created successfully but failed to grant permissions: ${error instanceof Error ? error.message : 'Unknown error'}`, + `⚠️ Table created successfully but failed to grant capabilities: ${error instanceof Error ? error.message : 'Unknown error'}`, ); - console.warn(`You may need to manually grant permissions for table: ${table.name}`); + console.warn(`You may need to manually grant capabilities for table: ${table.name}`); } } diff --git a/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-table-grants.ts b/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-table-grants.ts index 31e2f7d..1df427d 100644 --- a/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-table-grants.ts +++ b/packages/schema-builder/src/schema/schema-builder-policies/lib/gql/hooks/schema-builder/use-table-grants.ts @@ -1,5 +1,5 @@ /** - * Hook for managing table grants (permissions) + * Hook for managing table grants (capabilities) * Tier 4 wrapper: orchestrates table lookup + grant creation using SDK hooks */ import { useMutation, useQueryClient } from '@tanstack/react-query'; @@ -106,7 +106,7 @@ function resolveFieldIds(table: TableWithFields, fieldNames: string[]): string[] // ============================================================================ /** - * Hook for creating table grants (permissions). + * Hook for creating table grants (capabilities). * Supports both table-level grants (all fields) and field-level grants (specific fields). * * @example @@ -196,7 +196,7 @@ export function useCreateTableGrant() { /** * Hook for creating multiple table grants in batch. - * Useful when setting up a new table with standard permissions. + * Useful when setting up a new table with standard capabilities. * * @example * batchCreateGrants({ diff --git a/packages/schema-builder/src/schema/schema-builder-relationships/lib/gql/hooks/schema-builder/use-relationship-mutations.ts b/packages/schema-builder/src/schema/schema-builder-relationships/lib/gql/hooks/schema-builder/use-relationship-mutations.ts index a094473..9dd8b84 100644 --- a/packages/schema-builder/src/schema/schema-builder-relationships/lib/gql/hooks/schema-builder/use-relationship-mutations.ts +++ b/packages/schema-builder/src/schema/schema-builder-relationships/lib/gql/hooks/schema-builder/use-relationship-mutations.ts @@ -375,14 +375,14 @@ export function useCreateManyToMany() { type: 'u', }); - // Step 7: Grant permissions (non-fatal, after all DDL is complete) + // Step 7: Grant capabilities (non-fatal, after all DDL is complete) await createGrant({ databaseId, tableName: junctionTableName, privileges: ['select', 'insert', 'update', 'delete'], roleName: 'authenticated', }).catch((error) => { - console.warn(`Failed to grant permissions for junction table: ${error instanceof Error ? error.message : 'Unknown error'}`); + console.warn(`Failed to grant capabilities for junction table: ${error instanceof Error ? error.message : 'Unknown error'}`); }); return { diff --git a/packages/schema-builder/src/testing.ts b/packages/schema-builder/src/testing.ts index f43be4a..3555f6a 100644 --- a/packages/schema-builder/src/testing.ts +++ b/packages/schema-builder/src/testing.ts @@ -47,14 +47,14 @@ export function createNoopSchemaBuilderAdapter( updateIndex: operation }, policies: { - appPermissions: operation, + appCapabilities: operation, createField: operation, createSecureTableProvision: operation, createTable: operation, createTableGrant: operation, deletePolicy: operation, fields: operation, - orgPermissions: operation, + orgCapabilities: operation, tables: operation, updatePolicy: operation }, diff --git a/packages/schema-builder/src/types.ts b/packages/schema-builder/src/types.ts index f4cf329..e1ef1e4 100644 --- a/packages/schema-builder/src/types.ts +++ b/packages/schema-builder/src/types.ts @@ -153,14 +153,14 @@ export interface SchemaBuilderIndexesCapabilities { } export interface SchemaBuilderPoliciesCapabilities { - appPermissions: SchemaBuilderOperation; + appCapabilities: SchemaBuilderOperation; createField: SchemaBuilderOperation; createSecureTableProvision: SchemaBuilderOperation; createTable: SchemaBuilderOperation; createTableGrant: SchemaBuilderOperation; deletePolicy: SchemaBuilderOperation; fields: SchemaBuilderOperation; - orgPermissions: SchemaBuilderOperation; + orgCapabilities: SchemaBuilderOperation; tables: SchemaBuilderOperation; updatePolicy: SchemaBuilderOperation; }