feat(server): run server-exposed logic functions in the owner workspace (#22002)

## Summary

Implements the server-level logic-function tier in the simplest shape: a
logic function is "server-exposed" iff its manifest entry carries
`serverWebhookTriggerSettings`. Execution delegates to the
owner-workspace copy of that function — billing, throttling, env vars,
and the existing executor all apply uniformly against that workspace.

Supersedes #21971 with the simplified design from that discussion (no
`applicationRegistrationLogicFunction` registry, no dedicated manifest
type, no separate SDK helper, no special throttling).

## Design

- **Manifest**: `LogicFunctionManifest` gains
`serverWebhookTriggerSettings?`. The declarative `workspaceIdResolver`
shape is dropped.
- **Materialization**: those settings become two new jsonb columns on
`LogicFunctionEntity`. The manifest → flat converter and the
create-from-source DTO/util forward them; the property-config map and
editable-properties list are extended.
- **Lookup**: a single QB query joins `logicFunction → application →
applicationRegistration` and filters on `lf.workspaceId =
reg.workspaceId` to get only the owner workspace's copy.
- **Webhook**: `POST /webhooks/server/:logicFunctionUniversalIdentifier`
→ `ServerWebhookTriggerService.handle` → join lookup →
`LogicFunctionTriggerService.run`. No registry table, no
`:applicationRegistrationUniversalIdentifier` segment, no resolver.
- **Gate**: `IS_SERVER_LOGIC_FUNCTION_ENABLED` config var (disabled by
default).

## Test plan
- [x] `npx jest server-webhook-trigger` — 9 unit tests across the
webhook service.
- [x] `npx jest logic-function` — 88 existing tests stay green.
- [x] `npx nx typecheck twenty-server`.
- [x] `npx nx lint:diff-with-main twenty-server`.
- [x] Reset DB → init → run `database:migrate:prod` → run
`database:migrate:generate --name pending-migration-check` → no drift.
- [ ] Manual: hit `/webhooks/server/<uid>` end-to-end against a manifest
carrying `serverWebhookTriggerSettings`.

https://claude.ai/code/session_01GgsnCGmYJ26xRirx8va1Yh

---
_Generated by [Claude
Code](https://claude.ai/code/session_01GgsnCGmYJ26xRirx8va1Yh)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22002?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. -->
This commit is contained in:
martmull
2026-06-24 15:34:12 +02:00
committed by GitHub
parent 20ac0a52bf
commit b5a1aed24b
36 changed files with 1231 additions and 1061 deletions
@@ -132,6 +132,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
"cronTriggerSettings",
"databaseEventTriggerSettings",
"httpRouteTriggerSettings",
"serverRouteTriggerSettings",
"toolTriggerSettings",
"workflowActionTriggerSettings",
"builtHandlerPath",
@@ -140,6 +141,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
"cronTriggerSettings",
"databaseEventTriggerSettings",
"httpRouteTriggerSettings",
"serverRouteTriggerSettings",
"toolTriggerSettings",
"workflowActionTriggerSettings",
],
@@ -675,6 +675,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toStringify: true,
universalProperty: undefined,
},
serverRouteTriggerSettings: {
toCompare: true,
toStringify: true,
universalProperty: undefined,
},
toolTriggerSettings: {
toCompare: true,
toStringify: true,
@@ -10,6 +10,7 @@ export const FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES = [
'cronTriggerSettings',
'databaseEventTriggerSettings',
'httpRouteTriggerSettings',
'serverRouteTriggerSettings',
'toolTriggerSettings',
'workflowActionTriggerSettings',
'isBuildUpToDate',
@@ -15,6 +15,7 @@ import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
ServerRouteTriggerSettings,
ToolTriggerSettings,
WorkflowActionTriggerSettings,
} from 'twenty-shared/application';
@@ -72,6 +73,11 @@ export class CreateLogicFunctionFromSourceInput {
@IsOptional()
httpRouteTriggerSettings?: JsonbProperty<HttpRouteTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
serverRouteTriggerSettings?: JsonbProperty<ServerRouteTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
@@ -12,6 +12,7 @@ import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
ServerRouteTriggerSettings,
ToolTriggerSettings,
WorkflowActionTriggerSettings,
} from 'twenty-shared/application';
@@ -85,6 +86,9 @@ export class LogicFunctionEntity
@Column({ nullable: true, type: 'jsonb' })
httpRouteTriggerSettings: JsonbProperty<HttpRouteTriggerSettings> | null;
@Column({ nullable: true, type: 'jsonb' })
serverRouteTriggerSettings: JsonbProperty<ServerRouteTriggerSettings> | null;
@Column({ nullable: true, type: 'jsonb' })
toolTriggerSettings: JsonbProperty<ToolTriggerSettings> | null;
@@ -50,6 +50,7 @@ export const buildUniversalFlatLogicFunctionToCreate = (
cronTriggerSettings: input.cronTriggerSettings ?? null,
databaseEventTriggerSettings: input.databaseEventTriggerSettings ?? null,
httpRouteTriggerSettings: input.httpRouteTriggerSettings ?? null,
serverRouteTriggerSettings: input.serverRouteTriggerSettings ?? null,
toolTriggerSettings: input.toolTriggerSettings ?? null,
workflowActionTriggerSettings: input.workflowActionTriggerSettings ?? null,
createdAt: now,
@@ -56,6 +56,8 @@ export const fromCreateLogicFunctionFromSourceInputToUniversalFlatLogicFunctionT
createLogicFunctionFromSourceInput.databaseEventTriggerSettings ?? null,
httpRouteTriggerSettings:
createLogicFunctionFromSourceInput.httpRouteTriggerSettings ?? null,
serverRouteTriggerSettings:
createLogicFunctionFromSourceInput.serverRouteTriggerSettings ?? null,
toolTriggerSettings:
createLogicFunctionFromSourceInput.toolTriggerSettings ?? null,
workflowActionTriggerSettings:
@@ -22,7 +22,7 @@ import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadat
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { MinimalMetadataModule } from 'src/engine/metadata-modules/minimal-metadata/minimal-metadata.module';
import { ServerWebhookTriggerModule } from 'src/engine/core-modules/server-webhook-trigger/server-webhook-trigger.module';
import { ServerRouteTriggerModule } from 'src/engine/core-modules/server-route-trigger/server-route-trigger.module';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { RouteTriggerModule } from 'src/engine/metadata-modules/route-trigger/route-trigger.module';
import { SearchFieldMetadataModule } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.module';
@@ -55,7 +55,7 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
PermissionsModule,
PermissionFlagModule,
RouteTriggerModule,
ServerWebhookTriggerModule,
ServerRouteTriggerModule,
WebhookModule,
ConnectedAccountMetadataModule,
MessageChannelMetadataModule,