Upgrade call-recorder, people-data-labs, last-contact and partners apps to twenty-sdk 2.23.0-alpha.1 (#23098)

## What

Upgrades the two breaking-change-prone apps to `twenty-sdk` /
`twenty-client-sdk` `2.23.0-alpha.1`, and adds the server-side hook that
lets the 2.23 upgrade install them:

- **people-data-labs**
- **partners**

Follows up on #22882 (System side effect relations), which re-derived
the system relation field universal identifiers name-free and shipped
`getSystemRelationFieldUniversalIdentifier` in the SDK.

## How

- **people-data-labs**: bump the SDK to `2.23.0-alpha.1`. The enriched
views temporarily hardcoded the new system relation identifiers with a
TODO because the SDK still embedded the old values; now that the
name-free identifiers ship in `2.23`, derive them from
`STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.{company,person}.fields.{noteTargets,taskTargets,attachments,timelineActivities}.universalIdentifier`
(identical to the previously pinned values, verified). Engine already
pinned `twenty >=2.23.0`; app stays `1.0.7` (manifest unchanged).
- **partners**: bump the SDK to `2.23.0-alpha.1`. The partner role
references
`opportunity.fields.{taskTargets,noteTargets,attachments,timelineActivities}`
universal identifiers, which the SDK now resolves to the `2.23`
name-free values. Pin `engines.twenty >=2.23.0` and bump the app to
`1.3.1`.
- **server**: add an opt-in `skipWorkspaceCompatibilityCheck` to the
install/upgrade path. The `upgrade-people-data-labs-application` 2.23
command runs mid-upgrade, before the workspace is marked as having
completed 2.23, so the workspace-compatibility check would otherwise
reject installing `1.0.7` (`engines >=2.23.0`). The server is already on
2.23, so the command passes the flag to install `1.0.7` and close the
desync window. Version-progression (downgrade/same-version) checks still
run.
- **call-recorder** and **last-contact** are intentionally left
unchanged (reverted): they don't define custom objects and don't
reference the system relation identifiers, so they aren't
breaking-change-prone and need no SDK bump.

## Breaking change constraints

- **people-data-labs** and **partners** reference system relation
identifiers that only exist on a `2.23` server, so both pin
`engines.twenty >=2.23.0`. Their `dockerhub-latest` integration leg is
red by design until a >=2.23 server image is published (same accepted
state as #22882); the `local` leg is green.

## Validation

- Regenerated the app lockfiles against the published `2.23.0-alpha.1`.
- `people-data-labs` typechecks cleanly against the real `2.23` SDK
types.
- CI: people-data-labs and partners green on `local`, red on
`dockerhub-latest` by design; server/SDK/all other checks green.
- Rebased onto latest `main`.
This commit is contained in:
Paul Rastoin
2026-07-21 15:40:14 +02:00
committed by GitHub
parent bc3112a999
commit 2ef7b7824e
9 changed files with 94 additions and 74 deletions
@@ -9,27 +9,27 @@ import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { buildApplicationFileList } from 'src/engine/core-modules/application/application-install/utils/build-application-file-list.util';
import { ApplicationManifestApplyService } from 'src/engine/core-modules/application/application-manifest/application-manifest-apply.service';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
import {
ApplicationException,
ApplicationExceptionCode,
} from 'src/engine/core-modules/application/application.exception';
import { isImageFilePath } from 'src/engine/core-modules/application/application-registration/utils/is-image-file-path.util';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import {
type ResolvedPackage,
ApplicationPackageFetcherService,
type ResolvedPackage,
} from 'src/engine/core-modules/application/application-package/application-package-fetcher.service';
import { ApplicationVersionValidationService } from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import {
VERSION_PROGRESSION_REASON_TO_INSTALL_EXCEPTION_CODE,
VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE,
} from 'src/engine/core-modules/application/application-package/constants/version-reason-to-exception-code.constant';
import { buildApplicationFileList } from 'src/engine/core-modules/application/application-install/utils/build-application-file-list.util';
import { ApplicationManifestApplyService } from 'src/engine/core-modules/application/application-manifest/application-manifest-apply.service';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { isImageFilePath } from 'src/engine/core-modules/application/application-registration/utils/is-image-file-path.util';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import {
ApplicationException,
ApplicationExceptionCode,
} from 'src/engine/core-modules/application/application.exception';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/services/file-storage.service';
import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service';
@@ -69,6 +69,7 @@ export class ApplicationInstallService {
appRegistrationId: string;
version?: string;
workspaceId: string;
skipWorkspaceCompatibilityCheck?: boolean;
}): Promise<boolean> {
const appRegistration = await this.appRegistrationRepository.findOne({
where: { id: params.appRegistrationId },
@@ -109,6 +110,8 @@ export class ApplicationInstallService {
this.doInstallApplication(appRegistration, {
version: params.version,
workspaceId: params.workspaceId,
skipWorkspaceCompatibilityCheck:
params.skipWorkspaceCompatibilityCheck,
}),
lockKey,
{ ttl: 60_000, ms: 500, maxRetries: 120 },
@@ -117,7 +120,11 @@ export class ApplicationInstallService {
private async doInstallApplication(
preLockAppRegistration: ApplicationRegistrationEntity,
params: { version?: string; workspaceId: string },
params: {
version?: string;
workspaceId: string;
skipWorkspaceCompatibilityCheck?: boolean;
},
): Promise<boolean> {
// Re-read inside the lock so the authorization below cannot act on stale
// listing or ownership state.
@@ -184,7 +191,11 @@ export class ApplicationInstallService {
existingApplication,
}: {
appRegistration: ApplicationRegistrationEntity;
params: { version?: string; workspaceId: string };
params: {
version?: string;
workspaceId: string;
skipWorkspaceCompatibilityCheck?: boolean;
};
resolvedPackage: ResolvedPackage;
existingApplication: ApplicationEntity | null;
}): Promise<boolean> {
@@ -238,28 +249,36 @@ export class ApplicationInstallService {
existingApplication,
}: {
appRegistration: ApplicationRegistrationEntity;
params: { version?: string; workspaceId: string };
params: {
version?: string;
workspaceId: string;
skipWorkspaceCompatibilityCheck?: boolean;
};
resolvedPackage: ResolvedPackage;
existingApplication: ApplicationEntity | null;
}): Promise<boolean> {
const universalIdentifier = appRegistration.universalIdentifier;
const requiredServerVersion =
resolvedPackage.packageJson.engines?.['twenty'];
if (params.skipWorkspaceCompatibilityCheck !== true) {
const requiredServerVersion =
resolvedPackage.packageJson.engines?.['twenty'];
const versionValidation =
await this.applicationVersionValidationService.validateWorkspaceCompatibility(
{
requiredServerVersion,
workspaceId: params.workspaceId,
},
);
const versionValidation =
await this.applicationVersionValidationService.validateWorkspaceCompatibility(
{
requiredServerVersion,
workspaceId: params.workspaceId,
},
);
if (!versionValidation.compatible) {
throw new ApplicationException(
versionValidation.message,
VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE[versionValidation.reason],
);
if (!versionValidation.compatible) {
throw new ApplicationException(
versionValidation.message,
VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE[
versionValidation.reason
],
);
}
}
const isVersionUpgrade = isDefined(existingApplication);
@@ -172,6 +172,7 @@ export class ApplicationUpgradeService {
appRegistrationId: string;
targetVersion: string;
workspaceId: string;
skipWorkspaceCompatibilityCheck?: boolean;
}): Promise<boolean> {
const appRegistration = await this.appRegistrationRepository.findOneOrFail({
where: { id: params.appRegistrationId },
@@ -181,6 +182,7 @@ export class ApplicationUpgradeService {
appRegistration,
targetVersion: params.targetVersion,
workspaceId: params.workspaceId,
skipWorkspaceCompatibilityCheck: params.skipWorkspaceCompatibilityCheck,
});
}
@@ -188,6 +190,7 @@ export class ApplicationUpgradeService {
appRegistration: ApplicationRegistrationEntity;
targetVersion: string;
workspaceId: string;
skipWorkspaceCompatibilityCheck?: boolean;
}): Promise<boolean> {
const { appRegistration } = params;
@@ -209,6 +212,7 @@ export class ApplicationUpgradeService {
appRegistrationId: appRegistration.id,
version: params.targetVersion,
workspaceId: params.workspaceId,
skipWorkspaceCompatibilityCheck: params.skipWorkspaceCompatibilityCheck,
});
} catch (error) {
const appName =