Refactor and standardize isSystem field and object (#17992)

# Introduction

## Centralize system field definitions
- Extract a single `PARTIAL_SYSTEM_FLAT_FIELD_METADATAS` constant as the
source of truth for all 8 system fields (`id`, `createdAt`, `updatedAt`,
`deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`),
eliminating duplication across custom object and standard app field
builders
- Refactor `buildDefaultFlatFieldMetadatasForCustomObject` to use the
shared constant via a new `buildObjectSystemFlatFieldMetadatas` helper

## Mark system fields as `isSystem: true`
- Fields `id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`,
`updatedBy`, `position`, `searchVector` are now properly flagged as
system fields across all standard objects and custom object creation
- Standard app field builders for all ~30 standard objects updated to
set `isSystem: true` on `createdAt`, `updatedAt`, `deletedAt`,
`createdBy`, `updatedBy`
- System-only standard objects (blocklist, calendar channels, message
threads, etc.) now also include `createdBy`, `updatedBy`, `position`,
`searchVector` field definitions that were previously missing

## Validate system fields on object creation
- New transversal validation (`crossEntityTransversalValidation`) runs
after all atomic entity validations in the build orchestrator, ensuring
all 8 system fields are present with correct `type` and `isSystem: true`
when an object is created
- New `buildUniversalFlatObjectFieldByNameAndJoinColumnMaps` utility to
resolve field names to universal identifiers for a given object
- New exception codes: `MISSING_SYSTEM_FIELD` and `INVALID_SYSTEM_FIELD`
on `ObjectMetadataExceptionCode`

## Protect system fields and objects from mutation
- Field validators now block update/delete of `isSystem` fields by
non-system callers (`FIELD_MUTATION_NOT_ALLOWED`)
- Object validators now block update/delete of `isSystem` objects by
non-system callers
- `POSITION` and `TS_VECTOR` field type validators replaced: instead of
rejecting creation outright, they now validate that the field is named
correctly (`position` / `searchVector`) and has `isSystem: true`

## Distinguish `isSystemBuild` from `isCallerTwentyStandardApp`
- New `isCallerTwentyStandardApp` utility checks whether the caller's
`applicationUniversalIdentifier` matches the twenty standard app
- Name-sync logic (`isFlatFieldMetadataNameSyncedWithLabel`,
`areFlatObjectMetadataNamesSyncedWithLabels`) refactored to use
`isCallerTwentyStandardApp` for custom suffix decisions, keeping
`isSystemBuild` for mutation permission checks
- `WorkspaceMigrationBuilderOptions` type updated to include
`applicationUniversalIdentifier`

## Adapt frontend filtering
- New `HIDDEN_SYSTEM_FIELD_NAMES` constant (`id`, `position`,
`searchVector`) and `isHiddenSystemField` utility to only hide truly
internal fields while keeping user-facing system fields (`createdAt`,
`updatedAt`, `deletedAt`, `createdBy`, `updatedBy`) visible in the UI
- ~20 frontend files updated to replace `!field.isSystem` checks with
`!isHiddenSystemField(field)` across record index, settings, data model,
charts, workflows, spreadsheet import, aggregations, and role
permissions

## Add 1.19 upgrade commands
- **`backfill-system-fields-is-system`**: Raw SQL command to set
`isSystem = true` on existing workspace fields matching system field
names, and fix `position` field type from `NUMBER` to `POSITION` for
`favorite`/`favoriteFolder` objects. Includes proper cache invalidation.
- **`add-missing-system-fields-to-standard-objects`**: Codegen'd
workspace migration to create missing `position`, `searchVector`,
`createdBy`, `updatedBy` fields on standard objects that didn't
previously have them. Runs via `WorkspaceMigrationRunnerService` in a
single transaction with idempotency check. **Known limitation**: assumes
all standard objects exist and are valid in the target workspace.

## Add `universalIdentifier` for system fields in standard object
constants
- `standard-object.constant.ts` updated to include `universalIdentifier`
for `createdBy`, `updatedBy`, `position`, and `searchVector` across all
standard objects
- `fieldManifestType.ts` updated to support the new field manifest shape

## System relation
Completely removed and backfilled all `isSystem` relation to be false
false
As we won't require an object to have any relation system fields

## Add integration tests
- New test suite `failing-sync-application-object-system-fields`
covering: missing system fields, wrong field types (`id` as TEXT,
`createdAt` as TEXT, `position` as TEXT), system field deletion
attempts, and system field update attempts
- New test utilities: `buildDefaultObjectManifest` (builds an object
manifest with all 8 system fields) and `setupApplicationForSync`
(centralizes application setup)
- Existing successful sync test updated to verify system fields are
created with correct properties

## Next step
Make the builder scope the compared entity to be the currently built app
+ nor twenty standard app
This commit is contained in:
Paul Rastoin
2026-02-19 11:13:50 +01:00
committed by GitHub
parent 082400f751
commit 88424611ec
137 changed files with 8850 additions and 2056 deletions
@@ -14,6 +14,7 @@ import {
} from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type';
import { AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration/utils/aggregate-orchestrator-actions-report.util';
import { crossEntityTransversalValidation } from 'src/engine/workspace-manager/workspace-migration/utils/cross-entity-transversal-validation.util';
import { WorkspaceMigrationAgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/agent/workspace-migration-agent-actions-builder.service';
import { WorkspaceMigrationCommandMenuItemActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/workspace-migration-command-menu-item-actions-builder.service';
import { WorkspaceMigrationFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service';
@@ -126,7 +127,6 @@ export class WorkspaceMigrationBuildOrchestratorService {
fromToAllFlatEntityMaps,
dependencyAllFlatEntityMaps,
additionalCacheDataMaps,
applicationUniversalIdentifier,
}: WorkspaceMigrationOrchestratorBuildArgs): Promise<
| WorkspaceMigrationOrchestratorFailedResult
| WorkspaceMigrationOrchestratorSuccessfulResult
@@ -994,6 +994,13 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
const { objectMetadata } = crossEntityTransversalValidation({
optimisticUniversalFlatMaps: optimisticAllFlatEntityMaps,
orchestratorActionsReport,
});
orchestratorFailureReport.objectMetadata.push(...objectMetadata);
const allErrors = Object.values(orchestratorFailureReport);
if (allErrors.some((report) => report.length > 0)) {
@@ -1013,7 +1020,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
return {
status: 'success',
workspaceMigration: {
applicationUniversalIdentifier,
applicationUniversalIdentifier:
buildOptions.applicationUniversalIdentifier,
actions: [
// Object and fields and indexes
...aggregatedOrchestratorActionsReport.index.delete,
@@ -292,10 +292,10 @@ export class WorkspaceMigrationValidateBuildAndRunService {
});
return await this.validateBuildAndRunWorkspaceMigrationFromTo({
applicationUniversalIdentifier,
buildOptions: {
isSystemBuild,
inferDeletionFromMissingEntities,
applicationUniversalIdentifier,
},
fromToAllFlatEntityMaps,
workspaceId,
@@ -23,7 +23,6 @@ export type WorkspaceMigrationOrchestratorBuildArgs = {
fromToAllFlatEntityMaps: FromToAllUniversalFlatEntityMaps;
dependencyAllFlatEntityMaps?: Partial<AllUniversalFlatEntityMaps>;
additionalCacheDataMaps: WorkspaceMigrationBuilderAdditionalCacheDataMaps;
applicationUniversalIdentifier: string;
};
export type OrchestratorFailureReport = {
@@ -40,6 +40,7 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
buildOptions: {
inferDeletionFromMissingEntities: { fieldMetadata: false },
isSystemBuild: false,
applicationUniversalIdentifier: 'application-universal-identifier-1',
},
},
},
@@ -67,6 +68,7 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
buildOptions: {
inferDeletionFromMissingEntities: { fieldMetadata: true },
isSystemBuild: false,
applicationUniversalIdentifier: 'application-universal-identifier-1',
},
},
},
@@ -111,6 +113,7 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
buildOptions: {
inferDeletionFromMissingEntities: { fieldMetadata: false },
isSystemBuild: false,
applicationUniversalIdentifier: 'application-universal-identifier-1',
},
},
},
@@ -183,6 +186,7 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
buildOptions: {
inferDeletionFromMissingEntities: { fieldMetadata: true },
isSystemBuild: false,
applicationUniversalIdentifier: 'application-universal-identifier-1',
},
},
},
@@ -211,6 +215,7 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
buildOptions: {
inferDeletionFromMissingEntities: { fieldMetadata: false },
isSystemBuild: false,
applicationUniversalIdentifier: 'application-universal-identifier-1',
},
},
},
@@ -28,6 +28,7 @@ export const compareTwoFlatEntity = <T extends AllMetadataName>({
[fromUniversalFlatEntity, toUniversalFlatEntity].map(
(universalFlatEntity) =>
transformUniversalFlatEntityForComparison({
metadataName,
universalFlatEntity,
propertiesToCompare,
propertiesToStringify,
@@ -8,10 +8,12 @@ import { type MetadataUniversalFlatEntityPropertiesToStringify } from 'src/engin
export function transformUniversalFlatEntityForComparison<
T extends AllMetadataName,
>({
metadataName,
universalFlatEntity,
propertiesToCompare,
propertiesToStringify,
}: {
metadataName: T;
universalFlatEntity: MetadataUniversalFlatEntity<T>;
propertiesToCompare: MetadataUniversalFlatEntityPropertiesToCompare<T>[];
propertiesToStringify: MetadataUniversalFlatEntityPropertiesToStringify<T>[];
@@ -20,6 +22,18 @@ export function transformUniversalFlatEntityForComparison<
(flatEntityAccumulator, propertyToCompare) => {
const currentValue = universalFlatEntity[propertyToCompare];
// TODO remove once https://github.com/twentyhq/core-team-issues/issues/2227 has been resolved
if (
metadataName === 'index' &&
propertyToCompare === 'universalFlatIndexFieldMetadatas' &&
Array.isArray(currentValue)
) {
for (const item of currentValue) {
delete item['createdAt'];
delete item['updatedAt'];
}
}
if (
propertiesToStringify.includes(
propertyToCompare as string as MetadataUniversalFlatEntityPropertiesToStringify<T>,
@@ -0,0 +1,21 @@
import { validateObjectMetadataSystemFieldsIntegrity } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity';
import {
type OrchestratorActionsReport,
type OrchestratorFailureReport,
} from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type';
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
export const crossEntityTransversalValidation = ({
optimisticUniversalFlatMaps,
orchestratorActionsReport,
}: {
optimisticUniversalFlatMaps: AllUniversalFlatEntityMaps;
orchestratorActionsReport: OrchestratorActionsReport;
}): Pick<OrchestratorFailureReport, 'objectMetadata'> => {
const { objectMetadata } = validateObjectMetadataSystemFieldsIntegrity({
optimisticUniversalFlatMaps,
orchestratorActionsReport,
});
return { objectMetadata };
};
@@ -3,4 +3,5 @@ import { type InferDeletionFromMissingEntities } from 'src/engine/workspace-mana
export type WorkspaceMigrationBuilderOptions = {
inferDeletionFromMissingEntities?: InferDeletionFromMissingEntities;
isSystemBuild: boolean;
applicationUniversalIdentifier: string;
};
@@ -0,0 +1,40 @@
import { isDefined } from 'twenty-shared/utils';
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
export const buildUniversalFlatObjectFieldByNameAndJoinColumnMaps = ({
flatFieldMetadataMaps,
flatObjectMetadata,
}: {
flatFieldMetadataMaps: AllUniversalFlatEntityMaps['flatFieldMetadataMaps'];
flatObjectMetadata: UniversalFlatObjectMetadata;
}) => {
const fieldUniversalIdentifierByName: Record<string, string> = {};
const fieldUniversalIdentifierByJoinColumnName: Record<string, string> = {};
const objectFields =
findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow({
universalIdentifiers: flatObjectMetadata.fieldUniversalIdentifiers,
flatEntityMaps: flatFieldMetadataMaps,
});
for (const field of objectFields) {
fieldUniversalIdentifierByName[field.name] = field.universalIdentifier;
if (isMorphOrRelationUniversalFlatFieldMetadata(field)) {
if (isDefined(field.universalSettings.joinColumnName)) {
fieldUniversalIdentifierByJoinColumnName[
field.universalSettings.joinColumnName
] = field.universalIdentifier;
}
}
}
return {
fieldUniversalIdentifierByName,
fieldUniversalIdentifierByJoinColumnName,
};
};
@@ -8,6 +8,7 @@ import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agen
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { type UniversalFlatAgent } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-agent.type';
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
import { isCallerTwentyStandardApp } from 'src/engine/metadata-modules/utils/is-caller-twenty-standard-app.util';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
@@ -97,7 +98,7 @@ export class FlatAgentValidatorService {
}
if (
!buildOptions.isSystemBuild &&
!isCallerTwentyStandardApp(buildOptions) &&
belongsToTwentyStandardApp({
universalIdentifier: existingAgent.universalIdentifier,
applicationUniversalIdentifier:
@@ -148,7 +149,7 @@ export class FlatAgentValidatorService {
}
if (
!buildOptions.isSystemBuild &&
!isCallerTwentyStandardApp(buildOptions) &&
belongsToTwentyStandardApp({
universalIdentifier: fromFlatAgent.universalIdentifier,
applicationUniversalIdentifier:
@@ -14,7 +14,7 @@ import { isFlatFieldMetadataNameSyncedWithLabel } from 'src/engine/metadata-modu
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { validateFlatFieldMetadataNameAvailability } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name-availability.util';
import { validateFlatFieldMetadataName } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-flat-field-metadata-name.util';
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
import { UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
@@ -75,6 +75,31 @@ export class FlatFieldMetadataValidatorService {
flatFieldMetadataToValidate.objectMetadataUniversalIdentifier,
};
const SYSTEM_FIELD_ALLOWED_UPDATE_PROPERTIES = [
'universalSettings',
'isActive',
] as const satisfies (keyof UniversalFlatFieldMetadata)[];
if (
!buildOptions.isSystemBuild &&
existingFlatFieldMetadataToUpdate.isSystem
) {
const disallowedProperties = Object.keys(flatEntityUpdate).filter(
(property) =>
!SYSTEM_FIELD_ALLOWED_UPDATE_PROPERTIES.includes(
property as (typeof SYSTEM_FIELD_ALLOWED_UPDATE_PROPERTIES)[number],
),
);
if (disallowedProperties.length > 0) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
message: `System fields only allow updating: ${SYSTEM_FIELD_ALLOWED_UPDATE_PROPERTIES.join(', ')}. Forbidden properties: ${disallowedProperties.join(', ')}`,
userFriendlyMessage: msg`System fields cannot be updated`,
});
}
}
const flatObjectMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatFieldMetadataToValidate.objectMetadataUniversalIdentifier,
@@ -155,7 +180,7 @@ export class FlatFieldMetadataValidatorService {
flatFieldMetadataToValidate.isLabelSyncedWithName &&
!isFlatFieldMetadataNameSyncedWithLabel({
flatFieldMetadata: flatFieldMetadataToValidate,
isSystemBuild: buildOptions.isSystemBuild,
buildOptions,
})
) {
validationResult.errors.push({
@@ -205,6 +230,7 @@ export class FlatFieldMetadataValidatorService {
flatFieldMetadataMaps: optimisticFlatFieldMetadataMaps,
flatObjectMetadataMaps,
},
buildOptions,
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.fieldMetadata
>): FailedFlatEntityValidation<'fieldMetadata', 'delete'> {
@@ -244,6 +270,22 @@ export class FlatFieldMetadataValidatorService {
flatEntityMaps: flatObjectMetadataMaps,
});
const parentObjectMetadataHasBeenDeleted = !isDefined(
relatedFlatObjectMetadata,
);
if (
!buildOptions.isSystemBuild &&
flatFieldMetadataToDelete.isSystem &&
!parentObjectMetadataHasBeenDeleted
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
message: 'System fields cannot be deleted',
userFriendlyMessage: msg`System fields cannot be deleted`,
});
}
if (
isDefined(relatedFlatObjectMetadata) &&
relatedFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier ===
@@ -257,35 +299,6 @@ export class FlatFieldMetadataValidatorService {
});
}
const relationTargetObjectMetadataHasBeenDeleted =
isMorphOrRelationUniversalFlatFieldMetadata(flatFieldMetadataToDelete) &&
!isDefined(
findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatFieldMetadataToDelete.relationTargetObjectMetadataUniversalIdentifier,
flatEntityMaps: flatObjectMetadataMaps,
}),
);
const parentObjectMetadataHasBeenDeleted = !isDefined(
findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatFieldMetadataToDelete.objectMetadataUniversalIdentifier,
flatEntityMaps: flatObjectMetadataMaps,
}),
);
if (
belongsToTwentyStandardApp(flatFieldMetadataToDelete) &&
!relationTargetObjectMetadataHasBeenDeleted &&
!parentObjectMetadataHasBeenDeleted
) {
validationResult.errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message: "Standard Fields can't be deleted",
userFriendlyMessage: msg`Standard fields cannot be deleted`,
});
}
return validationResult;
}
@@ -365,7 +378,7 @@ export class FlatFieldMetadataValidatorService {
flatFieldMetadataToValidate.isLabelSyncedWithName &&
!isFlatFieldMetadataNameSyncedWithLabel({
flatFieldMetadata: flatFieldMetadataToValidate,
isSystemBuild: buildOptions.isSystemBuild,
buildOptions,
})
) {
validationResult.errors.push({
@@ -9,6 +9,7 @@ import { validateFlatObjectMetadataIdentifiers } from 'src/engine/metadata-modul
import { validateFlatObjectMetadataNameAndLabels } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util';
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
import { isCallerTwentyStandardApp } from 'src/engine/metadata-modules/utils/is-caller-twenty-standard-app.util';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
@@ -61,6 +62,14 @@ export class FlatObjectMetadataValidatorService {
nameSingular: existingFlatObjectMetadata.nameSingular,
};
if (!buildOptions.isSystemBuild && existingFlatObjectMetadata.isSystem) {
validationResult.errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`System objects cannot be updated`,
userFriendlyMessage: msg`System objects cannot be updated`,
});
}
validationResult.errors.push(
...validateFlatObjectMetadataNameAndLabels({
optimisticUniversalFlatObjectMetadataMaps:
@@ -139,8 +148,16 @@ export class FlatObjectMetadataValidatorService {
});
}
if (!buildOptions.isSystemBuild && flatObjectMetadataToDelete.isSystem) {
validationResult.errors.push({
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
message: t`System objects cannot be deleted`,
userFriendlyMessage: msg`System objects cannot be deleted`,
});
}
if (
!buildOptions.isSystemBuild &&
!isCallerTwentyStandardApp(buildOptions) &&
belongsToTwentyStandardApp(flatObjectMetadataToDelete)
) {
validationResult.errors.push({
@@ -8,6 +8,7 @@ import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules
import { SkillExceptionCode } from 'src/engine/metadata-modules/skill/skill.exception';
import { type UniversalFlatSkill } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-skill.type';
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
import { isCallerTwentyStandardApp } from 'src/engine/metadata-modules/utils/is-caller-twenty-standard-app.util';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
@@ -90,7 +91,7 @@ export class FlatSkillValidatorService {
}
if (
!buildOptions.isSystemBuild &&
!isCallerTwentyStandardApp(buildOptions) &&
belongsToTwentyStandardApp({
universalIdentifier: existingSkill.universalIdentifier,
applicationUniversalIdentifier:
@@ -153,7 +154,7 @@ export class FlatSkillValidatorService {
});
if (
!buildOptions.isSystemBuild &&
!isCallerTwentyStandardApp(buildOptions) &&
isTwentyStandardSkill &&
hasNonIsActiveUpdates
) {