Identify standard field do deploy until IS_WORKSPACE_CREATION_V2_ENABLED is enabled in prod (#16981)

# Introduction

fixes https://github.com/twentyhq/twenty/issues/16905

Do not merge until `IS_WORKSPACE_CREATION_V2_ENABLED` has been activated
by default, and so sync metadata has been deprecated by doing so. As the
sync metadata will attempt to insert `null` `applicationId` and
`universalIdentifier` values while creating a workspace

In this PR we're introducing a new `SyncableEntityRequired` which
enforces the non nullable `applicationId` and `universalIdentifier` on
extending entity

In this PR we also migrate the field metadata entity to extend the
required

## Identification upgrade command
This command will search for workspace field metadata entities that
aren't associated to an applicationId, dispatch them to either the
workspace-custom `applicationId` or the twenty-standard `applicationId`.
For the standard entities it will also set their universal identifier
based on the `STANDARD_OBJECTS` const hashmap

## Typeorm migration
As the non nullable `applicationId` and `universalIdentifier`migration
won't pass in the first we've been using the save point and upgrade
command migration fallback pattern

## Tests
Tested the command on a prod extract locally

Both `twenty-eng` and `twenty-for-twenty` have unexpected standard
objects
Please note that we will deprecate the `isCustom` and `standardId` col
later in the future

### Twenty-eng
```ts
[Nest] 98971  - 01/01/2026, 3:18:00 PM     LOG [IdentifyStandardEntitiesCommand] Successfully validated 600/600 field metadata update(s) for workspace 9870323e-22c3-4d14-9b7f-5bdc84f7d6ee (309 custom, 291 standard)
[Nest] 98971  - 01/01/2026, 3:18:00 PM    WARN [IdentifyStandardEntitiesCommand] Found 35 warning(s) while processing field metadata for workspace 9870323e-22c3-4d14-9b7f-5bdc84f7d6ee. These fields will become custom.
```

### Twenty for twenty

### Just created workspace
This commit is contained in:
Paul Rastoin
2026-01-12 10:59:01 +01:00
committed by GitHub
parent 46e5420d20
commit a6f371a42a
13 changed files with 459 additions and 16 deletions
@@ -161,6 +161,7 @@ export class FieldMetadataDTO<T extends FieldMetadataType = FieldMetadataType> {
@Field()
updatedAt: Date;
// TODO prastoin make non nullable once MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand has passed in production @Field(() => UUIDScalarType, { nullable: true })
@Field(() => UUIDScalarType, { nullable: true })
applicationId?: string;
}
@@ -30,7 +30,7 @@ import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permis
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface';
@Entity('fieldMetadata')
@Check(
@@ -56,7 +56,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti
export class FieldMetadataEntity<
TFieldMetadataType extends FieldMetadataType = FieldMetadataType,
>
extends SyncableEntity
extends SyncableEntityRequired
implements Required<FieldMetadataEntity>
{
@PrimaryGeneratedColumn('uuid')
@@ -1,4 +1,7 @@
import { FieldMetadataType } from 'twenty-shared/types';
import {
FieldMetadataType,
type NonNullableRequired,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
@@ -11,7 +14,9 @@ import {
type BuildDefaultFlatFieldMetadataForCustomObjectArgs = {
workspaceId: string;
flatObjectMetadata: Pick<FlatObjectMetadata, 'id' | 'applicationId'>;
flatObjectMetadata: NonNullableRequired<
Pick<FlatObjectMetadata, 'id' | 'applicationId'>
>;
};
export type DefaultFlatFieldForCustomObjectMaps = ReturnType<
@@ -57,7 +62,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const nameFieldId = v4();
@@ -94,7 +99,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const createdAtFieldId = v4();
@@ -131,7 +136,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const updatedAtFieldId = v4();
@@ -168,7 +173,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const deletedAtFieldId = v4();
@@ -205,7 +210,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const createdByFieldId = v4();
@@ -241,7 +246,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const updatedByFieldId = v4();
@@ -277,7 +282,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const positionFieldId = v4();
@@ -314,7 +319,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
relationTargetObjectMetadataId: null,
settings: null,
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
const searchVectorFieldId = v4();
@@ -354,7 +359,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
generatedType: 'STORED',
},
morphId: null,
applicationId: applicationId ?? null,
applicationId,
};
return {