[1/3] Rename permissionFlag to rolePermissionFlag + add permissionFlag catalog/backfill (#20481)

Split of #20377.

## Summary

This PR separates available permission flags from per-role permission
flag grants.

Previously, `core.permissionFlag` stored the role assignment directly:
`roleId + flag`. This PR renames that legacy grant table to
`core.rolePermissionFlag`, then recreates `core.permissionFlag` as the
catalog of available permission flags.

## What changed

- Rename the existing `core.permissionFlag` grant table to
`core.rolePermissionFlag`.
- Add the new syncable `core.permissionFlag` catalog entity with key,
label, description, icon, permission type, relevance flags, and
custom/standard metadata.
- Add stable `SystemPermissionFlag` universal identifiers for the
built-in `PermissionFlagType` values.
- Seed the standard permission flags for every workspace under the
Twenty standard application.
- Backfill existing role grants:
  - create missing catalog rows for existing grant keys,
  - add `rolePermissionFlag.permissionFlagId`,
- migrate grants from the old string `flag` column to the new catalog
FK,
- replace the old `(flag, roleId)` uniqueness with `(permissionFlagId,
roleId)`.
- Rewire role permission flag caches, permission checks, role DTO
mapping, and `upsertPermissionFlags` to resolve through the catalog.
- Keep the existing public role permission API shape: product/app
surfaces still talk about `permissionFlags` and return `{ id, roleId,
flag }`.
- Update metadata flat-entity machinery, migration builders, validators,
action handlers, snapshots, generated schemas, docs, and app fixtures
for the new `permissionFlag` / `rolePermissionFlag` split.

## Behavior after this PR

- Existing permission flag grants keep working.
- Existing GraphQL role permission flows keep the same public naming.
- Standard permission flags are represented as catalog rows.
- Permission checks now compare grants through catalog universal
identifiers instead of the legacy `flag` column.
- Workspace deletion cleanup now verifies both `permissionFlag` and
`rolePermissionFlag`.

## What is not in this PR

- Public GraphQL CRUD for custom permission flags.
- App manifest support for declaring new custom permission flags.
- Frontend UI for creating or assigning custom permission flags beyond
the existing role permission flow.

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
nitin
2026-05-18 17:27:47 +05:30
committed by GitHub
parent 01535a3b3e
commit db0547f503
132 changed files with 3663 additions and 719 deletions
@@ -1,23 +0,0 @@
import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type';
import { PermissionFlagManifest } from 'twenty-shared/application';
export const fromPermissionFlagToUniversalFlatPermissionFlag = ({
permissionFlag,
roleUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
permissionFlag: PermissionFlagManifest;
roleUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatPermissionFlag => {
return {
universalIdentifier: permissionFlag.universalIdentifier,
applicationUniversalIdentifier,
roleUniversalIdentifier,
flag: permissionFlag.flag,
createdAt: now,
updatedAt: now,
};
};
@@ -0,0 +1,27 @@
import { type PermissionFlagManifest } from 'twenty-shared/application';
import { SystemPermissionFlag } from 'twenty-shared/constants';
import { type UniversalFlatRolePermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type';
export const fromPermissionFlagToUniversalFlatRolePermissionFlag = ({
permissionFlag,
roleUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
permissionFlag: PermissionFlagManifest;
roleUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatRolePermissionFlag => {
return {
universalIdentifier: permissionFlag.universalIdentifier,
applicationUniversalIdentifier,
roleUniversalIdentifier,
permissionFlagUniversalIdentifier:
SystemPermissionFlag[permissionFlag.flag],
flag: permissionFlag.flag,
createdAt: now,
updatedAt: now,
};
};
@@ -32,7 +32,7 @@ export const fromRoleManifestToUniversalFlatRole = ({
roleTargetUniversalIdentifiers: [],
objectPermissionUniversalIdentifiers: [],
fieldPermissionUniversalIdentifiers: [],
permissionFlagUniversalIdentifiers: [],
rolePermissionFlagUniversalIdentifiers: [],
rowLevelPermissionPredicateUniversalIdentifiers: [],
rowLevelPermissionPredicateGroupUniversalIdentifiers: [],
createdAt: now,
@@ -17,7 +17,7 @@ import { fromObjectPermissionManifestToUniversalFlatObjectPermission } from 'src
import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-manifest-to-universal-flat-page-layout.util';
import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util';
import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util';
import { fromPermissionFlagToUniversalFlatPermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util';
import { fromPermissionFlagToUniversalFlatRolePermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-role-permission-flag.util';
import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util';
import { fromSkillManifestToUniversalFlatSkill } from 'src/engine/core-modules/application/application-manifest/converters/from-skill-manifest-to-universal-flat-skill.util';
import { computeSearchVectorUniversalSettingsFromObjectManifest } from 'src/engine/core-modules/application/application-manifest/utils/compute-search-vector-universal-settings-from-object-manifest.util';
@@ -214,14 +214,15 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
for (const permissionFlag of roleManifest.permissionFlags ?? []) {
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
universalFlatEntity: fromPermissionFlagToUniversalFlatPermissionFlag({
permissionFlag,
roleUniversalIdentifier: roleManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntity:
fromPermissionFlagToUniversalFlatRolePermissionFlag({
permissionFlag,
roleUniversalIdentifier: roleManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityMapsToMutate:
allUniversalFlatEntityMaps.flatPermissionFlagMaps,
allUniversalFlatEntityMaps.flatRolePermissionFlagMaps,
});
}
}