From 203da78b05f400cdff3b7c26b524cd058dc7b19f Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 3 Jul 2026 13:18:26 +0200 Subject: [PATCH] feat(logic-function): namespace shared Lambda resources per instance (#22509) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When several Twenty instances run in the **same AWS account + region** (our customer's setup), their logic-function Lambda resources collide. Four shared resources are named purely from a content hash: | Resource | Name | Derived from | |---|---|---| | Builder fn | `twenty-builder-` | handler code (⇒ version) | | Yarn-install fn | `twenty-yarn-install-` | handler code | | Common layer | `twenty-common-layer-` | dependencies | | Deps layer | `deps-` | app yarn.lock | Because the hash is identical across instances of the same version, every instance computes the **same name**. `ensureBuilderLambdaExists` / `ensureYarnInstallLambdaExists` do `GetFunction → if it exists, return` with **no role check**, so the first instance to create the function binds it to *its* `LOGIC_FUNCTION_LAMBDA_ROLE` and every other instance silently reuses it. When that role is later deleted or belongs to a different account, invokes fail with: > The role defined for the function cannot be assumed by Lambda. Nothing tears these shared resources down, so a poisoned function persists indefinitely. (Executors are UUID-named and SDK layers are workspace-scoped, so they don't collide.) ## Fix Namespace the four shared resources by a per-instance segment. - New optional config var **`LOGIC_FUNCTION_LAMBDA_RESOURCE_NAMESPACE`** (LOGIC_FUNCTION_CONFIG group). - When unset it defaults to `sha256(LOGIC_FUNCTION_LAMBDA_ROLE).slice(0, 10)`. Keying the namespace on the execution role makes the sharing boundary correct: - **same role → same names →** instances still dedupe (original intent preserved), - **different role → different names →** full isolation, invoke can never hit a role it can't assume, - **role change →** resources are recreated fresh under a new name (self-healing). Names become `twenty-builder--`, `deps--`, etc. — the owning instance stays legible in the AWS console. ### Defensive role-heal As a safety net (and to heal already-poisoned functions), after `GetFunction` succeeds we compare `Configuration.Role` to the configured role; on mismatch we delete and recreate the tool function. (`waitFunctionDeleted` polls `GetFunction` until `ResourceNotFoundException` — this SDK version has no `waitUntilFunctionNotExists` waiter.) ## Scope / compatibility - Executor functions (``) and SDK layers (`sdk--`) are unchanged — already unique. - On upgrade, shared-resource names change once (role-hash namespace), so each instance recreates its builder/yarn-install/common-layer/deps on first use; old ones are orphaned (harmless, unreferenced). - Operators who want explicit control can set `LOGIC_FUNCTION_LAMBDA_RESOURCE_NAMESPACE`. ## Ops note (immediate unblock, independent of this PR) Delete the poisoned `twenty-builder-` (and sibling `twenty-yarn-install-*`) in the affected region; it is recreated with the correct role on next use. ## Tests - `compute-hashed-lambda-resource-name.util.spec.ts` — namespace segment behavior - `get-lambda-deps-layer-name.util.spec.ts` — namespaced deps layer name - `get-lambda-resource-namespace.util.spec.ts` — stable, role-distinct namespace All pass; `lint:diff-with-main` clean; typecheck clean for changed files. Review in cubic --- .../services/lambda-layer-manager.service.ts | 15 ++++++-- .../services/lambda-tool-functions.service.ts | 14 +++++-- .../lambda/types/lambda-driver.type.ts | 1 + .../build-lambda-resource-name.util.spec.ts | 32 ++++++++++++++++ ...e-hashed-lambda-resource-name.util.spec.ts | 38 ++++++++++++++++++- .../get-lambda-deps-layer-name.util.spec.ts | 34 +++++++++++++---- ...get-lambda-resource-namespace.util.spec.ts | 33 ++++++++++++++++ .../utils/build-lambda-resource-name.util.ts | 14 +++++++ ...ompute-hashed-lambda-resource-name.util.ts | 10 +++-- .../utils/get-lambda-deps-layer-name.util.ts | 20 +++++++--- .../get-lambda-resource-namespace.util.ts | 16 ++++++++ .../logic-function-driver.factory.ts | 5 +++ 12 files changed, 208 insertions(+), 24 deletions(-) create mode 100644 packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/build-lambda-resource-name.util.spec.ts create mode 100644 packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-resource-namespace.util.spec.ts create mode 100644 packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-lambda-resource-name.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-resource-namespace.util.ts diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-layer-manager.service.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-layer-manager.service.ts index 9487055200..d9b44b307d 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-layer-manager.service.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-layer-manager.service.ts @@ -32,7 +32,10 @@ export class LambdaLayerManagerService { private readonly logger = new Logger(LambdaLayerManagerService.name); constructor( - private readonly options: Pick, + private readonly options: Pick< + LambdaDriverOptions, + 'layerBucket' | 'resourceNamespace' + >, private readonly awsClient: LambdaAwsClientService, private readonly toolFunctions: LambdaToolFunctionsService, private readonly logicFunctionResourceService: LogicFunctionResourceService, @@ -40,7 +43,10 @@ export class LambdaLayerManagerService { ) {} async ensureDepsLayer(context: LayerAppContext): Promise { - const layerName = getLambdaDepsLayerName(context.flatApplication); + const layerName = getLambdaDepsLayerName({ + flatApplication: context.flatApplication, + namespace: this.options.resourceNamespace, + }); const existingArn = await this.awsClient.getExistingLayerArn(layerName); @@ -128,7 +134,10 @@ export class LambdaLayerManagerService { return false; } - const depsLayerName = getLambdaDepsLayerName(flatApplication); + const depsLayerName = getLambdaDepsLayerName({ + flatApplication, + namespace: this.options.resourceNamespace, + }); const sdkLayerName = getLambdaSdkLayerName({ workspaceId: flatApplication.workspaceId, applicationUniversalIdentifier, diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-tool-functions.service.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-tool-functions.service.ts index 8d0cf4f032..37ae0d9729 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-tool-functions.service.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-tool-functions.service.ts @@ -52,7 +52,10 @@ export class LambdaToolFunctionsService { private builderFunctionName: string | undefined; constructor( - private readonly options: Pick, + private readonly options: Pick< + LambdaDriverOptions, + 'lambdaRole' | 'resourceNamespace' + >, private readonly awsClient: LambdaAwsClientService, ) {} @@ -322,7 +325,8 @@ export class LambdaToolFunctionsService { ]); this.commonLayerName = computeHashedLambdaResourceName({ - prefix: COMMON_LAYER_NAME_PREFIX, + resourceNamePrefix: COMMON_LAYER_NAME_PREFIX, + namespace: this.options.resourceNamespace, contents: [packageJson, yarnLock], }); @@ -340,7 +344,8 @@ export class LambdaToolFunctionsService { ); this.yarnInstallFunctionName = computeHashedLambdaResourceName({ - prefix: YARN_INSTALL_FUNCTION_NAME_PREFIX, + resourceNamePrefix: YARN_INSTALL_FUNCTION_NAME_PREFIX, + namespace: this.options.resourceNamespace, contents: [handlerContent], }); @@ -355,7 +360,8 @@ export class LambdaToolFunctionsService { const handlerContent = await fs.readFile(BUILDER_HANDLER_PATH, 'utf-8'); this.builderFunctionName = computeHashedLambdaResourceName({ - prefix: BUILDER_FUNCTION_NAME_PREFIX, + resourceNamePrefix: BUILDER_FUNCTION_NAME_PREFIX, + namespace: this.options.resourceNamespace, contents: [handlerContent], }); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/types/lambda-driver.type.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/types/lambda-driver.type.ts index 57c6c13a8d..4b215902e1 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/types/lambda-driver.type.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/types/lambda-driver.type.ts @@ -44,6 +44,7 @@ export interface LambdaDriverOptions extends LambdaClientConfig { subhostingRole?: string; layerBucket: string; layerBucketRegion: string; + resourceNamespace: string; } export enum LambdaExecutionPhase { diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/build-lambda-resource-name.util.spec.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/build-lambda-resource-name.util.spec.ts new file mode 100644 index 0000000000..f8fea3457c --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/build-lambda-resource-name.util.spec.ts @@ -0,0 +1,32 @@ +import { buildLambdaResourceName } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-lambda-resource-name.util'; + +describe('buildLambdaResourceName', () => { + it('joins prefix, namespace and checksum', () => { + expect( + buildLambdaResourceName({ + resourceNamePrefix: 'twenty-builder', + namespace: 'abc123', + checksum: 'def456', + }), + ).toBe('twenty-builder-abc123-def456'); + }); + + it('omits the namespace segment when it is undefined', () => { + expect( + buildLambdaResourceName({ + resourceNamePrefix: 'deps', + checksum: 'def456', + }), + ).toBe('deps-def456'); + }); + + it('omits the namespace segment when it is an empty string', () => { + expect( + buildLambdaResourceName({ + resourceNamePrefix: 'deps', + namespace: '', + checksum: 'def456', + }), + ).toBe('deps-def456'); + }); +}); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/compute-hashed-lambda-resource-name.util.spec.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/compute-hashed-lambda-resource-name.util.spec.ts index d05e725da8..9ae79f0871 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/compute-hashed-lambda-resource-name.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/compute-hashed-lambda-resource-name.util.spec.ts @@ -3,7 +3,43 @@ import { computeHashedLambdaResourceName } from 'src/engine/core-modules/logic-f describe('computeHashedLambdaResourceName', () => { it('returns prefix concatenated with a 12-character sha256 hex checksum of the contents', () => { const name = computeHashedLambdaResourceName({ - prefix: 'twenty-builder', + resourceNamePrefix: 'twenty-builder', + contents: ['handler-code'], + }); + + expect(name).toMatch(/^twenty-builder-[0-9a-f]{12}$/); + }); + + it('inserts the namespace as a distinct segment before the checksum', () => { + const name = computeHashedLambdaResourceName({ + resourceNamePrefix: 'twenty-builder', + namespace: 'abc123def0', + contents: ['handler-code'], + }); + + expect(name).toMatch(/^twenty-builder-abc123def0-[0-9a-f]{12}$/); + }); + + it('keeps the checksum stable and only prepends the namespace', () => { + const withoutNamespace = computeHashedLambdaResourceName({ + resourceNamePrefix: 'twenty-builder', + contents: ['handler-code'], + }); + const withNamespace = computeHashedLambdaResourceName({ + resourceNamePrefix: 'twenty-builder', + namespace: 'abc123def0', + contents: ['handler-code'], + }); + + const checksum = withoutNamespace.replace('twenty-builder-', ''); + + expect(withNamespace).toBe(`twenty-builder-abc123def0-${checksum}`); + }); + + it('omits the namespace segment when it is an empty string', () => { + const name = computeHashedLambdaResourceName({ + resourceNamePrefix: 'twenty-builder', + namespace: '', contents: ['handler-code'], }); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-deps-layer-name.util.spec.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-deps-layer-name.util.spec.ts index e74584f370..f8df6b9f79 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-deps-layer-name.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-deps-layer-name.util.spec.ts @@ -11,24 +11,44 @@ const buildFlatApplication = ( describe('getLambdaDepsLayerName', () => { it('returns deps- when yarnLockChecksum is set', () => { - expect(getLambdaDepsLayerName(buildFlatApplication())).toBe('deps-abc123'); + expect( + getLambdaDepsLayerName({ flatApplication: buildFlatApplication() }), + ).toBe('deps-abc123'); }); it('falls back to deps-default when yarnLockChecksum is undefined', () => { expect( - getLambdaDepsLayerName( - buildFlatApplication({ yarnLockChecksum: undefined }), - ), + getLambdaDepsLayerName({ + flatApplication: buildFlatApplication({ yarnLockChecksum: undefined }), + }), ).toBe('deps-default'); }); it('falls back to deps-default when yarnLockChecksum is null', () => { expect( - getLambdaDepsLayerName( - buildFlatApplication({ + getLambdaDepsLayerName({ + flatApplication: buildFlatApplication({ yarnLockChecksum: null as unknown as string, }), - ), + }), ).toBe('deps-default'); }); + + it('inserts the namespace segment when provided', () => { + expect( + getLambdaDepsLayerName({ + flatApplication: buildFlatApplication(), + namespace: 'ns123', + }), + ).toBe('deps-ns123-abc123'); + }); + + it('omits the namespace segment when it is an empty string', () => { + expect( + getLambdaDepsLayerName({ + flatApplication: buildFlatApplication(), + namespace: '', + }), + ).toBe('deps-abc123'); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-resource-namespace.util.spec.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-resource-namespace.util.spec.ts new file mode 100644 index 0000000000..fc5f1656fd --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/get-lambda-resource-namespace.util.spec.ts @@ -0,0 +1,33 @@ +import { getLambdaResourceNamespace } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-resource-namespace.util'; + +describe('getLambdaResourceNamespace', () => { + it('returns a stable 10-character hex namespace for a role ARN', () => { + const lambdaRoleArn = 'arn:aws:iam::123456789012:role/twenty-lambda'; + + const namespace = getLambdaResourceNamespace({ lambdaRoleArn }); + + expect(namespace).toMatch(/^[0-9a-f]{10}$/); + expect(getLambdaResourceNamespace({ lambdaRoleArn })).toBe(namespace); + }); + + it('produces different namespaces for different role ARNs', () => { + const namespaceA = getLambdaResourceNamespace({ + lambdaRoleArn: 'arn:aws:iam::111111111111:role/twenty-lambda', + }); + const namespaceB = getLambdaResourceNamespace({ + lambdaRoleArn: 'arn:aws:iam::222222222222:role/twenty-lambda', + }); + + expect(namespaceA).not.toBe(namespaceB); + }); + + it.each([undefined, ''])( + 'falls back to a stable sentinel namespace when the role is missing (%p)', + (lambdaRoleArn) => { + const namespace = getLambdaResourceNamespace({ lambdaRoleArn }); + + expect(namespace).toMatch(/^[0-9a-f]{10}$/); + expect(getLambdaResourceNamespace({ lambdaRoleArn })).toBe(namespace); + }, + ); +}); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-lambda-resource-name.util.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-lambda-resource-name.util.ts new file mode 100644 index 0000000000..64803efe90 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-lambda-resource-name.util.ts @@ -0,0 +1,14 @@ +import { isNonEmptyString } from '@sniptt/guards'; + +export const buildLambdaResourceName = ({ + resourceNamePrefix, + namespace, + checksum, +}: { + resourceNamePrefix: string; + namespace?: string; + checksum: string; +}): string => + isNonEmptyString(namespace) + ? `${resourceNamePrefix}-${namespace}-${checksum}` + : `${resourceNamePrefix}-${checksum}`; diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/compute-hashed-lambda-resource-name.util.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/compute-hashed-lambda-resource-name.util.ts index 0303f7f53c..ff008dcef9 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/compute-hashed-lambda-resource-name.util.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/compute-hashed-lambda-resource-name.util.ts @@ -1,12 +1,16 @@ import { createHash } from 'crypto'; +import { buildLambdaResourceName } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-lambda-resource-name.util'; + const RESOURCE_NAME_CHECKSUM_LENGTH = 12; export const computeHashedLambdaResourceName = ({ - prefix, + resourceNamePrefix, + namespace, contents, }: { - prefix: string; + resourceNamePrefix: string; + namespace?: string; contents: ReadonlyArray; }): string => { const hash = createHash('sha256'); @@ -17,5 +21,5 @@ export const computeHashedLambdaResourceName = ({ const checksum = hash.digest('hex').slice(0, RESOURCE_NAME_CHECKSUM_LENGTH); - return `${prefix}-${checksum}`; + return buildLambdaResourceName({ resourceNamePrefix, namespace, checksum }); }; diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-deps-layer-name.util.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-deps-layer-name.util.ts index 52455b80d5..8a237be027 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-deps-layer-name.util.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-deps-layer-name.util.ts @@ -1,9 +1,17 @@ import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { buildLambdaResourceName } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-lambda-resource-name.util'; -export const getLambdaDepsLayerName = ( - flatApplication: FlatApplication, -): string => { - const checksum = flatApplication.yarnLockChecksum ?? 'default'; +const DEPS_LAYER_NAME_PREFIX = 'deps'; - return `deps-${checksum}`; -}; +export const getLambdaDepsLayerName = ({ + flatApplication, + namespace, +}: { + flatApplication: FlatApplication; + namespace?: string; +}): string => + buildLambdaResourceName({ + resourceNamePrefix: DEPS_LAYER_NAME_PREFIX, + namespace, + checksum: flatApplication.yarnLockChecksum ?? 'default', + }); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-resource-namespace.util.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-resource-namespace.util.ts new file mode 100644 index 0000000000..9196709c16 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-resource-namespace.util.ts @@ -0,0 +1,16 @@ +import { createHash } from 'crypto'; + +import { isNonEmptyString } from '@sniptt/guards'; + +const RESOURCE_NAMESPACE_LENGTH = 10; +const NO_ROLE_SENTINEL = 'no-role'; + +export const getLambdaResourceNamespace = ({ + lambdaRoleArn, +}: { + lambdaRoleArn?: string; +}): string => + createHash('sha256') + .update(isNonEmptyString(lambdaRoleArn) ? lambdaRoleArn : NO_ROLE_SENTINEL) + .digest('hex') + .slice(0, RESOURCE_NAMESPACE_LENGTH); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/logic-function-driver.factory.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/logic-function-driver.factory.ts index befc0ee0fa..7bd07dfb30 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/logic-function-driver.factory.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/logic-function-driver.factory.ts @@ -11,6 +11,7 @@ import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock. import { DisabledDriver } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/disabled.driver'; import { LambdaDriver } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver'; import { LocalDriver } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/local.driver'; +import { getLambdaResourceNamespace } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/get-lambda-resource-namespace.util'; import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service'; import { SdkClientArchiveService } from 'src/engine/core-modules/sdk-client/sdk-client-archive.service'; import { DriverFactoryBase } from 'src/engine/core-modules/twenty-config/dynamic-factory.base'; @@ -82,6 +83,9 @@ export class LogicFunctionDriverFactory extends DriverFactoryBase