Files
twenty/packages
Charles Bochet 203da78b05 feat(logic-function): namespace shared Lambda resources per instance (#22509)
## 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-<sha256(handler)>` | handler code (⇒
version) |
| Yarn-install fn | `twenty-yarn-install-<sha256(handler)>` | handler
code |
| Common layer | `twenty-common-layer-<sha256(pkg+lock)>` | dependencies
|
| Deps layer | `deps-<yarnLockChecksum>` | 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-<namespace>-<checksum>`,
`deps-<namespace>-<checksum>`, 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 (`<logicFunctionId>`) and SDK layers
(`sdk-<workspaceId>-<appUUID>`) 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-<hash>` (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.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22509?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-03 13:18:26 +02:00
..