Add definePermissionFlag for app-defined permission flags (#20887)

## Context
Adds the SDK plumbing for apps to declare custom permission flags and
the server-side manifest pipeline to persist them.

```typescript
import { definePermissionFlag } from 'twenty-sdk/define';

export const MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER = '…';

export default definePermissionFlag({
  universalIdentifier: MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER,
  key: 'MANAGE_INVOICES',
  label: 'Manage Invoices',
  description: 'Create, edit, and delete invoices',
  icon: 'IconReceipt',
});
```

```typescript
import { defineApplicationRole, SystemPermissionFlag } from 'twenty-sdk/define';
import { MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER } from './permission-flags/manage-invoices';

export default defineApplicationRole({
  universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
  label: `${APP_DISPLAY_NAME} default function role`,
  // ...
  permissionFlagUniversalIdentifiers: [
    SystemPermissionFlag.UPLOAD_FILE,
    MANAGE_INVOICES_PERMISSION_FLAG_UNIVERSAL_IDENTIFIER,
  ],
});
```

The flag can then be referenced by UUID in a role's
permissionFlagUniversalIdentifiers. On sync, the catalog row lands in
core.permissionFlag and the link in core.rolePermissionFlag.

## Not in this PR
- Runtime permission checks.
PermissionsService.getUserWorkspacePermissions still builds its result
from Object.values(PermissionFlagType), so custom flags are stored but
not yet enforced, code asking "does this role have MANAGE_INVOICES?"
won't get a meaningful answer. Widening PermissionsService and
UserWorkspacePermissions.permissionFlags to support arbitrary flag keys
is the next PR.
- PermissionFlag from apps can only define "tool" permissions and not
"settings" as a permissionType, this parameter is not mutable. This is
because "settings" are for settings page (until we might decide to
separate both type of permissions into 2 different entities) and apps
can't declare settings page or interact with them so this parameter
would be unnecessary.
This commit is contained in:
Weiko
2026-05-25 18:53:37 +02:00
committed by GitHub
parent 98d47d0dd0
commit 90f711361c
32 changed files with 157 additions and 53 deletions
@@ -55,9 +55,9 @@ export class FlatPermissionFlagValidatorService {
});
}
const duplicateKey = Object.values(
const collidingPermissionFlag = Object.values(
optimisticFlatPermissionFlagMaps.byUniversalIdentifier,
).filter(
).find(
(definition) =>
isDefined(definition) &&
definition.key === flatPermissionFlagToValidate.key &&
@@ -65,11 +65,11 @@ export class FlatPermissionFlagValidatorService {
flatPermissionFlagToValidate.universalIdentifier,
);
if (duplicateKey.length > 0) {
if (isDefined(collidingPermissionFlag)) {
validationResult.errors.push({
code: PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS,
message: t`Permission flag definition with key ${flatPermissionFlagToValidate.key} already exists in this workspace`,
userFriendlyMessage: msg`A permission flag with this key already exists`,
message: t`Permission flag definition with key "${flatPermissionFlagToValidate.key}" is already registered in this workspace.`,
userFriendlyMessage: msg`Another application in this workspace has already registered a permission flag with this key.`,
});
}