fix(server): guard role-permission cache against stripped permissionFlag relation during upgrade (#21257)
## Problem Self-hosted upgrades that jump versions (e.g. `2.4 → 2.7/2.9`) abort with: ``` TypeError: Cannot read properties of undefined (reading 'universalIdentifier') at WorkspaceRolesPermissionsCacheService.hasSettingsGatedObjectPermissions at WorkspaceRolesPermissionsCacheService.computeForCache at WorkspaceCacheService.recomputeDataFromProvider ``` Reported in #20841 (Failure #2). The sequence aborts mid-upgrade and leaves the DB in a half-migrated state. ## Root cause The per-workspace **cache recompute runs at a `2.5.0` workspace step — before the `2.6` schema migrations apply**. At that cursor: - `RolePermissionFlagEntity.permissionFlag` is `@WasIntroducedInUpgrade('2.6.0_LinkRolePermissionFlagToPermissionFlag…')`, so `UpgradeAwareRepositoryProxy` **strips the relation** (`[upgrade-proxy] strip relation RolePermissionFlagEntity.permissionFlag` in the logs) → `permissionFlag` is `undefined`. - `hasSettingsGatedObjectPermissions()` then does an **unguarded** `rolePermissionFlag.permissionFlag.universalIdentifier` → throws. The crash only manifests when a workspace has **≥1 `rolePermissionFlag` row** (custom roles with gated settings perms / SDK `defineRole`). A vanilla seed has an empty table, so `.find()` over `[]` never dereferences anything — which is why it didn't reproduce on a clean instance. A null-safe fallback to the legacy `flag` column used to exist here; it was dropped in #20730. ## Fix Resolve the flag's universal identifier through a small helper that falls back to the legacy `flag` column (only removed in `2.7.0`) when the relation is unavailable: ```ts private getRolePermissionFlagUniversalIdentifier( rolePermissionFlag: RolePermissionFlagEntity, ): string { // The `permissionFlag` relation is stripped during upgrades until the 2.6.0 // cursor (@WasIntroducedInUpgrade), so fall back to the legacy `flag` column. return ( rolePermissionFlag.permissionFlag?.universalIdentifier ?? SystemPermissionFlag[rolePermissionFlag.flag] ); } ``` `SystemPermissionFlag[flag]` yields the same UUID the relation would, so the comparison stays in a single space and the computed permission is exact (not an over-grant). Correct at every transitional cursor: pre-`2.6` (relation stripped → use `flag`), `2.6` (both present → relation wins), post-`2.7` (`flag` removed → relation wins). ## Reproduction & validation Locally jumped a real `2.4.0` DB → `v2.9.0` build via `yarn command:prod upgrade`: | Scenario | Result | | --- | --- | | Empty `permissionFlag` (vanilla seed) | passes (no crash) | | **+1 flag row**, current code | `TypeError … universalIdentifier` → **3 succeeded, 1 failed** | | Same fixture, **this fix** | **16 succeeded, 0 failed**, DB fully migrated to 2.9.0 | `nx typecheck twenty-server` clean; existing cache-service unit tests pass; app boots on the upgraded DB. ## Scope / follow-up This fixes **Failure #2**. **Failure #1** in the same issue (`viewFilter.relationTargetFieldMetadataId` selected before its column exists) is a separate instance of the same theme — cache recompute reading "future" schema before migrations run — and is worth a follow-up. A more durable systemic fix would defer the workspace cache recompute until after all schema-adding migrations; this PR is the low-risk, backport-friendly fix for the immediate breakage. > Note: an earlier bot branch (`sonarly-39738-fixupgrade-guard-role-permission-flag-relation`) proposed the same fallback inline. This PR supersedes it with a named helper + a focused comment. Fixes #20841 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+12
-1
@@ -279,11 +279,22 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
const hasPermissionFromSettingPermissions = isDefined(
|
||||
rolePermissionFlags.find(
|
||||
(rolePermissionFlag) =>
|
||||
rolePermissionFlag.permissionFlag.universalIdentifier ===
|
||||
this.getRolePermissionFlagUniversalIdentifier(rolePermissionFlag) ===
|
||||
permissionFlagUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
return hasPermissionFromRole || hasPermissionFromSettingPermissions;
|
||||
}
|
||||
|
||||
private getRolePermissionFlagUniversalIdentifier(
|
||||
rolePermissionFlag: RolePermissionFlagEntity,
|
||||
): string {
|
||||
// The `permissionFlag` relation is stripped during upgrades until the 2.6.0
|
||||
// cursor (@WasIntroducedInUpgrade), so fall back to the legacy `flag` column.
|
||||
return (
|
||||
rolePermissionFlag.permissionFlag?.universalIdentifier ??
|
||||
SystemPermissionFlag[rolePermissionFlag.flag]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user