[Breaking change] Prepare non-system permission flags (#20847)

# Summary

Replaces the enum-keyed `permissionFlags: PermissionFlag[]` on roles
with `permissionFlagUniversalIdentifiers: string[]`

This unlocks mixing system flags (`SystemPermissionFlag.*`) with
app-defined flags in a role config.

This is a breaking change. Existing app source must switch to the new
field.

# Breaking changes

- `RoleManifest.permissionFlags` removed. Use
`RoleManifest.permissionFlagUniversalIdentifiers: string[]`.
- `RoleConfig.permissionFlags` removed (was `PermissionFlagType[]`). Use
`RoleConfig.permissionFlagUniversalIdentifiers: string[]`.
- `PermissionFlagManifest` type removed from
`twenty-shared/application`.
- `PermissionFlag` re-export removed from `twenty-sdk/define`.
`SystemPermissionFlag` is re-exported in its place.
- Retargeting a permission flag between roles is now classified as
delete + create instead of update

 ### Not in this PR
- definePermissionFlag SDK function and top-level
Manifest.permissionFlags catalog (apps defining their own custom flags).
Until those land, permissionFlagUniversalIdentifiers only accepts
SystemPermissionFlag.* UUIDs; arbitrary UUIDs fail validation.
This commit is contained in:
Weiko
2026-05-22 23:22:28 +02:00
committed by GitHub
parent eda41b4eba
commit 3bda05ea57
18 changed files with 81 additions and 80 deletions
@@ -1,26 +1,45 @@
import { type PermissionFlagManifest } from 'twenty-shared/application';
import { SystemPermissionFlag } from 'twenty-shared/constants';
import {
PermissionFlagType,
SystemPermissionFlag,
} from 'twenty-shared/constants';
import { v5 } from 'uuid';
import { type UniversalFlatRolePermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type';
export const ROLE_PERMISSION_FLAG_UUID_NAMESPACE =
'b9a3b3b3-58a3-4f6c-9c1f-3a4f6c9c1f3a';
const SYSTEM_PERMISSION_FLAG_BY_UNIVERSAL_IDENTIFIER = Object.fromEntries(
Object.entries(SystemPermissionFlag).map(([key, uuid]) => [uuid, key]),
) as Record<string, PermissionFlagType | undefined>;
export const fromPermissionFlagToUniversalFlatRolePermissionFlag = ({
permissionFlag,
permissionFlagUniversalIdentifier,
roleUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
permissionFlag: PermissionFlagManifest;
permissionFlagUniversalIdentifier: string;
roleUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatRolePermissionFlag => {
const universalIdentifier = v5(
`${roleUniversalIdentifier}:${permissionFlagUniversalIdentifier}`,
ROLE_PERMISSION_FLAG_UUID_NAMESPACE,
);
const resolvedFlag =
SYSTEM_PERMISSION_FLAG_BY_UNIVERSAL_IDENTIFIER[
permissionFlagUniversalIdentifier
];
return {
universalIdentifier: permissionFlag.universalIdentifier,
universalIdentifier,
applicationUniversalIdentifier,
roleUniversalIdentifier,
permissionFlagUniversalIdentifier:
SystemPermissionFlag[permissionFlag.flag],
flag: permissionFlag.flag,
permissionFlagUniversalIdentifier,
flag: resolvedFlag as PermissionFlagType,
createdAt: now,
updatedAt: now,
};
@@ -212,11 +212,12 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
});
}
for (const permissionFlag of roleManifest.permissionFlags ?? []) {
for (const permissionFlagUniversalIdentifier of roleManifest.permissionFlagUniversalIdentifiers ??
[]) {
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
universalFlatEntity:
fromPermissionFlagToUniversalFlatRolePermissionFlag({
permissionFlag,
permissionFlagUniversalIdentifier,
roleUniversalIdentifier: roleManifest.universalIdentifier,
applicationUniversalIdentifier,
now,