diff --git a/.changeset/automation-client-event-catalog-trigger.md b/.changeset/automation-client-event-catalog-trigger.md new file mode 100644 index 000000000..80405a532 --- /dev/null +++ b/.changeset/automation-client-event-catalog-trigger.md @@ -0,0 +1,10 @@ +--- +"@epilot/automation-client": minor +--- + +Add Event Catalog trigger support + +- `EventCatalogTrigger` (`type: event_catalog`) — starts a flow when an Event Catalog event is published; pinned to an `event_version`, runs in the context of the entity behind `entity_node_id`, `ignore_automation_triggered` defaults to `true` +- `TriggerEventEventCatalog` — new `AutomationExecution.trigger_event` variant with `event_id`, `event_name`, `event_version`, `published_version`, `entity_node_id` and a `payload_ref` to the stored payload +- `ConditionStatement.source.originType` accepts `event` to read the operand from the trigger's event payload +- `searchFlows` accepts a `trigger_event_name` query parameter to filter flows by Event Catalog event diff --git a/.changeset/event-catalog-client-automation-chain.md b/.changeset/event-catalog-client-automation-chain.md new file mode 100644 index 000000000..729df3d5f --- /dev/null +++ b/.changeset/event-catalog-client-automation-chain.md @@ -0,0 +1,7 @@ +--- +"@epilot/event-catalog-client": patch +--- + +Add `_automation_chain` to `TriggerEventPayload` and `Event` + +Optional ordered list of automation flow ids that caused the trigger. It is propagated verbatim onto the published event so that automations started by Event Catalog events can detect and break automation loops. diff --git a/clients/automation-client/src/openapi-runtime.json b/clients/automation-client/src/openapi-runtime.json index 378153e7d..319193abd 100644 --- a/clients/automation-client/src/openapi-runtime.json +++ b/clients/automation-client/src/openapi-runtime.json @@ -29,6 +29,10 @@ "name": "target_workflow", "in": "query" }, + { + "name": "trigger_event_name", + "in": "query" + }, { "name": "include_flows", "in": "query" diff --git a/clients/automation-client/src/openapi.d.ts b/clients/automation-client/src/openapi.d.ts index 8653c3472..ded3f8261 100644 --- a/clients/automation-client/src/openapi.d.ts +++ b/clients/automation-client/src/openapi.d.ts @@ -560,7 +560,23 @@ declare namespace Components { * ``` * */ - EntityOperationTrigger | ActivityTrigger | EntityManualTrigger | ReceivedEmailTrigger | NewEmailThreadTrigger | FlowsTrigger; + EntityOperationTrigger | ActivityTrigger | EntityManualTrigger | ReceivedEmailTrigger | NewEmailThreadTrigger | FlowsTrigger | /** + * Starts the flow when an Event Catalog event is published for the organization. The execution runs in the context of one entity from the event's entity graph (`entity_node_id`), and the event payload is available to conditions and actions as the `event` variable context. + * + * example: + * { + * "id": "12d4f45a-1883-4841-a94c-5928cb338a94", + * "type": "event_catalog", + * "configuration": { + * "event_name": "CustomerRequestSubmitted", + * "event_version": "1.1", + * "entity_node_id": "ticket", + * "entity_schema": "ticket", + * "ignore_automation_triggered": true + * } + * } + */ + EventCatalogTrigger; export interface AnythingButCondition { "anything-but"?: string[]; } @@ -1278,14 +1294,14 @@ declare namespace Components { * 2 */ version?: number; - trigger_event?: TriggerEventManual | TriggerEventEntityActivity | TriggerEventEntityOperation | TriggerEventFlowAutomationTask | TriggerEventMessaging; - workflow_context?: /** - * Automation Executions triggered by workflow task automations will always carry information about the triggering workflow. This information is helpful in correlating workflow executions with all the triggered automation executions + trigger_event?: TriggerEventManual | TriggerEventEntityActivity | TriggerEventEntityOperation | TriggerEventFlowAutomationTask | TriggerEventMessaging | /** + * Set on executions started by an Event Catalog event (see EventCatalogTrigger). The full event payload is not stored inline (it may be up to 256 KB) but by reference in `payload_ref`; automation workers hydrate it before every action. * */ - WorkflowExecutionContext; + TriggerEventEventCatalog; + workflow_context?: WorkflowExecutionContext; workflow_wait_context?: /** - * Workflow automation tasks can be paused & waiting for journey automation executions to succeed. If such is the case, this context tracks the task_id of the workflow waiting for journey submission success This context is consumed by svc-workflows to resume a task waiting on this journey submission. + * Correlation stamped when the triggering submission entity carried workflow wait claims from a journey link (AL-2521). Consumed by svc-workflows to resume a task waiting on this journey submission. * */ WorkflowWaitContext; @@ -1876,7 +1892,11 @@ declare namespace Components { */ id?: string; origin?: "trigger" | "action"; - originType?: "entity" | "workflow" | "journey_block"; + /** + * `event` reads the operand from the trigger's Event Catalog payload instead of an entity: `attribute` is then a dot path into the payload (e.g. `ticket.subject`). Only valid with `origin: trigger` on flows started by an EventCatalogTrigger. + * + */ + originType?: "entity" | "workflow" | "journey_block" | "event"; schema?: string; attribute?: string; attributeType?: "string" | "text" | "number" | "boolean" | "date" | "datetime" | "tags" | "country" | "email" | "phone" | "product" | "price" | "status" | "relation" | "multiselect" | "select" | "radio" | "relation_user" | "purpose" | "label" | "payment" | "relation_payment_method"; @@ -2229,7 +2249,7 @@ declare namespace Components { schema?: string; }; } - export type EntityOperation = "createEntity" | "updateEntity" | "deleteEntity" | "softDeleteEntity" | "restoreEntity"; + export type EntityOperation = "createEntity" | "updateEntity" | "deleteEntity" | "softDeleteEntity" | "restoreEntity" | "relationsAdded" | "relationsRemoved" | "relationsSoftDeleted" | "relationsRestored" | "relationsDeleted"; /** * - If provides filter_config, executes an automation based on the filtered configuration when an entity event occurs. * - The conditions on a filter follows the event bridge patterns - `https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html` @@ -2637,6 +2657,63 @@ declare namespace Components { details?: ErrorDetail[]; }; } + /** + * Starts the flow when an Event Catalog event is published for the organization. The execution runs in the context of one entity from the event's entity graph (`entity_node_id`), and the event payload is available to conditions and actions as the `event` variable context. + * + * example: + * { + * "id": "12d4f45a-1883-4841-a94c-5928cb338a94", + * "type": "event_catalog", + * "configuration": { + * "event_name": "CustomerRequestSubmitted", + * "event_version": "1.1", + * "entity_node_id": "ticket", + * "entity_schema": "ticket", + * "ignore_automation_triggered": true + * } + * } + */ + export interface EventCatalogTrigger { + /** + * example: + * 12d4f45a-1883-4841-a94c-5928cb338a94 + */ + id?: string; // uuid + type: "event_catalog"; + configuration: { + /** + * Name of the Event Catalog event that starts this flow + * example: + * CustomerRequestSubmitted + */ + event_name: string; + /** + * Event version (MAJOR.MINOR) this trigger is pinned to. Payloads published in a newer version are downgraded to this version before the flow runs, so field references in conditions and actions stay stable. + * + * example: + * 1.1 + */ + event_version: string; + /** + * Id of the cardinality-one node in the event's entity graph whose entity becomes the execution's main entity (the entity actions operate on). + * + * example: + * ticket + */ + entity_node_id: string; + /** + * Schema of the entity behind `entity_node_id`, denormalized from the event definition at configuration time + * example: + * ticket + */ + entity_schema: string; + /** + * When true (default), events that were emitted by an automation (`_trigger_source_type: automation`) do not start this flow. This prevents automation → event → automation chains unless explicitly enabled. + * + */ + ignore_automation_triggered?: boolean; + }; + } /** * Execution item for bulk trigger automation. It maps each entity to its automation execution id & status */ @@ -3054,6 +3131,107 @@ declare namespace Components { total: number; results: AutomationExecution[]; } + /** + * One multi-hop graph query to run against entity-api's `POST /v1/entity:graph`. Every node + * in `graph.nodes` is merged into the mapping's source context under its own `id`, so + * listing multiple nodes here costs one entity-api call, not one per node. A node's value + * overwrites any existing source-context key of the same name (the source entity, a 1-hop + * relation, or another graph node). + * + */ + export interface GraphContextEntry { + seed: GraphSeed; + graph: GraphDefinition; + } + export interface GraphDefinition { + /** + * List of node definitions in the graph + */ + nodes: GraphNode[]; + /** + * List of edge definitions connecting nodes + */ + edges: GraphEdge[]; + } + export interface GraphEdge { + /** + * Source node ID + * example: + * contact + */ + from: string; + /** + * Target node ID + * example: + * billing_account + */ + to: string; + } + export interface GraphNode { + /** + * Unique identifier for this node in the graph definition + * example: + * contact + */ + id: string; + /** + * Entity schema slug for this node + * example: + * contact + */ + schema: string; + /** + * "one": this node resolves to a single entity. "many" (default if unset): this node + * resolves to an array of entities. The seed node always returns a single entity + * regardless of this setting. + * + * example: + * one + */ + cardinality?: "one" | "many"; + /** + * Optional entity fields to include in the hydrated response for this node. + */ + fields?: string[]; + /** + * Narrows this node's traversal results to entities matching every filter (AND + * semantics). Useful for disambiguating among multiple entities reachable via the same + * graph edge. + * + */ + filter?: /* Entities are included in this node's result only if `attribute` exactly equals the literal `value`. */ GraphNodeFilter[]; + } + /** + * Entities are included in this node's result only if `attribute` exactly equals the literal `value`. + */ + export interface GraphNodeFilter { + /** + * Entity attribute name to match against. + * example: + * order_number + */ + attribute: string; + /** + * Literal value the attribute must exactly equal for the entity to be included in this node's result. Supports `{{handlebars}}` placeholders resolved against sourceContext when given as a string. + * example: + * OR-113 + */ + value: /** + * Literal value the attribute must exactly equal for the entity to be included in this node's result. Supports `{{handlebars}}` placeholders resolved against sourceContext when given as a string. + * example: + * OR-113 + */ + (string | null) | number | boolean; + } + export interface GraphSeed { + entity_id: string; // uuid + /** + * The node ID in the graph definition that corresponds to the seed entity + * example: + * contact + */ + node_id: string; + } export interface InformERPAction { id?: /** * example: @@ -3509,6 +3687,27 @@ declare namespace Components { * Relation tags (labels) to include in main entity linkback relation attribute */ linkback_relation_tags?: string[]; + /** + * Multi-hop graph queries (executed via entity-api's `POST /v1/entity:graph`), each + * merging every traversed node into the mapping's source context under its own node id, + * so `mapping_attributes` (e.g. `_copy` / `_template`) can reference it alongside the + * main entity, e.g. `{ "_copy": "order.line_items" }`. + * + * `seed.entity_id` and any node `filter[].value` may contain `{{handlebars}}` + * placeholders (e.g. `{{trigger.entity._id}}`) that are resolved at execution time + * against the mapping's source context. Resolution and graph execution happen in + * entity-mapping-api; this API only accepts, validates and passes through this field. + * + */ + graph_context?: /** + * One multi-hop graph query to run against entity-api's `POST /v1/entity:graph`. Every node + * in `graph.nodes` is merged into the mapping's source context under its own `id`, so + * listing multiple nodes here costs one entity-api call, not one per node. A node's value + * overwrites any existing source-context key of the same name (the source entity, a 1-hop + * relation, or another graph node). + * + */ + GraphContextEntry[]; } export type MappingAttribute = SetValueMapper | CopyValueMapper | AppendValueMapper; /** @@ -3892,8 +4091,10 @@ declare namespace Components { email_template_id?: string; /** * Language code for the email template + * example: + * de */ - language_code?: "de" | "en"; + language_code?: string; /** * Controls how the reply email is sent. * - reply_in_thread: Sends the email as a reply within the existing email thread (default). @@ -4122,7 +4323,11 @@ declare namespace Components { } export interface SendEmailConfig { email_template_id?: string; - language_code?: "de" | "en"; + /** + * example: + * de + */ + language_code?: string; /** * Send an email exclusively to the portal user if they are registered on the portal. */ @@ -4256,11 +4461,7 @@ declare namespace Components { * 7791b04a-16d2-44a2-9af9-2d59c25c512f */ AutomationFlowId; - workflow_context?: /** - * Automation Executions triggered by workflow task automations will always carry information about the triggering workflow. This information is helpful in correlating workflow executions with all the triggered automation executions - * - */ - WorkflowExecutionContext; + workflow_context?: WorkflowExecutionContext; /** * Use workflow_context.workflow_exec_id instead */ @@ -4487,6 +4688,64 @@ declare namespace Components { ActivityId; operation_type: EntityOperation; } + /** + * Set on executions started by an Event Catalog event (see EventCatalogTrigger). The full event payload is not stored inline (it may be up to 256 KB) but by reference in `payload_ref`; automation workers hydrate it before every action. + * + */ + export interface TriggerEventEventCatalog { + type: "event_catalog"; + /** + * example: + * 123 + */ + org_id: string; + entity_id: /** + * example: + * e3d3ebac-baab-4395-abf4-50b5bf1f8b74 + */ + EntityId; + /** + * Node id of the event's entity graph that was resolved to `entity_id` + * example: + * ticket + */ + entity_node_id: string; + /** + * Event Catalog event id (ULID) of the published event + * example: + * 01K3ZK8QZ7Y7Q2M8W1V3N4X5P6 + */ + event_id: string; + /** + * example: + * CustomerRequestSubmitted + */ + event_name: string; + /** + * Version the payload was delivered in (the version pinned on the trigger) + * example: + * 1.1 + */ + event_version: string; + /** + * Version the event was originally published with, before downgrading to `event_version` + * example: + * 1.2 + */ + published_version?: string; + event_time?: string; // date-time + /** + * How the event was triggered in the catalog (api | automation | operation | portal_user) + * example: + * operation + */ + trigger_source_type?: string; + /** + * Opaque source reference from the catalog (e.g. activity id, automation execution id) + */ + trigger_source?: string; + payload_ref?: /* S3 reference to the stored trigger event payload (`_downgrades` stripped, downgraded to the pinned version) */ TriggerEventPayloadRef; + } export interface TriggerEventFlowAutomationTask { type?: "flow_automation_task"; /** @@ -4540,6 +4799,21 @@ declare namespace Components { */ EntityId; } + /** + * S3 reference to the stored trigger event payload (`_downgrades` stripped, downgraded to the pinned version) + */ + export interface TriggerEventPayloadRef { + /** + * example: + * automation-trigger-payloads-prod + */ + bucket: string; + /** + * example: + * trigger-payloads/123/6c8e5d9a-7f1b-4c2e-9d3a-0b1c2d3e4f5a.json + */ + key: string; + } export interface TriggerShareEntityAction { id?: /** * example: @@ -5037,10 +5311,6 @@ declare namespace Components { * The role this automation plays in the workflow. */ export type WorkflowContextRole = "trigger_workflow" | "run_task_automation"; - /** - * Automation Executions triggered by workflow task automations will always carry information about the triggering workflow. This information is helpful in correlating workflow executions with all the triggered automation executions - * - */ export interface WorkflowExecutionContext { workflow_exec_id: string; workflow_exec_task_id?: string; @@ -5060,9 +5330,16 @@ declare namespace Components { entity_schema?: string; is_primary?: boolean; }[]; + /** + * Id of the user on whose behalf the workflow task triggered this automation — the caller of a user-initiated run, or the user whose task completion advanced the flow to the automation task. Action workers resolve it as the acting user for template variable resolution (user.*) and email thread assignment, matching the attribution of manually triggered executions. Absent when no human action led to the trigger (scheduler fires, journey-triggered flows). + * + * example: + * 10006129 + */ + trigger_user_id?: string; } /** - * Workflow automation tasks can be paused & waiting for journey automation executions to succeed. If such is the case, this context tracks the task_id of the workflow waiting for journey submission success This context is consumed by svc-workflows to resume a task waiting on this journey submission. + * Correlation stamped when the triggering submission entity carried workflow wait claims from a journey link (AL-2521). Consumed by svc-workflows to resume a task waiting on this journey submission. * */ export interface WorkflowWaitContext { @@ -5497,6 +5774,11 @@ declare namespace Paths { * wfABCDEFGH */ export type TargetWorkflow = string; + /** + * example: + * CustomerRequestSubmitted + */ + export type TriggerEventName = string; /** * example: * 600945fe-212e-4b97-acf7-391d64648384 @@ -5521,6 +5803,11 @@ declare namespace Paths { * wfABCDEFGH */ Parameters.TargetWorkflow; + trigger_event_name?: /** + * example: + * CustomerRequestSubmitted + */ + Parameters.TriggerEventName; include_flows?: Parameters.IncludeFlows; } namespace Responses { @@ -5993,6 +6280,7 @@ export type ErrorCode = Components.Schemas.ErrorCode; export type ErrorDetail = Components.Schemas.ErrorDetail; export type ErrorObject = Components.Schemas.ErrorObject; export type ErrorOutput = Components.Schemas.ErrorOutput; +export type EventCatalogTrigger = Components.Schemas.EventCatalogTrigger; export type ExecItem = Components.Schemas.ExecItem; export type ExecutionChain = Components.Schemas.ExecutionChain; export type ExecutionStatus = Components.Schemas.ExecutionStatus; @@ -6007,6 +6295,12 @@ export type ForwardEmailActionConfig = Components.Schemas.ForwardEmailActionConf export type ForwardEmailConfig = Components.Schemas.ForwardEmailConfig; export type FrontendSubmitTrigger = Components.Schemas.FrontendSubmitTrigger; export type GetExecutionsResp = Components.Schemas.GetExecutionsResp; +export type GraphContextEntry = Components.Schemas.GraphContextEntry; +export type GraphDefinition = Components.Schemas.GraphDefinition; +export type GraphEdge = Components.Schemas.GraphEdge; +export type GraphNode = Components.Schemas.GraphNode; +export type GraphNodeFilter = Components.Schemas.GraphNodeFilter; +export type GraphSeed = Components.Schemas.GraphSeed; export type InformERPAction = Components.Schemas.InformERPAction; export type InformERPActionConfig = Components.Schemas.InformERPActionConfig; export type InformERPConfig = Components.Schemas.InformERPConfig; @@ -6058,9 +6352,11 @@ export type TriggerEventActionConfig = Components.Schemas.TriggerEventActionConf export type TriggerEventConfig = Components.Schemas.TriggerEventConfig; export type TriggerEventEntityActivity = Components.Schemas.TriggerEventEntityActivity; export type TriggerEventEntityOperation = Components.Schemas.TriggerEventEntityOperation; +export type TriggerEventEventCatalog = Components.Schemas.TriggerEventEventCatalog; export type TriggerEventFlowAutomationTask = Components.Schemas.TriggerEventFlowAutomationTask; export type TriggerEventManual = Components.Schemas.TriggerEventManual; export type TriggerEventMessaging = Components.Schemas.TriggerEventMessaging; +export type TriggerEventPayloadRef = Components.Schemas.TriggerEventPayloadRef; export type TriggerShareEntityAction = Components.Schemas.TriggerShareEntityAction; export type TriggerShareEntityActionConfig = Components.Schemas.TriggerShareEntityActionConfig; export type TriggerShareEntityConfig = Components.Schemas.TriggerShareEntityConfig; diff --git a/clients/automation-client/src/openapi.json b/clients/automation-client/src/openapi.json index 1df104b64..92e0254c3 100644 --- a/clients/automation-client/src/openapi.json +++ b/clients/automation-client/src/openapi.json @@ -79,6 +79,15 @@ "example": "wfABCDEFGH" } }, + { + "name": "trigger_event_name", + "in": "query", + "description": "Filter by Event Catalog event name. Returns only automations that have an event_catalog trigger for this event.", + "schema": { + "type": "string", + "example": "CustomerRequestSubmitted" + } + }, { "name": "include_flows", "in": "query", @@ -1249,6 +1258,9 @@ }, { "$ref": "#/components/schemas/FlowsTrigger" + }, + { + "$ref": "#/components/schemas/EventCatalogTrigger" } ] }, @@ -1900,12 +1912,168 @@ "items": { "type": "string" } + }, + "graph_context": { + "type": "array", + "description": "Multi-hop graph queries (executed via entity-api's `POST /v1/entity:graph`), each\nmerging every traversed node into the mapping's source context under its own node id,\nso `mapping_attributes` (e.g. `_copy` / `_template`) can reference it alongside the\nmain entity, e.g. `{ \"_copy\": \"order.line_items\" }`.\n\n`seed.entity_id` and any node `filter[].value` may contain `{{handlebars}}`\nplaceholders (e.g. `{{trigger.entity._id}}`) that are resolved at execution time\nagainst the mapping's source context. Resolution and graph execution happen in\nentity-mapping-api; this API only accepts, validates and passes through this field.\n", + "items": { + "$ref": "#/components/schemas/GraphContextEntry" + } } }, "required": [ "target_schema" ] }, + "GraphContextEntry": { + "type": "object", + "description": "One multi-hop graph query to run against entity-api's `POST /v1/entity:graph`. Every node\nin `graph.nodes` is merged into the mapping's source context under its own `id`, so\nlisting multiple nodes here costs one entity-api call, not one per node. A node's value\noverwrites any existing source-context key of the same name (the source entity, a 1-hop\nrelation, or another graph node).\n", + "required": [ + "seed", + "graph" + ], + "properties": { + "seed": { + "$ref": "#/components/schemas/GraphSeed" + }, + "graph": { + "$ref": "#/components/schemas/GraphDefinition" + } + } + }, + "GraphSeed": { + "type": "object", + "required": [ + "entity_id", + "node_id" + ], + "properties": { + "entity_id": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "description": "The node ID in the graph definition that corresponds to the seed entity", + "example": "contact" + } + } + }, + "GraphDefinition": { + "type": "object", + "required": [ + "nodes", + "edges" + ], + "properties": { + "nodes": { + "type": "array", + "description": "List of node definitions in the graph", + "items": { + "$ref": "#/components/schemas/GraphNode" + } + }, + "edges": { + "type": "array", + "description": "List of edge definitions connecting nodes", + "items": { + "$ref": "#/components/schemas/GraphEdge" + } + } + } + }, + "GraphNode": { + "type": "object", + "required": [ + "id", + "schema" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this node in the graph definition", + "example": "contact" + }, + "schema": { + "type": "string", + "description": "Entity schema slug for this node", + "example": "contact" + }, + "cardinality": { + "type": "string", + "enum": [ + "one", + "many" + ], + "description": "\"one\": this node resolves to a single entity. \"many\" (default if unset): this node\nresolves to an array of entities. The seed node always returns a single entity\nregardless of this setting.\n", + "example": "one" + }, + "fields": { + "type": "array", + "description": "Optional entity fields to include in the hydrated response for this node.", + "items": { + "type": "string" + } + }, + "filter": { + "type": "array", + "description": "Narrows this node's traversal results to entities matching every filter (AND\nsemantics). Useful for disambiguating among multiple entities reachable via the same\ngraph edge.\n", + "items": { + "$ref": "#/components/schemas/GraphNodeFilter" + } + } + } + }, + "GraphNodeFilter": { + "type": "object", + "description": "Entities are included in this node's result only if `attribute` exactly equals the literal `value`.", + "required": [ + "attribute", + "value" + ], + "properties": { + "attribute": { + "type": "string", + "description": "Entity attribute name to match against.", + "example": "order_number" + }, + "value": { + "description": "Literal value the attribute must exactly equal for the entity to be included in this node's result. Supports `{{handlebars}}` placeholders resolved against sourceContext when given as a string.", + "oneOf": [ + { + "type": "string", + "nullable": true + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ], + "example": "OR-113" + } + } + }, + "GraphEdge": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string", + "description": "Source node ID", + "example": "contact" + }, + "to": { + "type": "string", + "description": "Target node ID", + "example": "billing_account" + } + } + }, "MappingConfigRef": { "type": "object", "properties": { @@ -2663,10 +2831,7 @@ "language_code": { "type": "string", "description": "Language code for the email template", - "enum": [ - "de", - "en" - ] + "example": "de" }, "reply_mode": { "type": "string", @@ -2697,10 +2862,7 @@ }, "language_code": { "type": "string", - "enum": [ - "de", - "en" - ] + "example": "de" }, "notify_portal_user_only": { "type": "boolean", @@ -3503,10 +3665,12 @@ }, "originType": { "type": "string", + "description": "`event` reads the operand from the trigger's Event Catalog payload instead of an entity: `attribute` is then a dot path into the payload (e.g. `ticket.subject`). Only valid with `origin: trigger` on flows started by an EventCatalogTrigger.\n", "enum": [ "entity", "workflow", - "journey_block" + "journey_block", + "event" ] }, "schema": { @@ -3796,6 +3960,9 @@ }, { "$ref": "#/components/schemas/TriggerEventMessaging" + }, + { + "$ref": "#/components/schemas/TriggerEventEventCatalog" } ] }, @@ -3855,7 +4022,6 @@ }, "WorkflowExecutionContext": { "type": "object", - "description": "Automation Executions triggered by workflow task automations will always carry information about the triggering workflow. This information is helpful in correlating workflow executions with all the triggered automation executions\n", "required": [ "workflow_exec_id", "workflow_role" @@ -3897,12 +4063,17 @@ } } } + }, + "trigger_user_id": { + "type": "string", + "description": "Id of the user on whose behalf the workflow task triggered this automation — the caller of a user-initiated run, or the user whose task completion advanced the flow to the automation task. Action workers resolve it as the acting user for template variable resolution (user.*) and email thread assignment, matching the attribution of manually triggered executions. Absent when no human action led to the trigger (scheduler fires, journey-triggered flows).\n", + "example": "10006129" } } }, "WorkflowWaitContext": { "type": "object", - "description": "Workflow automation tasks can be paused & waiting for journey automation executions to succeed. If such is the case, this context tracks the task_id of the workflow waiting for journey submission success This context is consumed by svc-workflows to resume a task waiting on this journey submission.\n", + "description": "Correlation stamped when the triggering submission entity carried workflow wait claims from a journey link (AL-2521). Consumed by svc-workflows to resume a task waiting on this journey submission.\n", "required": [ "workflow_execution_id", "workflow_task_id" @@ -4088,6 +4259,92 @@ "operation_type" ] }, + "TriggerEventEventCatalog": { + "type": "object", + "description": "Set on executions started by an Event Catalog event (see EventCatalogTrigger). The full event payload is not stored inline (it may be up to 256 KB) but by reference in `payload_ref`; automation workers hydrate it before every action.\n", + "properties": { + "type": { + "type": "string", + "enum": [ + "event_catalog" + ] + }, + "org_id": { + "type": "string", + "example": "123" + }, + "entity_id": { + "$ref": "#/components/schemas/EntityId" + }, + "entity_node_id": { + "type": "string", + "description": "Node id of the event's entity graph that was resolved to `entity_id`", + "example": "ticket" + }, + "event_id": { + "type": "string", + "description": "Event Catalog event id (ULID) of the published event", + "example": "01K3ZK8QZ7Y7Q2M8W1V3N4X5P6" + }, + "event_name": { + "type": "string", + "example": "CustomerRequestSubmitted" + }, + "event_version": { + "type": "string", + "description": "Version the payload was delivered in (the version pinned on the trigger)", + "example": "1.1" + }, + "published_version": { + "type": "string", + "description": "Version the event was originally published with, before downgrading to `event_version`", + "example": "1.2" + }, + "event_time": { + "type": "string", + "format": "date-time" + }, + "trigger_source_type": { + "type": "string", + "description": "How the event was triggered in the catalog (api | automation | operation | portal_user)", + "example": "operation" + }, + "trigger_source": { + "type": "string", + "description": "Opaque source reference from the catalog (e.g. activity id, automation execution id)" + }, + "payload_ref": { + "$ref": "#/components/schemas/TriggerEventPayloadRef" + } + }, + "required": [ + "type", + "org_id", + "entity_id", + "entity_node_id", + "event_id", + "event_name", + "event_version" + ] + }, + "TriggerEventPayloadRef": { + "type": "object", + "description": "S3 reference to the stored trigger event payload (`_downgrades` stripped, downgraded to the pinned version)", + "properties": { + "bucket": { + "type": "string", + "example": "automation-trigger-payloads-prod" + }, + "key": { + "type": "string", + "example": "trigger-payloads/123/6c8e5d9a-7f1b-4c2e-9d3a-0b1c2d3e4f5a.json" + } + }, + "required": [ + "bucket", + "key" + ] + }, "ApiCallerContext": { "type": "object", "additionalProperties": true, @@ -4999,7 +5256,12 @@ "updateEntity", "deleteEntity", "softDeleteEntity", - "restoreEntity" + "restoreEntity", + "relationsAdded", + "relationsRemoved", + "relationsSoftDeleted", + "relationsRestored", + "relationsDeleted" ] }, "EntitySearchFilter": { @@ -5205,6 +5467,76 @@ } ] }, + "EventCatalogTrigger": { + "description": "Starts the flow when an Event Catalog event is published for the organization. The execution runs in the context of one entity from the event's entity graph (`entity_node_id`), and the event payload is available to conditions and actions as the `event` variable context.\n", + "allOf": [ + { + "$ref": "#/components/schemas/AutomationTrigger" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "event_catalog" + ] + }, + "configuration": { + "type": "object", + "properties": { + "event_name": { + "type": "string", + "description": "Name of the Event Catalog event that starts this flow", + "example": "CustomerRequestSubmitted" + }, + "event_version": { + "type": "string", + "description": "Event version (MAJOR.MINOR) this trigger is pinned to. Payloads published in a newer version are downgraded to this version before the flow runs, so field references in conditions and actions stay stable.\n", + "example": "1.1" + }, + "entity_node_id": { + "type": "string", + "description": "Id of the cardinality-one node in the event's entity graph whose entity becomes the execution's main entity (the entity actions operate on).\n", + "example": "ticket" + }, + "entity_schema": { + "type": "string", + "description": "Schema of the entity behind `entity_node_id`, denormalized from the event definition at configuration time", + "example": "ticket" + }, + "ignore_automation_triggered": { + "type": "boolean", + "default": true, + "description": "When true (default), events that were emitted by an automation (`_trigger_source_type: automation`) do not start this flow. This prevents automation → event → automation chains unless explicitly enabled.\n" + } + }, + "required": [ + "event_name", + "event_version", + "entity_node_id", + "entity_schema" + ] + } + }, + "required": [ + "type", + "configuration" + ] + } + ], + "example": { + "id": "12d4f45a-1883-4841-a94c-5928cb338a94", + "type": "event_catalog", + "configuration": { + "event_name": "CustomerRequestSubmitted", + "event_version": "1.1", + "entity_node_id": "ticket", + "entity_schema": "ticket", + "ignore_automation_triggered": true + } + } + }, "TriggerCondition": { "type": "object", "properties": { diff --git a/clients/event-catalog-client/src/openapi.d.ts b/clients/event-catalog-client/src/openapi.d.ts index 4535df896..96cc454f7 100644 --- a/clients/event-catalog-client/src/openapi.d.ts +++ b/clients/event-catalog-client/src/openapi.d.ts @@ -209,6 +209,10 @@ declare namespace Components { * Inline downgrade chain stamped by Event Catalog at publish time, ordered newest-to-oldest. Present ONLY on multi-version events. Internal versioning transport: consumers (e.g. svc-webhooks) walk the payload back to a pinned version using these JSONata steps, then strip the field before delivery -- end customers never receive it. */ _downgrades?: /* One step of an event's inline `_downgrades` chain. Maps the current-version payload to the previous version via a JSONata expression. Stamped by Event Catalog at publish time; executed by consumers during walk-back, never by EC itself. */ InlineDowngradeStep[]; + /** + * Ordered automation flow ids that caused this event (at most 100), propagated verbatim from the trigger input or the originating entity operation. Internal loop-prevention transport for automation-api; svc-webhooks strips it before delivery. + */ + _automation_chain?: string[]; } /** * A file attachment associated with an event @@ -401,6 +405,24 @@ declare namespace Components { * true */ automation_trigger?: boolean; + /** + * Whether explicit triggering is restricted to Automation. When true, callers must pass + * `_trigger_source_type: automation` and a stable `_trigger_source`. The trigger uses + * strict entity readiness validation and the durable Automation outbox. + * Requires `automation_trigger: true` and cannot be combined with `entity_operation`. + * + * example: + * true + */ + automation_trigger_only?: boolean; + /** + * Required entity-graph seed node for an Automation-only trigger. When configured, an + * explicit trigger using any other node is rejected before hydration. + * + * example: + * ticket + */ + automation_trigger_seed_node?: string; /** * Org-defined success criteria for this event: the entity attributes that an * organization considers must be captured for an event change request to be @@ -586,6 +608,24 @@ declare namespace Components { * true */ automation_trigger?: boolean; + /** + * Whether explicit triggering is restricted to Automation. When true, callers must pass + * `_trigger_source_type: automation` and a stable `_trigger_source`. The trigger uses + * strict entity readiness validation and the durable Automation outbox. + * Requires `automation_trigger: true` and cannot be combined with `entity_operation`. + * + * example: + * true + */ + automation_trigger_only?: boolean; + /** + * Required entity-graph seed node for an Automation-only trigger. When configured, an + * explicit trigger using any other node is rejected before hydration. + * + * example: + * ticket + */ + automation_trigger_seed_node?: string; /** * Org-defined success criteria for this event: the entity attributes that an * organization considers must be captured for an event change request to be @@ -1169,6 +1209,15 @@ declare namespace Components { } /** * Payload for explicitly triggering an event via API + * example: + * { + * "seed": { + * "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + * "node_id": "ticket" + * }, + * "_trigger_source_type": "automation", + * "_trigger_source": "execution-id/action-id" + * } */ export interface TriggerEventPayload { /** @@ -1214,16 +1263,22 @@ declare namespace Components { * Identifier of the specific trigger source. * Examples: user ID, automation execution ID, activity ID, portal user email * Defaults to the calling user ID if not specified. + * Required for events marked `automation_trigger_only` and must remain stable across + * action retries. * */ _trigger_source?: string; + /** + * Ordered automation flow ids that caused this trigger (at most 100); propagated verbatim onto the published event for automation loop prevention. + */ + _automation_chain?: string[]; } /** * Response from triggering an event */ export interface TriggerEventResponse { /** - * Whether the event was triggered successfully + * Whether the event was published successfully */ success: boolean; /** @@ -1231,7 +1286,7 @@ declare namespace Components { */ event_id: string; /** - * EventBridge event ID from publishing + * EventBridge event ID when synchronous delivery or a completed retry provides it */ event_bridge_event_id?: string; } @@ -1387,6 +1442,24 @@ declare namespace Components { * true */ automation_trigger?: boolean; + /** + * Whether explicit triggering is restricted to Automation. When true, callers must pass + * `_trigger_source_type: automation` and a stable `_trigger_source`. The trigger uses + * strict entity readiness validation and the durable Automation outbox. + * Requires `automation_trigger: true` and cannot be combined with `entity_operation`. + * + * example: + * true + */ + automation_trigger_only?: boolean; + /** + * Required entity-graph seed node for an Automation-only trigger. When configured, an + * explicit trigger using any other node is rejected before hydration. + * + * example: + * ticket + */ + automation_trigger_seed_node?: string; /** * Org-defined success criteria for this event: the entity attributes that an * organization considers must be captured for an event change request to be @@ -1855,7 +1928,19 @@ declare namespace Paths { export interface PathParameters { event_name: Parameters.EventName; } - export type RequestBody = /* Payload for explicitly triggering an event via API */ Components.Schemas.TriggerEventPayload; + export type RequestBody = /** + * Payload for explicitly triggering an event via API + * example: + * { + * "seed": { + * "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + * "node_id": "ticket" + * }, + * "_trigger_source_type": "automation", + * "_trigger_source": "execution-id/action-id" + * } + */ + Components.Schemas.TriggerEventPayload; namespace Responses { export type $200 = /* Response from triggering an event */ Components.Schemas.TriggerEventResponse; export interface $400 { @@ -1864,6 +1949,12 @@ declare namespace Paths { } export interface $404 { } + export interface $409 { + } + export interface $425 { + } + export interface $503 { + } } } } @@ -1986,6 +2077,9 @@ export interface OperationMethods { * - For events without an entity_graph, only fields are needed * - Entity operation context fields (operation, trigger_entity, activity_id, activity_type) * are not included when triggering via API + * - Events marked `automation_trigger_only` require `_trigger_source_type: automation` and a + * stable execution/action ID in `_trigger_source`; retries with that ID return the same + * logical event, including while durable delivery is pending * */ 'triggerEvent'( @@ -2129,6 +2223,9 @@ export interface PathsDictionary { * - For events without an entity_graph, only fields are needed * - Entity operation context fields (operation, trigger_entity, activity_id, activity_type) * are not included when triggering via API + * - Events marked `automation_trigger_only` require `_trigger_source_type: automation` and a + * stable execution/action ID in `_trigger_source`; retries with that ID return the same + * logical event, including while durable delivery is pending * */ 'post'( diff --git a/clients/event-catalog-client/src/openapi.json b/clients/event-catalog-client/src/openapi.json index b466fc374..35466cb34 100644 --- a/clients/event-catalog-client/src/openapi.json +++ b/clients/event-catalog-client/src/openapi.json @@ -437,7 +437,7 @@ "post": { "operationId": "triggerEvent", "summary": "triggerEvent", - "description": "Explicitly trigger an event by providing input field values and an optional entity seed\nfor graph hydration. The event must be enabled for the organization.\n\n- For events with an entity_graph, a seed (entity_id + node_id) is required\n- For events without an entity_graph, only fields are needed\n- Entity operation context fields (operation, trigger_entity, activity_id, activity_type)\n are not included when triggering via API\n", + "description": "Explicitly trigger an event by providing input field values and an optional entity seed\nfor graph hydration. The event must be enabled for the organization.\n\n- For events with an entity_graph, a seed (entity_id + node_id) is required\n- For events without an entity_graph, only fields are needed\n- Entity operation context fields (operation, trigger_entity, activity_id, activity_type)\n are not included when triggering via API\n- Events marked `automation_trigger_only` require `_trigger_source_type: automation` and a\n stable execution/action ID in `_trigger_source`; retries with that ID return the same\n logical event, including while durable delivery is pending\n", "tags": [ "Event Catalog" ], @@ -464,7 +464,7 @@ }, "responses": { "200": { - "description": "Event triggered successfully", + "description": "Event published successfully", "content": { "application/json": { "schema": { @@ -481,6 +481,33 @@ }, "404": { "description": "Event not found" + }, + "409": { + "description": "The Automation trigger source was already used with different trigger input, including a different entity seed" + }, + "425": { + "description": "The hydrated entity graph is not ready yet, another request currently owns the\nidempotency lease, or a durably queued event (including an already-requested redrive)\nis awaiting publication. Automation must retry with the same trigger source and may\nadvance only after a 200 response.\n", + "headers": { + "Retry-After": { + "description": "Seconds before the Automation action should retry", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } + }, + "503": { + "description": "Durable delivery exhausted its bounded automatic redrives. The same logical event is\nretained and no new event is created. An API retry with the same trigger source\nrequests a redrive of the existing Standard workflow from its failed task checkpoint\nand receives this retryable response. Subsequent retries receive 425 while that\nredrive is pending or running.\n", + "headers": { + "Retry-After": { + "description": "Seconds before the Automation action should retry", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } } } } @@ -594,6 +621,19 @@ "default": false, "example": true }, + "automation_trigger_only": { + "type": "boolean", + "readOnly": true, + "description": "Whether explicit triggering is restricted to Automation. When true, callers must pass\n`_trigger_source_type: automation` and a stable `_trigger_source`. The trigger uses\nstrict entity readiness validation and the durable Automation outbox.\nRequires `automation_trigger: true` and cannot be combined with `entity_operation`.\n", + "default": false, + "example": true + }, + "automation_trigger_seed_node": { + "type": "string", + "readOnly": true, + "description": "Required entity-graph seed node for an Automation-only trigger. When configured, an\nexplicit trigger using any other node is rejected before hydration.\n", + "example": "ticket" + }, "success_criteria": { "type": "array", "description": "Org-defined success criteria for this event: the entity attributes that an\norganization considers must be captured for an event change request to be\ntreated as complete (e.g. for telephony / self-service flows).\n\nAdvisory metadata — event-catalog does NOT require an org to define any and\ndoes NOT enforce them when an event is triggered or published. The org may\ndefine none (empty array or omitted). When provided, each entry is validated\nfor well-formedness on write (see SuccessCriterion).\n", @@ -982,6 +1022,13 @@ "items": { "$ref": "#/components/schemas/InlineDowngradeStep" } + }, + "_automation_chain": { + "type": "array", + "description": "Ordered automation flow ids that caused this event (at most 100), propagated verbatim from the trigger input or the originating entity operation. Internal loop-prevention transport for automation-api; svc-webhooks strips it before delivery.", + "items": { + "type": "string" + } } }, "required": [ @@ -1300,6 +1347,14 @@ "TriggerEventPayload": { "type": "object", "description": "Payload for explicitly triggering an event via API", + "example": { + "seed": { + "entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "ticket" + }, + "_trigger_source_type": "automation", + "_trigger_source": "execution-id/action-id" + }, "properties": { "seed": { "type": "object", @@ -1338,7 +1393,14 @@ }, "_trigger_source": { "type": "string", - "description": "Identifier of the specific trigger source.\nExamples: user ID, automation execution ID, activity ID, portal user email\nDefaults to the calling user ID if not specified.\n" + "description": "Identifier of the specific trigger source.\nExamples: user ID, automation execution ID, activity ID, portal user email\nDefaults to the calling user ID if not specified.\nRequired for events marked `automation_trigger_only` and must remain stable across\naction retries.\n" + }, + "_automation_chain": { + "type": "array", + "description": "Ordered automation flow ids that caused this trigger (at most 100); propagated verbatim onto the published event for automation loop prevention.", + "items": { + "type": "string" + } } } }, @@ -1348,7 +1410,7 @@ "properties": { "success": { "type": "boolean", - "description": "Whether the event was triggered successfully" + "description": "Whether the event was published successfully" }, "event_id": { "type": "string", @@ -1356,7 +1418,7 @@ }, "event_bridge_event_id": { "type": "string", - "description": "EventBridge event ID from publishing" + "description": "EventBridge event ID when synchronous delivery or a completed retry provides it" } }, "required": [