@aglyn/plugins-workflows 1.0.0-beta.228 → 1.0.0-beta.229
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -12
- package/src/lib/engine/run-event-actions.js +21 -5
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/webhook-delivery.d.ts +24 -0
- package/src/lib/engine/webhook-delivery.js +24 -0
- package/src/lib/engine/webhook-delivery.js.map +1 -0
- package/src/lib/model/webhooks.d.ts +8 -2
- package/src/lib/model/webhooks.js +7 -1
- package/src/lib/model/webhooks.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-workflows",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.229",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,17 +25,17 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
36
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
37
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.229",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.229",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.229",
|
|
31
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.229",
|
|
32
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.229",
|
|
33
|
+
"@aglyn/shared-util-email": "1.0.0-beta.229",
|
|
34
|
+
"@aglyn/shared-util-http": "1.0.0-beta.229",
|
|
35
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.229",
|
|
36
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.229",
|
|
37
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.229",
|
|
38
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.229",
|
|
39
39
|
"@swc/helpers": "0.5.23"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -34,7 +34,6 @@ import { FieldValue } from "firebase-admin/firestore";
|
|
|
34
34
|
import { runSummaryFields } from "../model/run-history.js";
|
|
35
35
|
import { ACTION_MAX_EVENT_DEPTH, runWorkflow, WORKFLOW_MAX_STEPS } from "../model/workflows.js";
|
|
36
36
|
import { describeStepOutcome } from "../model/step-outcomes.js";
|
|
37
|
-
import { WEBHOOK_URL_PATTERN } from "../model/webhooks.js";
|
|
38
37
|
import { eventRunSuspension } from "./site-suspension.js";
|
|
39
38
|
import { triggeredDocsForEvent } from "./triggered-docs.js";
|
|
40
39
|
import { resolveStepEmailMerge, stepEmailMergeContext } from "./email-merge.js";
|
|
@@ -297,7 +296,7 @@ export function makeWorkflowContextLoader(hostRef) {
|
|
|
297
296
|
// Id-first lookup (AGL-261); the name query is the legacy path.
|
|
298
297
|
const hookDoc = ((_step_webhookId = step.webhookId) == null ? void 0 : _step_webhookId.trim()) ? await hostRef.collection('webhooks').doc(step.webhookId.trim()).get() : (await hostRef.collection('webhooks').where('name', '==', (_ref3 = (_step_webhookName = step.webhookName) == null ? void 0 : _step_webhookName.trim()) != null ? _ref3 : '').limit(1).get()).docs[0];
|
|
299
298
|
const hook = (hookDoc == null ? void 0 : hookDoc.exists) ? hookDoc.data() : undefined;
|
|
300
|
-
if (!hook || hookDoc.get('deletedAt') || hook.enabled === false || hook.direction !== 'outbound' || !hook.url
|
|
299
|
+
if (!hook || hookDoc.get('deletedAt') || hook.enabled === false || hook.direction !== 'outbound' || !hook.url) {
|
|
301
300
|
return failed(`unknown webhook "${step.webhookName || step.webhookId}"`);
|
|
302
301
|
}
|
|
303
302
|
const body = JSON.stringify({
|
|
@@ -308,11 +307,23 @@ export function makeWorkflowContextLoader(hostRef) {
|
|
|
308
307
|
const signature = hook.secret ? createHmac('sha256', hook.secret).update(body).digest('hex') : '';
|
|
309
308
|
// Two quick retries — serverless-friendly; longer retry queues
|
|
310
309
|
// are a follow-up.
|
|
310
|
+
//
|
|
311
|
+
// The hook's URL is tenant-typed and the step runs on any visitor's
|
|
312
|
+
// form submission, so it goes out through the configured-URL fetch
|
|
313
|
+
// (AGL-3363), never a bare `fetch`: https only, the name resolved to
|
|
314
|
+
// PUBLIC addresses with the socket pinned to the one checked, and no
|
|
315
|
+
// redirect followed — a 3xx is a failed delivery. That is what refuses
|
|
316
|
+
// `[::1]`, `[::ffff:127.0.0.1]`, a decimal `2130706433`, a name that
|
|
317
|
+
// resolves inward, and a public host answering `302` to the metadata
|
|
318
|
+
// endpoint, none of which a pattern over the URL text can see.
|
|
319
|
+
//
|
|
320
|
+
// Loaded here, not at the top: see `webhook-delivery.ts`.
|
|
321
|
+
const { describeConfiguredUrlRefusal, fetchConfiguredPublicUrl } = await import("./webhook-delivery.js");
|
|
311
322
|
let delivered = false;
|
|
312
323
|
let lastStatus;
|
|
313
324
|
for(let attempt = 0; attempt < 3 && !delivered; attempt += 1){
|
|
314
325
|
try {
|
|
315
|
-
const
|
|
326
|
+
const result = await fetchConfiguredPublicUrl(hook.url, {
|
|
316
327
|
method: 'POST',
|
|
317
328
|
headers: _extends({
|
|
318
329
|
'Content-Type': 'application/json'
|
|
@@ -322,8 +333,13 @@ export function makeWorkflowContextLoader(hostRef) {
|
|
|
322
333
|
body,
|
|
323
334
|
signal: AbortSignal.timeout(5000)
|
|
324
335
|
});
|
|
325
|
-
|
|
326
|
-
|
|
336
|
+
if ('refusal' in result) {
|
|
337
|
+
// A refusal is the URL's, not the network's: retrying it would
|
|
338
|
+
// only re-resolve the same answer.
|
|
339
|
+
return failed(`webhook "${step.webhookName || step.webhookId}" refused: ${describeConfiguredUrlRefusal(result.refusal)}`);
|
|
340
|
+
}
|
|
341
|
+
lastStatus = result.status;
|
|
342
|
+
delivered = result.status >= 200 && result.status < 300;
|
|
327
343
|
} catch (unused) {
|
|
328
344
|
// Retry below.
|
|
329
345
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/run-event-actions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ACTION_MAX_STEPS,\n ACTIONS_MAX_PER_HOST,\n checkEntitlement,\n consentGroupForHost,\n planLabelGrantingFeature,\n evaluateExpression,\n evaluateStepGuard,\n evaluateTriggerConditions,\n flowSubscriptionTopicId,\n hostPublicOrigin,\n isClientActionStep,\n type HostEventType,\n type HostFunction,\n type HostVariable,\n normalizeTriggerConditions,\n type PluginJobHostGate,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/server'\nimport {\n isDeferrableSendResult,\n isEmailConfigured,\n sendEmail,\n sendFailureReason,\n} from '@aglyn/shared-util-email'\nimport {\n enrollListMember,\n filterSendableForHost,\n firebaseAdmin,\n flowEmailRefusal,\n getOrgForHost,\n hostDisplayName,\n hostSendingIdentity,\n meterHostEmail,\n notifyHostManagers,\n consentGroupForSite,\n resolveOrgIdForHost,\n} from '@aglyn/tenant-data-admin'\nimport { activitySearchTokens } from '@aglyn/aglyn/app-utils/activity-search'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { triggerFilterProblem } from '@aglyn/aglyn/app-utils/site-interactions'\n// The person behind an address, and their filing under a campaign, through\n// the plugin that keeps people (AGL-3080): the engine opens none of its\n// collections.\nimport {\n filePluginPersonUnder,\n findPluginPerson,\n} from '@aglyn/aglyn/plugin-manager/plugin-person-records'\nimport {\n findOrgContainersByName,\n readOrgContainers,\n type OrgContainerRecord,\n} from '@aglyn/tenant-data-admin/server/org-containers'\n// The leaf again, for the phishing screen's hold (AGL-3356): the control\n// that stops a send must be the real one under a spec that mocks the barrel.\nimport { screenOutboundSend } from '@aglyn/tenant-data-admin/server/outbound-send-review'\nimport { renderSiteTextEmail } from '@aglyn/tenant-data-admin/server/host-email-tokens'\nimport { createHmac } from 'crypto'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { runSummaryFields } from '../model/run-history'\nimport {\n ACTION_MAX_EVENT_DEPTH,\n type HostWorkflow,\n type HostWorkflowStep,\n runWorkflow,\n WORKFLOW_MAX_STEPS,\n} from '../model/workflows'\nimport { describeStepOutcome } from '../model/step-outcomes'\nimport { type HostWebhook, WEBHOOK_URL_PATTERN } from '../model/webhooks'\nimport { eventRunSuspension } from './site-suspension'\nimport { triggeredDocsForEvent } from './triggered-docs'\nimport { resolveStepEmailMerge, stepEmailMergeContext } from './email-merge'\nimport {\n recordRuns,\n type RunMeterScope,\n runMonthKey,\n runsUsedThisMonth,\n} from './run-meter'\nimport {\n advanceFlowEnrollment,\n claimFlowEnrollment,\n deferFlowEnrollment,\n endFlowEnrollment,\n enrollInFlow,\n findFlowEnrollmentsAwaiting,\n type FlowEnrollment,\n type FlowSweepCursor,\n type FlowSweepResult,\n sweepDueFlowEnrollments,\n} from './flow-enrollments'\n// The runtime's leaves rather than its barrel: the engine's specs substitute\n// each leaf, and a mock of the barrel would take the rest of it down too.\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\nimport {\n resumeWorkflowEnrollment,\n runEventWorkflows,\n} from './run-event-workflows'\nimport {\n ORG_AUTOMATION_RUN_TARGET,\n type OrgAutomation,\n orgAutomationStepRefusal,\n} from '../model/org-automations'\nimport {\n findOrgAutomationsForEvent,\n resumeOrgAutomationEnrollment,\n} from './run-org-automations'\nimport {\n type AutomationWorkflow,\n isWorkflowActionStep,\n type WorkflowStep,\n workflowActionStepRefusal,\n workflowHasActionSteps,\n workflowStepTypeLabel,\n} from './workflow-steps'\nimport { runTriggeredByFields } from './run-trigger-actor'\n// By path, not the barrel: only a server run asks.\nimport { pluginServerStepExecutor } from '@aglyn/aglyn/plugin-manager/plugin-server-steps'\nimport { preparePluginRecordEmail } from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport {\n FLOW_TIMED_OUT_FIELD,\n type HostAction,\n type HostActionAlert,\n type HostActionStep,\n type HostActionStepType,\n isFlowSuspendingStep,\n sendEmailIsTransactionalReply,\n} from '../model/host-actions'\n\n/**\n * Every live, switched-on action a site holds for an event runs, in\n * document-id order (AGL-3458) — up to the most live actions a site may hold\n * at all, so no matching action is ever left out. See `triggered-docs.ts`.\n */\nconst MAX_TRIGGERED_ACTIONS = ACTIONS_MAX_PER_HOST\n\n/** The live, switched-on actions a site holds for `event`, in document-id order. */\nexport function liveActionsForEvent(\n hostRef: FirebaseFirestore.DocumentReference,\n event: string,\n): Promise<FirebaseFirestore.QueryDocumentSnapshot[]> {\n return triggeredDocsForEvent(hostRef.collection('actions'), event, {\n keep: (doc) => !doc.get('deletedAt') && doc.get('enabled') !== false,\n max: MAX_TRIGGERED_ACTIONS,\n maxReads: MAX_TRIGGERED_ACTIONS * 4,\n })\n}\n\nexport interface ActionRunEnv {\n hostId: string\n hostRef: FirebaseFirestore.DocumentReference\n alerts: HostActionAlert[]\n /**\n * Whether the owning org holds the actions builder (`actions`, Pro and up)\n * — the gate every Actions step takes. An action run is admitted by it\n * before its first step, so it is always true there; a workflow run is not,\n * because its function calls need no such plan, so the workflow executor\n * asks it of each Actions step it reaches.\n */\n actionsAllowed: boolean\n webhooksAllowed: boolean\n depth: number\n /**\n * The owning org's billing doc, already read by the entitlement gate that\n * admitted this run, and the org's id beside it.\n *\n * Carried rather than re-read: both entry points resolve `getOrgForHost`\n * before they build this, so a step that gates on the plan — another\n * plugin's step, handed both — costs no extra org read.\n * Null only when the host has no resolvable org, which the gate treats as\n * the free plan.\n */\n org: unknown\n orgId: string | null\n loadWorkflowContext: () => Promise<WorkflowContext>\n}\n\n/**\n * The site's functions, variables and workflows, double-keyed by document id\n * and by name (AGL-261). A workflow value also carries its document id as\n * `$id`, so a step that names a workflow by its name can still run it as the\n * automation it is.\n */\nexport interface WorkflowContext {\n functions: Record<string, HostFunction>\n variables: Record<string, HostVariable>\n workflows: Record<string, HostWorkflow & { $id?: string }>\n}\n\n/**\n * The automation a run belongs to, as the step executors need it: which kind,\n * its document id, and its name for the history and the enrollment.\n */\nexport interface AutomationRun {\n /**\n * `orgAutomation` for an org automation (AGL-3302) — a run on the event's\n * site of an automation the organization placed there.\n */\n kind: 'action' | 'workflow' | 'orgAutomation'\n id: string\n name: string\n /** The owning organization, on an `orgAutomation` run: a wait enrolls with it. */\n orgId?: string\n}\n\n/**\n * The run environment for one automation run: the org's plan gates resolved\n * once, from the org document the caller already read.\n */\nexport function automationRunEnv(input: {\n hostId: string\n hostRef: FirebaseFirestore.DocumentReference\n owner: { org?: unknown; orgId?: string | null } | null | undefined\n depth: number\n alerts?: HostActionAlert[]\n loadWorkflowContext?: ActionRunEnv['loadWorkflowContext']\n}): ActionRunEnv {\n const org = input.owner?.org ?? null\n return {\n hostId: input.hostId,\n hostRef: input.hostRef,\n alerts: input.alerts ?? [],\n actionsAllowed: checkEntitlement(org as any, 'actions'),\n webhooksAllowed: checkEntitlement(org as any, 'webhooks'),\n depth: input.depth,\n org,\n orgId: input.owner?.orgId ?? null,\n loadWorkflowContext:\n input.loadWorkflowContext ?? makeWorkflowContextLoader(input.hostRef),\n }\n}\n\nexport function makeWorkflowContextLoader(\n hostRef: FirebaseFirestore.DocumentReference,\n) {\n let workflowContext: WorkflowContext | null = null\n return async (): Promise<WorkflowContext> => {\n if (workflowContext) return workflowContext\n const [functionDocs, variableDocs, workflowDocs] = await Promise.all([\n hostRef.collection('functions').limit(100).get(),\n hostRef.collection('variables').limit(100).get(),\n hostRef.collection('workflows').limit(100).get(),\n ])\n workflowContext = workflowContextFromDocs(\n functionDocs.docs,\n variableDocs.docs,\n workflowDocs.docs,\n )\n return workflowContext\n }\n}\n\n/**\n * The working set a run reads, from documents already in hand — for a door\n * that read them itself and must not pay for them twice.\n */\nexport function workflowContextFromDocs(\n functionDocs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n variableDocs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n workflowDocs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n): WorkflowContext {\n // Double-keyed by doc id AND name (AGL-261): id references are\n // rename-safe; legacy name references keep resolving.\n const byName = <T extends { name?: string; deletedAt?: unknown }>(\n docs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n ) => {\n const map: Record<string, T> = {}\n for (const doc of docs) {\n const data = doc.data() as T\n if (data.deletedAt) continue\n map[doc.id] = data\n if (data?.name) map[data.name] = data\n }\n return map\n }\n // A workflow is carried with its id so a step naming it by its legacy\n // name still runs it as the automation it is (see `WorkflowContext`).\n const workflows: WorkflowContext['workflows'] = {}\n for (const doc of workflowDocs) {\n const data = doc.data() as HostWorkflow\n if ((data as { deletedAt?: unknown }).deletedAt) continue\n const withId = { ...data, $id: doc.id }\n workflows[doc.id] = withId\n if (data?.name) workflows[data.name] = withId\n }\n return {\n functions: byName<HostFunction>(functionDocs),\n variables: byName<HostVariable>(variableDocs),\n workflows,\n }\n}\n\n/** How a run of an action's step list ended. */\ntype ActionRunEnding = 'ran' | 'waiting' | 'exited' | 'deferred'\n\ninterface ExecuteActionOptions {\n /**\n * The step to start at. Non-zero only on a resume, where it is the\n * enrollment's `nextStepIndex`.\n */\n startIndex?: number\n /**\n * The step list to run.\n *\n * A resume passes the enrollment's SNAPSHOT rather than the action's\n * current steps — see `FlowEnrollment.steps` for why a position in a list\n * is meaningless against a list that has since been edited.\n */\n steps?: readonly HostActionStep[]\n /**\n * The enrollment this run belongs to. Present only on a resume; a first run\n * mints one if it reaches a wait.\n */\n enrollmentRef?: FirebaseFirestore.DocumentReference | null\n /**\n * Present for a run of an ORG automation (AGL-3302), naming the\n * organization that owns it. The steps run on this site exactly as a site\n * action's do; what changes is that each is held to the org vocabulary as\n * it runs, a wait enrolls under the org automation's own id, and the\n * history row is filed under it.\n */\n orgAutomation?: { orgId: string }\n}\n\n/** What one step is run against: its place, the list it is in, the event. */\ninterface ServerStepContext {\n /** The step's index in {@link steps}. */\n index: number\n /** The list being run — the snapshot a wait enrolls with. */\n steps: readonly WorkflowStep[]\n event: string\n /** The payload the step reads its values from. */\n payload: HostEventPayload\n /** The scope a step's `when` is evaluated against. */\n scope: Record<string, unknown>\n enrollmentRef: FirebaseFirestore.DocumentReference | null\n /**\n * Set when this run is itself a step of another automation — a workflow a\n * `runWorkflow` step started. Such a run finishes inside its caller's run,\n * so it has no enrollment of its own to wait in.\n */\n nested?: boolean\n}\n\n/**\n * What one Actions step did, for the run that holds it.\n *\n * - `skipped` — its `when` was not met, or it is a step the visitor's page\n * runs; nothing is recorded.\n * - `done` — it did its work; `detail` is the fact the history line carries.\n * - `failed` — it did not; the run records the error and continues.\n * - `exited` — an `exitFlow`: the run ends here.\n * - `waiting` — a wait enrolled the person; the rest runs from the beat.\n * - `halted` — a wait that could not enroll; the run ends with the error.\n * - `deferred` — a resumed send was refused for now; the enrollment retries.\n */\nexport type ServerStepVerdict =\n | { kind: 'skipped' }\n | { kind: 'done'; detail?: string }\n | { kind: 'failed'; error: string }\n | { kind: 'exited' }\n | { kind: 'waiting'; detail?: string }\n | { kind: 'halted'; error: string }\n | { kind: 'deferred' }\n\n/** A run's tally so far: how it is ending, its errors, what its steps did. */\ninterface RunTally {\n ending: ActionRunEnding\n errors: string[]\n outcomes: string[]\n}\n\n/**\n * Folds one step's verdict into its run's tally, in the run-history phrasing\n * every automation shares; true when the run stops at this step.\n */\nfunction tallyStep(\n tally: RunTally,\n type: HostActionStepType,\n verdict: ServerStepVerdict,\n): boolean {\n switch (verdict.kind) {\n case 'skipped':\n return false\n case 'done':\n tally.outcomes.push(describeStepOutcome(type, verdict.detail))\n return false\n case 'failed':\n tally.errors.push(verdict.error)\n return false\n case 'exited':\n tally.ending = 'exited'\n tally.outcomes.push(describeStepOutcome(type))\n return true\n case 'waiting':\n tally.ending = 'waiting'\n tally.outcomes.push(describeStepOutcome(type, verdict.detail))\n return true\n case 'halted':\n tally.errors.push(verdict.error)\n return true\n case 'deferred':\n tally.ending = 'deferred'\n return true\n }\n}\n\n/**\n * Raises an event a step's write earned — a stage an automation set IS a\n * stage change, and whatever listens for one must hear it.\n *\n * Fanned out here, one level deeper under the same depth guard a\n * `customEvent` chain runs under, rather than through `emitHostEvent`, which\n * starts every chain at depth zero and would let an automation that causes\n * the event it listens for run forever. Workflows take the guard too.\n */\nasync function raiseEarnedEvent(\n env: ActionRunEnv,\n emit: { event: string; payload: Record<string, unknown> },\n): Promise<void> {\n const payload = emit.payload as HostEventPayload\n const [fromWorkflows, fromActions] = await Promise.all([\n // A step names any event a listener may hear: a declared one or a custom one.\n runEventWorkflows(env.hostId, emit.event as HostEventType, payload, env.depth + 1),\n runEventActions(env.hostId, emit.event, payload, env.depth + 1),\n ])\n env.alerts.push(...fromActions, ...fromWorkflows)\n}\n\n/**\n * Runs ONE Actions step on the server: the executor every automation shares.\n *\n * An action's step list runs through it one step at a time, and so does\n * every Actions step inside a workflow — so the two engines cannot disagree\n * about what `sendEmail` or `webhookPost` does, what a step is gated on, or\n * how its outcome reads in the run history. There is no second copy of any\n * branch below.\n *\n * Client-side steps (AGL-257) are skipped — the tenant page runtime runs\n * those in the visitor's browser.\n *\n * Never throws: a step that throws is a failed step, and the run continues.\n */\nasync function runServerStep(\n env: ActionRunEnv,\n run: AutomationRun,\n step: HostActionStep,\n context: ServerStepContext,\n): Promise<ServerStepVerdict> {\n const { hostId, hostRef, alerts, depth } = env\n const { event, payload, enrollmentRef } = context\n /**\n * The one fact worth carrying into the summary — the webhook's status, or\n * whatever a plugin's step answers. Set by the branch that knows it.\n */\n let detail: string | undefined\n const failed = (error: string): ServerStepVerdict => ({ kind: 'failed', error })\n try {\n /*\n * BRANCHING INSIDE A FLOW: the step's own condition, evaluated against\n * the same scope the trigger's is. An unmet guard skips this step and\n * only this step — the run continues, which is what makes \"wait three\n * days, then, only if they have not ordered, send the reminder\" a thing\n * an author can write without a second action.\n */\n if (!evaluateStepGuard(step.when, context.scope)) return { kind: 'skipped' }\n if (isClientActionStep(step) && step.type !== 'siteAlert') {\n return { kind: 'skipped' } // Runs in the visitor's page (AGL-257).\n }\n if (step.type === 'exitFlow') return { kind: 'exited' }\n if (isFlowSuspendingStep(step)) {\n // A run inside another automation's run finishes inside it: there is\n // no enrollment of its own for the person to wait in.\n if (context.nested) {\n return {\n kind: 'halted',\n error:\n 'a workflow run as a step of another automation cannot wait — ' +\n 'give it its own trigger instead',\n }\n }\n const suspended = await suspendFlow(env, run, {\n step,\n steps: context.steps,\n nextStepIndex: context.index + 1,\n event,\n payload,\n enrollmentRef,\n })\n if (suspended.error) return { kind: 'halted', error: suspended.error }\n return { kind: 'waiting', detail: suspended.detail }\n }\n if (step.type === 'siteAlert') {\n alerts.push({\n message: String(step.message ?? '').slice(0, 300),\n severity: step.severity ?? 'info',\n })\n } else if (step.type === 'runWorkflow') {\n const workflowContext = await env.loadWorkflowContext()\n const workflow =\n workflowContext.workflows[step.workflowId?.trim() ?? ''] ??\n workflowContext.workflows[step.workflowName?.trim() ?? '']\n if (!workflow) {\n return failed(`unknown workflow \"${step.workflowName || step.workflowId}\"`)\n }\n if (workflowHasActionSteps(workflow)) {\n /*\n * A workflow with Actions steps is PERFORMED, not evaluated: its\n * steps run here, inside this run, one level deeper under the same\n * guard a custom-event chain runs under. It is part of this run, so\n * it is not metered or recorded as a run of its own.\n */\n if (depth + 1 > ACTION_MAX_EVENT_DEPTH) {\n return failed(`workflow \"${workflow.name}\" is nested too deeply`)\n }\n const nested = await executeWorkflow(\n { ...env, depth: depth + 1 },\n {\n kind: 'workflow',\n id: workflow.$id ?? step.workflowId?.trim() ?? '',\n name: workflow.name ?? '',\n },\n workflow as AutomationWorkflow,\n event,\n payload,\n { nested: true },\n )\n if (nested.errors.length) {\n return failed(\n `workflow \"${workflow.name}\": ${nested.errors.join('; ')}`.slice(0, 300),\n )\n }\n } else {\n const evaluated = runWorkflow(\n workflow,\n workflowContext.functions,\n workflowContext.variables,\n { event, ...payload },\n { workflows: workflowContext.workflows },\n )\n if (evaluated.ok === false) return failed(evaluated.error)\n }\n } else if (step.type === 'customEvent') {\n const nested = await runEventActions(\n hostId,\n step.eventName.trim(),\n payload,\n depth + 1,\n )\n alerts.push(...nested)\n } else if (step.type === 'webhookPost') {\n if (!env.webhooksAllowed) return failed('webhooks require a Business plan')\n // Id-first lookup (AGL-261); the name query is the legacy path.\n const hookDoc = step.webhookId?.trim()\n ? await hostRef\n .collection('webhooks')\n .doc(step.webhookId.trim())\n .get()\n : (\n await hostRef\n .collection('webhooks')\n .where('name', '==', step.webhookName?.trim() ?? '')\n .limit(1)\n .get()\n ).docs[0]\n const hook = hookDoc?.exists\n ? (hookDoc.data() as HostWebhook)\n : undefined\n if (\n !hook ||\n hookDoc.get('deletedAt') ||\n hook.enabled === false ||\n hook.direction !== 'outbound' ||\n !hook.url ||\n !WEBHOOK_URL_PATTERN.test(hook.url)\n ) {\n return failed(`unknown webhook \"${step.webhookName || step.webhookId}\"`)\n }\n const body = JSON.stringify({\n event,\n payload,\n sentAt: new Date().toISOString(),\n })\n const signature = hook.secret\n ? createHmac('sha256', hook.secret).update(body).digest('hex')\n : ''\n // Two quick retries — serverless-friendly; longer retry queues\n // are a follow-up.\n let delivered = false\n let lastStatus: number | undefined\n for (let attempt = 0; attempt < 3 && !delivered; attempt += 1) {\n try {\n const response = await fetch(hook.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(signature && { 'X-Aglyn-Signature': signature }),\n },\n body,\n signal: AbortSignal.timeout(5000),\n })\n lastStatus = response.status\n delivered = response.ok\n } catch {\n // Retry below.\n }\n if (!delivered && attempt < 2) {\n await new Promise((resolve) =>\n setTimeout(resolve, 500 * (attempt + 1)),\n )\n }\n }\n if (!delivered) {\n return failed(\n `webhook \"${step.webhookName || step.webhookId}\" delivery failed`,\n )\n }\n // The status the mockup prints — discarded on the line it arrived\n // until AGL-2171. A 200 and a 204 are both `ok`, and knowing which is\n // the whole reason anyone opens a run history after a webhook.\n detail = String(lastStatus ?? '')\n } else if (step.type === 'notifyAdmins') {\n await notifyHostManagers(hostId, {\n type: 'system.announcement',\n title: String(step.title ?? '').slice(0, 200),\n ...(step.body ? { body: String(step.body).slice(0, 500) } : {}),\n link: `/${hostId}`,\n })\n } else if (step.type === 'sendEmail') {\n const to = String(\n (payload as any)[step.toField?.trim() || 'email'] ?? '',\n ).trim()\n if (!isEmailConfigured()) return failed('email is not configured')\n if (!to || !to.includes('@')) {\n return failed('no recipient email in the event payload')\n }\n // The site's own origin, for the unsubscribe link. Read here rather\n // than carried on the run env because most action runs send no email\n // at all, and a document read every workflow pays for is a read on\n // the hot path for a link nine runs in ten never need.\n const hostData =\n ((await hostRef.get().catch(() => null))?.data() as\n | Record<string, unknown>\n | undefined) ?? null\n const siteBase = hostPublicOrigin(hostData as never) ?? ''\n /*\n * MARKETING, unless it is a transactional reply (below). The subject\n * and body are merchant-authored and the recipient comes out of the\n * event payload — which, for the collect route, is a write triggered\n * by an anonymous visitor. So a step that is not a reply is a site\n * mailing an address on the merchant's say-so, and it owes what every\n * other such message owes: the unsubscribe header pair and a visible\n * link, both suppression lists, and a share of the ceiling on how much\n * one person receives from this site.\n *\n * Priority stays transactional. An action run is not resumable — the\n * event has already happened and there is no beat that comes back for\n * it — and the rule on `'bulk'` is that only a resumable sweep may\n * refuse in a way the recipient survives.\n *\n * A merchant who wants an internal alert that no suppression can stop\n * uses the `notifyAdmins` step beside this one: it reaches managers\n * in the console rather than the shared sending domain, which is the\n * right instrument for a notification nobody consented to receive.\n */\n /*\n * A STEP THAT RUNS AFTER A WAIT IS A CAMPAIGN, not a reply.\n *\n * The paragraph above is exactly right about an IMMEDIATE step: the\n * event has already happened, the recipient just did something, and\n * the message is the response to it. None of that survives a three-day\n * delay. Everything after a wait goes out on the merchant's schedule,\n * to somebody who did one thing once — which is `marketing-send.ts`'s\n * own definition of marketing mail, and it earns the full consent\n * split and the default stream, exactly as a campaign does.\n *\n * An immediate step is not ungated, which it used to be. `to` is read\n * out of the event payload — an anonymous visitor's write on the\n * collect route — so nothing here establishes that whoever typed the\n * address is whoever receives the mail, and a person with a RECORDED\n * REFUSAL on this site was mailed merchant-authored content because a\n * third party entered their address in a form. `'immediate'` asks the\n * narrower question that catches exactly that and cannot refuse a\n * new visitor their auto-response: see `FlowEmailScope`.\n *\n * Refused BEFORE `sendEmail` rather than inside it, because these two\n * are the merchant's own policy over their own audience, where the\n * ones the seam asks are platform controls over the shared sending\n * domain. Both refusals are permanent for this message, so the\n * enrollment moves on rather than retrying.\n */\n const scope = enrollmentRef ? 'scheduled' : 'immediate'\n /*\n * A TRANSACTIONAL REPLY (AGL-3458) — the answer to what the recipient\n * just did: the run started on their own form submission, booking or\n * sign-up, this step runs at once, names no topic, and goes to the\n * address the event carries. Such a message is not a mailing, so it\n * leaves with no unsubscribe header and no unsubscribe link, and asks\n * no marketing-consent question — the reply the Inbox sends to a\n * submission is the same kind of message, and CAN-SPAM exempts both.\n * What it keeps is the suppression check below: a hard bounce, a spam\n * complaint or a person who asked this site to stop is not mailed by\n * anything. The author can switch a qualifying step back to a mailing\n * (`transactional: false`); a step that does not qualify is a mailing\n * whatever it says.\n */\n const transactional = sendEmailIsTransactionalReply(step, {\n event,\n afterWait: Boolean(enrollmentRef),\n })\n /*\n * The stream this message belongs to, resolved ONCE and read twice:\n * the gate below filters on it, and it rides the `marketing` context\n * so the opt-out link the seam mints names it. Without that the\n * preference page opens on a list of every stream the site has, and\n * the recipient has to find the one they were trying to leave.\n */\n const topicId = flowSubscriptionTopicId(step.topicId, scope)\n const consentGroup = consentGroupForHost(\n (env.org as Record<string, unknown> | null) ?? null,\n hostId,\n )\n if (transactional) {\n // Both suppression lists — the platform's bounces and complaints, and\n // this site's (and its consent group's) opt-outs — fail CLOSED.\n const sendable = await filterSendableForHost(hostId, [to], undefined, consentGroup)\n if (!sendable.length) return failed('the recipient is unsubscribed or suppressed')\n } else {\n const gate = await flowEmailRefusal({\n hostId,\n email: to,\n topicId: step.topicId ?? null,\n org: env.org,\n scope,\n })\n if (gate) {\n return failed(\n gate !== 'consent-withheld'\n ? 'the recipient has left this email topic'\n : // Named by what was actually asked. An immediate step refuses\n // only on a stated refusal, so reporting it as a missing\n // record would send a merchant looking for a consent field to\n // fill in that would change nothing.\n enrollmentRef\n ? 'the recipient has no marketing consent record on this site'\n : 'the recipient has declined marketing from this site',\n )\n }\n }\n /*\n * THE WORDS AS AUTHORED, and as sent (AGL-3458). The merge tags — the\n * campaign's `{{firstName|there}}` and the CRM's `{{contact.firstName}}`\n * — are filled from the contact or the lead the run is about, else from\n * the name and address the event carried. The phishing screen below\n * reads the AUTHORED words: it judges what the business wrote, once per\n * automation, not one copy per recipient.\n */\n const authoredSubject = String(step.subject ?? '').slice(0, 200)\n const authoredText = String(step.body ?? '').slice(0, 5000)\n const mergeContext = await stepEmailMergeContext({\n hostId,\n email: String(payload['email'] ?? '').trim() || to,\n contactGroupId: consentGroup.groupId,\n siteName: hostDisplayName(hostData ?? undefined, hostId),\n })\n const mergePerson = {\n email: to,\n name: String(payload['name'] ?? payload['fullName'] ?? '').trim(),\n }\n const emailSubject = resolveStepEmailMerge(authoredSubject, mergeContext, mergePerson)\n .text.replace(/[\\r\\n]+/g, ' ')\n .slice(0, 200)\n const emailText = resolveStepEmailMerge(authoredText, mergeContext, mergePerson).text.slice(\n 0,\n 5000,\n )\n /*\n * THE PHISHING SCREEN, for a workspace in its first fortnight\n * (AGL-3356). The incident's second message was exactly this step: a\n * `contactCreated` workflow mailing \"Poshmark Order … has finally\n * sold\" with a link to a Poshmark lookalike, once per imported contact.\n *\n * A hold files one review row per (automation, content) in the abuse\n * queue and stops the message. After a wait the step DEFERS, so the\n * enrollment sends the same step once staff release it; an immediate\n * step has no later beat to come back on, so this run records the hold\n * as its failure and the automation sends again from its next event\n * once released. See `outbound-send-review.ts`.\n */\n const screened = await screenOutboundSend({\n kind: run.kind,\n path:\n run.kind === 'orgAutomation'\n ? `orgs/${run.orgId ?? env.orgId ?? ''}/automations/${run.id}`\n : `hosts/${hostId}/${run.kind === 'workflow' ? 'workflows' : 'actions'}/${run.id}`,\n hostId,\n orgId: env.orgId,\n org: (env.org as Record<string, unknown> | null) ?? null,\n host: hostData,\n subject: authoredSubject,\n bodies: [authoredText],\n })\n if (screened.outcome === 'rejected') {\n return failed(\n `this email was rejected by staff review (${screened.reference})`,\n )\n }\n if (screened.outcome === 'held') {\n if (enrollmentRef) return { kind: 'deferred' }\n return failed(\n 'this email is held for staff review because it may impersonate ' +\n `another business (${screened.reference})`,\n )\n }\n /*\n * THE TIMELINE ENTRY (AGL-2615). A message addressed to the person the\n * event is about is filed on that person's timeline — the same entry a\n * member's own send files — so an automated welcome shows beside the\n * calls a rep made, with its delivery state. Whether the message earns\n * an entry, and where, is the record system's to say: it is offered\n * the message first, because the entry's tags have to ride the message\n * for the delivery webhook to find it, and files it only after the\n * provider accepted. No record system, or a message that earns no\n * entry, sends untagged.\n */\n const recordEmail = env.orgId\n ? await preparePluginRecordEmail({\n orgId: env.orgId,\n hostId,\n to,\n link: {\n ...(String(payload['contactId'] ?? '').trim()\n ? { contactId: String(payload['contactId']).trim() }\n : {}),\n ...(String(payload['email'] ?? '').trim()\n ? { email: String(payload['email']).trim() }\n : {}),\n },\n org: env.org,\n })\n : null\n // In the site's header and footer (AGL-3370): a workflow's email is the\n // site writing to its contact, in words the site owner typed.\n const framed = await renderSiteTextEmail(\n firebaseAdmin.app().firestore(),\n hostId,\n 'workflow-email',\n { subject: emailSubject, text: emailText },\n ).catch(() => null)\n const result = await sendEmail({\n to,\n subject: emailSubject,\n text: framed?.text || emailText,\n ...(framed?.html ? { html: framed.html } : {}),\n sendingIdentity: await hostSendingIdentity(hostId),\n ...(recordEmail ? { tags: [...recordEmail.tags] } : {}),\n audience: 'tenant',\n context: enrollmentRef ? 'flow step' : 'event action',\n // A step staff released above carries its release to the send\n // seam's screen, which would otherwise hold the same signals again.\n releasedReviewId:\n screened.outcome === 'send' ? (screened.releasedReviewId ?? null) : null,\n /*\n * A resumed step may take `'bulk'` where an immediate one may not,\n * and the reason is the same one the abandoned-checkout sweep gives:\n * only a RESUMABLE sender may be refused in a way the recipient\n * survives. The enrollment is the thing that makes it resumable —\n * a deferral below leaves the row waiting and the next beat sends\n * the same step to the same person.\n */\n ...(enrollmentRef ? { priority: 'bulk' as const } : {}),\n // `topicId` is `''` for a step that belongs to no stream, which\n // every reader of it treats as absent — see `flowSubscriptionTopicId`.\n // The consent group comes off the org the run already holds, so the\n // gate honors an unsubscribe from any site of a declared group —\n // the sender this site mails as — and the org's confirmation switch,\n // at no extra read. A transactional reply is not marketing and\n // carries none of it: no `List-Unsubscribe`, no opt-out line.\n ...(transactional\n ? {}\n : {\n marketing: {\n hostId,\n siteBase,\n topicId,\n consentHostIds: consentGroup.hostIds,\n consentAwaitsConfirmation: consentGroup.awaitsConfirmation,\n },\n }),\n })\n /*\n * DEFERRED IS NOT FAILED, and it is not SENT either.\n *\n * The platform's hourly ceiling and this person's own frequency window\n * are both refusals a later beat can pass. Advancing past this step\n * would turn \"not this hour\" into an email nobody ever receives, which\n * is the defect the campaign processor and the cart sweep each name.\n * So the enrollment is put back with the SAME `nextStepIndex` and the\n * run ends here.\n */\n if (enrollmentRef && isDeferrableSendResult(result)) {\n return { kind: 'deferred' }\n }\n // Named rather than lumped into \"delivery failed\": a suppression and\n // a frequency ceiling are the controls working, and a merchant\n // reading the run's alerts has a different thing to do about each.\n const refusal = sendFailureReason(result)\n const sendError = refusal\n ? refusal === 'suppressed'\n ? 'the recipient is unsubscribed or suppressed'\n : refusal === 'frequency-capped'\n ? 'the recipient has already had today’s limit of email ' +\n 'from this site'\n : refusal === 'held-for-review'\n ? 'this email is held for staff review because it may ' +\n 'impersonate another business'\n : 'email delivery failed'\n : null\n // Cost meter (AGL-1438). A workflow notification is transactional:\n // counted, never capped. `sent` is false when Resend refused or the\n // environment is unconfigured, and an email that never left is not a\n // cost.\n if (result.sent) {\n await meterHostEmail(hostId)\n await recordEmail?.file({\n subject: emailSubject,\n body: emailText,\n to,\n sourceRef: run.id,\n })\n }\n if (sendError) return failed(sendError)\n } else if (step.type === 'enrollList') {\n const orgId = await resolveOrgIdForHost(hostId)\n const email = String((payload as any).email ?? '')\n .trim()\n .toLowerCase()\n if (!orgId || !email || !email.includes('@')) {\n return failed('no email to enroll')\n }\n const listsRef = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection('lists')\n const listDoc = step.listId?.trim()\n ? await listsRef.doc(step.listId.trim()).get()\n : (\n await listsRef\n .where('name', '==', step.listName?.trim() ?? '')\n .limit(1)\n .get()\n ).docs[0]\n if (!listDoc?.exists) {\n return failed(`unknown list \"${step.listName || step.listId}\"`)\n }\n // `enrollListMember` owns the document id: the commerce newsletter\n // handler enrolls into the same collection, and an id derived here\n // would be a second answer to which document describes which person.\n await enrollListMember({\n listRef: listDoc.ref,\n group: await consentGroupForSite(hostId),\n email,\n // Which automation enrolled them: `action:<id>` or `workflow:<id>`.\n source: `${run.kind}:${run.id}`,\n })\n } else if (step.type === 'assignCampaign') {\n const email = String((payload as any).email ?? '')\n .trim()\n .toLowerCase()\n if (!email || !email.includes('@')) {\n return failed('no contact email to assign')\n }\n // Scoped to this host (AGL-1039): a site must not reach a contact\n // it cannot see, even to tag it onto a campaign. Asked of the plugin\n // that keeps people (AGL-3080), whose lookup answers an address a\n // merge folded into another record (AGL-2633) with the person who now\n // holds it.\n const contact = await findPluginPerson({ hostId, email, onlyVisibleToSite: true })\n if (!contact) return failed(`no contact for ${email}`)\n /*\n * THE CAMPAIGN IS RESOLVED TO A DOCUMENT, exactly as `enrollList`\n * resolves a list one branch above.\n *\n * A step may name a campaign by id or by name — the picker writes the\n * id, an imported automation may carry only the name — and what gets\n * stored is the id either way. Storing whichever of the two the step\n * happened to hold would put names and ids in one array, and every\n * reader of that array resolves ids: a name in it renders as a chip\n * nobody can click and matches no campaign the console can find.\n *\n * An unknown campaign is an ERROR rather than a stored string. The\n * reference audit already reports a step pointing at a campaign that\n * does not exist; a run that wrote the dangling name anyway would\n * make the audit's finding untrue the moment it fired.\n *\n * The containers are the ORGANIZATION's, and this run is one site's,\n * so a campaign counts only while it is live and placed on this site\n * (`visibleTo` holds `org` or `host:{hostId}`) — the same set the\n * site's picker offers. One the console deleted, or one placed only\n * on a sibling site, is refused like a missing one: filing a person\n * under it would put them in a campaign nobody at this site can open.\n * A name lookup reads a few matches rather than one, so a deleted or\n * sibling campaign sharing the name cannot shadow the live one.\n */\n const campaignLabel = step.campaignName || step.campaignId\n const campaignOrgId = env.orgId ?? (await resolveOrgIdForHost(hostId))\n if (!campaignOrgId) return failed(`unknown campaign \"${campaignLabel}\"`)\n // The org's containers of the `campaign` kind, read where the kind's\n // declaration says they are stored.\n const containerStore = firebaseAdmin.app().firestore()\n const usable = (container: OrgContainerRecord | undefined) =>\n Boolean(container?.live && visibleToHost(container.visibleTo, hostId))\n const namedId = step.campaignId?.trim() ?? ''\n const campaign = namedId\n ? (await readOrgContainers(containerStore, 'campaign', campaignOrgId, [namedId]))[0]\n : (\n await findOrgContainersByName(\n containerStore,\n 'campaign',\n campaignOrgId,\n step.campaignName?.trim() ?? '',\n 10,\n )\n ).find(usable)\n if (!campaign?.exists) {\n return failed(`unknown campaign \"${campaignLabel}\"`)\n }\n if (!campaign.live) {\n return failed(`campaign \"${campaignLabel}\" was deleted`)\n }\n if (!usable(campaign)) {\n return failed(`campaign \"${campaignLabel}\" is not placed on this site`)\n }\n /*\n * Filed by the plugin that keeps people, as THIS SITE holds them\n * (AGL-3080): which campaigns a merchant has filed somebody under is\n * that merchant's business record, on the same footing as their notes\n * and their tags, and where it lives on the person is the owner's to\n * know. A person the owner no longer finds for the site is not filed.\n */\n const filed = await filePluginPersonUnder({\n hostId,\n orgId: campaignOrgId,\n record: { kind: contact.kind, id: contact.id },\n containerKind: 'campaign',\n ids: [campaign.id],\n })\n if (!filed?.filed) return failed(`no contact for ${email}`)\n } else {\n /*\n * A STEP ANOTHER PLUGIN RUNS: one that writes that plugin's records,\n * handed to the executor it registered (`plugin-server-steps`). The\n * guard, the order, the history and the nesting cap stay here; the\n * write is the owner's. A declared step whose executor is missing\n * throws, and is this step's failure. A type nobody runs answers\n * `null` and reads as done, as an unknown type always has.\n */\n const executor = await pluginServerStepExecutor(step.type)\n if (executor) {\n const answer = await executor.run({\n hostId,\n org: env.org,\n orgId: env.orgId,\n run: { kind: run.kind, id: run.id, name: run.name },\n event,\n payload: payload as Record<string, unknown>,\n step: step as { type: string; [field: string]: unknown },\n })\n if (answer.error) return failed(answer.error)\n detail = answer.detail\n if (answer.emit) await raiseEarnedEvent(env, answer.emit)\n }\n }\n } catch (error) {\n return failed((error as Error).message)\n }\n return { kind: 'done', detail }\n}\n\n/**\n * Executes one action's SERVER steps in order, collecting per-step errors\n * into the activity summary. Each step runs through {@link runServerStep}.\n *\n * An org automation's run comes through here too (AGL-3302): on the site the\n * event happened on, against that site's run environment, so its email goes\n * from that site and its CRM writes land in that site's facet.\n *\n * A `wait` step ENDS this call and hands the rest of the list to the job\n * beat: everything after the wait belongs to a run that has not happened yet,\n * so nothing below the wait may execute in this request.\n */\nexport async function executeAction(\n env: ActionRunEnv,\n actionId: string,\n action: Pick<HostAction, 'name' | 'steps'>,\n event: string,\n payload: HostEventPayload,\n options: ExecuteActionOptions = {},\n): Promise<ActionRunEnding> {\n const { hostRef } = env\n const orgRun = options.orgAutomation ?? null\n const run: AutomationRun = orgRun\n ? {\n kind: 'orgAutomation',\n id: actionId,\n name: action.name ?? '',\n orgId: orgRun.orgId,\n }\n : {\n kind: 'action',\n id: actionId,\n name: action.name ?? '',\n }\n const enrollmentRef = options.enrollmentRef ?? null\n const steps = (options.steps ?? action.steps ?? []).slice(0, ACTION_MAX_STEPS)\n const startIndex = Math.max(0, options.startIndex ?? 0)\n /**\n * What each step actually DID (AGL-2171). Only failures were recorded,\n * so a run that sent an email, wrote a row and posted a webhook logged\n * the same eight words as a run that did nothing — and\n * `/product/workflows` advertises a `What happened` column reading\n * `Sent email · saved to Leads · webhook 200`.\n */\n const tally: RunTally = { ending: 'ran', errors: [], outcomes: [] }\n const scope = { event, ...payload }\n for (let index = startIndex; index < steps.length; index += 1) {\n const step = steps[index] as HostActionStep\n /*\n * An org automation runs its own vocabulary and nothing else. The save\n * route refuses the rest, so this answers only a document written around\n * it — and answers it before the step can reach this site's workflows or\n * webhooks, which are not the organization's to call.\n */\n const refusal = orgRun ? orgAutomationStepRefusal(step) : null\n if (refusal) {\n tally.errors.push(refusal)\n continue\n }\n const verdict = await runServerStep(env, run, step, {\n index,\n steps,\n event,\n payload,\n scope,\n enrollmentRef,\n })\n if (tallyStep(tally, step.type, verdict)) break\n }\n const { ending, errors: stepErrors, outcomes } = tally\n /*\n * A DEFERRED run writes no history line and leaves the enrollment where it\n * was. Nothing happened that a merchant should read as a run: the step is\n * still ahead of this person, and a row per refused beat would bury the\n * runs that did something under a log of the ceiling working.\n */\n if (ending === 'deferred') return ending\n const noun = orgRun ? 'Org automation' : 'Action'\n const summary = stepErrors.length\n ? `${noun} ran on ${event} with errors: ${stepErrors.join('; ')}`.slice(\n 0,\n 300,\n )\n : ending === 'waiting'\n ? `${noun} is waiting, on ${event}`\n : `${noun} ran on ${event}`\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n // The prose line stays exactly as it was: `activityPrimaryText` and\n // three other renderers read it, and the run table is not the only\n // thing this collection feeds.\n action: summary,\n // The structured half (AGL-2171) — the two columns the advertised\n // run-history table could not otherwise fill.\n result: stepErrors.length ? 'failed' : 'succeeded',\n trigger: event,\n // The summary and its search tokens, written together (AGL-3321).\n ...runSummaryFields(\n (outcomes.length ? outcomes.join(' · ') : 'Ran').slice(0, 300),\n ),\n target: {\n // An org automation's run is filed under its own type, so the site's\n // run history can tell it from a site action sharing the feed.\n type: orgRun ? ORG_AUTOMATION_RUN_TARGET : 'workflow',\n id: actionId,\n name: action.name ?? '',\n },\n // The site log's search finds a run by its action's name (AGL-3321).\n searchTokens: activitySearchTokens({ target: { name: action.name } }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n return ending\n}\n\n/** How a workflow run went, for whoever records or answers for it. */\nexport interface WorkflowExecution {\n ending: ActionRunEnding\n /**\n * Every error, in step order. An Actions step's error is recorded and the\n * run goes on, as it does in an action; a function call's error ENDS the\n * run, as it always has, because the steps after it read its result.\n */\n errors: string[]\n /** What each step that ran did, in the Actions run-history phrasing. */\n outcomes: string[]\n /** The workflow's return value, as the pure evaluator reports it. */\n value: number | string | boolean\n /** Every result the function calls bound, by name. */\n results: Record<string, number | string | boolean>\n}\n\ninterface ExecuteWorkflowOptions {\n /** The step to start at: a resume's `nextStepIndex`. */\n startIndex?: number\n /** The list to run: a resume's snapshot. */\n steps?: readonly WorkflowStep[]\n /** The enrollment a resume belongs to. */\n enrollmentRef?: FirebaseFirestore.DocumentReference | null\n /** A run started by another automation's `runWorkflow` step. */\n nested?: boolean\n}\n\n/**\n * Runs a workflow whose steps include Actions steps: function calls and\n * Actions steps, in order, in one scope.\n *\n * - A FUNCTION CALL is evaluated by the platform's pure evaluator,\n * `runWorkflow`, one call at a time, so its expressions see the event, the\n * site's variables and every result bound before it — exactly the scope a\n * function-only workflow gives it. A call that fails ends the run.\n * - An ACTIONS STEP runs through {@link runServerStep}, the executor actions\n * use, and reads the event payload together with every result bound so\n * far: a workflow can compute a score and write it to a dataset. Each one\n * takes the Actions tier gate (`actions`, Pro and up) on top of its own —\n * `webhookPost` on Business, the CRM steps on the CRM suite — and a step\n * only the visitor's browser can run is refused.\n * - `wait` and `waitForEvent` enroll the person and end this call; the rest\n * of the list, results included, continues from the beat. `exitFlow` ends\n * the run.\n *\n * Records nothing and meters nothing: the caller that admitted the run does\n * both, once.\n */\nexport async function executeWorkflow(\n env: ActionRunEnv,\n run: AutomationRun,\n workflow: AutomationWorkflow,\n event: string,\n payload: HostEventPayload,\n options: ExecuteWorkflowOptions = {},\n): Promise<WorkflowExecution> {\n const steps = options.steps ?? workflow.steps ?? []\n const tally: RunTally = { ending: 'ran', errors: [], outcomes: [] }\n const results: Record<string, number | string | boolean> = {}\n if (steps.length > WORKFLOW_MAX_STEPS) {\n return {\n ...tally,\n errors: [`Workflows are capped at ${WORKFLOW_MAX_STEPS} steps`],\n value: '',\n results,\n }\n }\n const context = await env.loadWorkflowContext()\n const enrollmentRef = options.enrollmentRef ?? null\n const startIndex = Math.max(0, options.startIndex ?? 0)\n /** The payload an Actions step reads: the event's, then every result. */\n const stepPayload = (): HostEventPayload => ({ ...payload, ...results })\n for (let index = startIndex; index < steps.length; index += 1) {\n const step = steps[index]\n if (!isWorkflowActionStep(step)) {\n const call = step as HostWorkflowStep\n const resultName = call.resultName?.trim() || `step${index + 1}`\n const evaluated = runWorkflow(\n { name: workflow.name, steps: [{ ...call, resultName }] },\n context.functions,\n context.variables,\n { event, ...stepPayload() },\n { workflows: context.workflows },\n )\n if (evaluated.ok === false) {\n // The evaluator numbers the one step it was handed; the author\n // numbers it by its place in the workflow.\n tally.errors.push(\n evaluated.error.replace(/^Step 1\\b/, `Step ${index + 1}`),\n )\n break\n }\n results[resultName] = evaluated.results[resultName]\n tally.outcomes.push(\n `ran ${String(call.functionName || call.functionId || 'a function').trim()}`,\n )\n continue\n }\n const refusal = workflowActionStepRefusal(step)\n if (refusal) {\n tally.errors.push(refusal)\n continue\n }\n if (!env.actionsAllowed) {\n tally.errors.push(\n `“${workflowStepTypeLabel(step.type)}” needs the ` +\n `${planLabelGrantingFeature('actions')} plan`,\n )\n continue\n }\n const verdict = await runServerStep(env, run, step, {\n index,\n steps,\n event,\n payload: stepPayload(),\n scope: { event, ...stepPayload() },\n enrollmentRef,\n nested: options.nested,\n })\n if (tallyStep(tally, step.type, verdict)) break\n }\n /*\n * The return value the pure evaluator would give: the named scope entry —\n * a result, a payload field or a variable — when the workflow names one,\n * and the last result otherwise. Asked of the evaluator itself, with no\n * steps, so a variable resolves by the evaluator's own rules.\n */\n const returnName = workflow.returnValue?.trim()\n const value = returnName\n ? (() => {\n const named = runWorkflow(\n { name: workflow.name, steps: [], returnValue: returnName },\n context.functions,\n context.variables,\n { event, ...stepPayload() },\n )\n return named.ok === false ? '' : named.value\n })()\n : (Object.values(results).at(-1) ?? '')\n return { ...tally, value, results }\n}\n\n/**\n * Suspends the run at a `wait` or `waitForEvent` step.\n *\n * One function for both the first suspension and every later one, because the\n * two differ only in whether a row already exists — and writing \"create here,\n * update there\" twice is how the two drift into disagreeing about which\n * fields a waiting enrollment carries.\n */\nasync function suspendFlow(\n env: ActionRunEnv,\n run: AutomationRun,\n request: {\n step: HostActionStep\n steps: readonly WorkflowStep[]\n nextStepIndex: number\n event: string\n payload: HostEventPayload\n enrollmentRef: FirebaseFirestore.DocumentReference | null\n },\n): Promise<{ error?: string; detail?: string }> {\n const { step } = request\n const minutes =\n step.type === 'wait'\n ? Number(step.delayMinutes)\n : step.type === 'waitForEvent'\n ? Number(step.timeoutMinutes)\n : 0\n if (!Number.isFinite(minutes) || minutes <= 0) {\n return { error: 'the wait has no duration' }\n }\n const nowMs = Date.now()\n const resumeAtMs = nowMs + minutes * 60_000\n const awaitingEvent =\n step.type === 'waitForEvent' ? step.eventName.trim() : null\n const detail =\n step.type === 'waitForEvent'\n ? `${awaitingEvent} (up to ${minutes}m)`\n : `${minutes}m`\n\n if (request.enrollmentRef) {\n await advanceFlowEnrollment(\n request.enrollmentRef,\n {\n nextStepIndex: request.nextStepIndex,\n resumeAtMs,\n awaitingEvent,\n payload: request.payload as Record<string, unknown>,\n },\n nowMs,\n )\n return { detail }\n }\n\n const enrolled = await enrollInFlow({\n hostId: env.hostId,\n actionId: run.id,\n // Absent for an action, which is every enrollment written before a\n // workflow could wait — the resume reads absent as `action`.\n ...(run.kind === 'workflow' ? { automation: 'workflow' as const } : {}),\n // An org automation waits on the site it ran on, under its own id prefix,\n // and names the organization whose document the resume re-reads.\n ...(run.kind === 'orgAutomation'\n ? { automation: 'org' as const, orgId: run.orgId ?? env.orgId ?? '' }\n : {}),\n // The SNAPSHOT is the list this run is executing, which on a first run is\n // the automation's own — so one edited later cannot move this person's\n // remaining steps out from under them.\n action: { name: run.name, steps: [...request.steps] },\n email: String((request.payload as any)?.['email'] ?? ''),\n event: request.event,\n payload: (request.payload ?? {}) as Record<string, unknown>,\n nextStepIndex: request.nextStepIndex,\n resumeAtMs,\n awaitingEvent,\n nowMs,\n })\n if (enrolled.enrolled === true) return { detail }\n /*\n * Both refusals are stated rather than swallowed, because both are things\n * an author can act on: a flow that waits needs an address in its trigger\n * payload, and a person already inside this flow is not put through it\n * twice concurrently.\n */\n return {\n error:\n enrolled.reason === 'no-person'\n ? 'a flow that waits needs the person’s email in the event payload'\n : 'this person is already waiting inside this flow',\n }\n}\n\n/**\n * Events too frequent to log a skip for.\n *\n * `runEventActions` fires on EVERY page view of every published site. A\n * Firestore write per visitor per non-matching action is not a run\n * history, it is an outage — and a page-view condition is one an author\n * tunes by watching the site, not by reading a log. The events people\n * actually debug are the server-emitted ones (a form submission, a\n * booking, a sign-up), and those are low-volume by construction.\n */\nexport const SKIP_LOG_EXCLUDED_EVENTS: ReadonlySet<string> = new Set(['pageView'])\n\n/**\n * Whether an action's unmet conditions only say it is ANOTHER FORM'S\n * (AGL-3458) — the case that is noise rather than an answer.\n *\n * A site with one auto-reply per form keyed on `formId equals …` would write\n * a `Skipped` row for every other form's reply on every submission: eight\n * rows a fill on a nine-form site, burying the one row that explains a real\n * miss. A form picked by id cannot be mistyped, so its mismatch against a\n * submission that names a DIFFERENT form says only \"not this form\", and it\n * is not recorded. Every other unmet condition still is: a hand-typed\n * `formName`, a field condition, a submission that names no form at all.\n *\n * With `and`, one such clause is the whole answer; with `or`, only when every\n * clause is about which form it was — `formId` or `formName` equals — since\n * any other clause could have been the one that was meant to match.\n */\nexport function conditionsNameAnotherForm(\n trigger: HostAction['trigger'] | null | undefined,\n payload: HostEventPayload,\n): boolean {\n const submitted = String(payload['formId'] ?? '').trim()\n if (!submitted) return false\n const clauses = normalizeTriggerConditions(trigger)\n const another = (clause: { field?: string; op?: string; value?: string }) =>\n clause.field?.trim() === 'formId' &&\n clause.op === 'equals' &&\n Boolean(clause.value?.trim()) &&\n clause.value?.trim() !== submitted\n if (trigger?.combinator === 'or') {\n return (\n clauses.some(another) &&\n clauses.every(\n (clause) =>\n clause.op === 'equals' && ['formId', 'formName'].includes(clause.field?.trim() ?? ''),\n )\n )\n }\n return clauses.some(another)\n}\n\n/**\n * Writes the `Skipped` row (AGL-2171): which condition stopped the run,\n * so the answer to \"why didn't it fire?\" is in the same place as every\n * run that did.\n *\n * Never counts against `actionRunsPerMonth` — nothing executed, and\n * charging for a condition that said no would be its own bug.\n */\nasync function recordSkippedRun(\n hostRef: FirebaseFirestore.DocumentReference,\n actionId: string,\n action: Pick<HostAction, 'name' | 'trigger'>,\n event: string,\n kind: 'action' | 'orgAutomation' = 'action',\n /** What stopped it, when it was not an unmet condition. */\n because?: string,\n): Promise<void> {\n if (SKIP_LOG_EXCLUDED_EVENTS.has(event)) return\n // `normalizeTriggerConditions`, not `trigger.conditions` — a pre-AGL-565\n // action carries a single `condition` and reading only the array would\n // give every one of them the nameless fallback.\n const named = normalizeTriggerConditions(action.trigger)\n .map((condition) => String(condition?.field ?? '').trim())\n .filter(Boolean)\n const reason =\n because ??\n (named.length\n ? `Condition on ${named.slice(0, 3).join(', ')} not met`\n : 'Trigger condition not met')\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n action: `${kind === 'orgAutomation' ? 'Org automation' : 'Action'} skipped on ${event}`,\n result: 'skipped',\n trigger: event,\n ...runSummaryFields(reason.slice(0, 300)),\n target: {\n type: kind === 'orgAutomation' ? ORG_AUTOMATION_RUN_TARGET : 'workflow',\n id: actionId,\n name: action.name ?? '',\n },\n searchTokens: activitySearchTokens({ target: { name: action.name } }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n}\n\n/**\n * Event-triggered action runner (AGL-148): loads enabled actions whose\n * `trigger.event` matches (built-in, site event, or custom), evaluates\n * optional filters over the payload, and executes each step list in\n * order. Never throws into the emitting request. Paid feature: the\n * `actions` flag gates and `actionRunsPerMonth` meters runs.\n *\n * The organization's automations placed on this site run here too\n * (AGL-3302), after the site's own, on the same run environment — they run\n * AS this site — and on the same meter.\n */\nexport async function runEventActions(\n hostId: string,\n event: string,\n payload: HostEventPayload = {},\n depth = 0,\n): Promise<HostActionAlert[]> {\n const alerts: HostActionAlert[] = []\n if (depth > ACTION_MAX_EVENT_DEPTH) return alerts\n /*\n * A flow waiting for THIS event, for THIS person, resumes here.\n *\n * Above the action lookup and outside its try, because the two are\n * unrelated: an event that matches no action can still be the one a flow\n * has been waiting a week for, and a site with no actions at all can hold\n * enrollments from an action that has since been rewritten.\n */\n await wakeFlowsAwaitingEvent(hostId, event, payload).catch(() => undefined)\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const actions = await liveActionsForEvent(hostRef, event)\n /*\n * THE ORGANIZATION'S AUTOMATIONS placed on this site and not paused here.\n *\n * Asked only for an org trigger — a server event a person is the subject\n * of — so a page view, a custom event and every on-page site event cost\n * nothing more than they did. Never throws: an org lookup that fails runs\n * this site's own actions exactly as before.\n */\n const placed = await findOrgAutomationsForEvent(hostId, event)\n if (!actions.length && !placed) return alerts\n\n // Webhook steps take the higher `webhooks` gate (AGL-149); plan gates\n // ride the owning org's doc (AGL-238).\n let webhooksAllowed = true\n /*\n * Whether each half fits under the month's allowance — all or nothing per\n * half, as the site's own actions always were.\n *\n * The site's actions are asked first and on exactly the question they\n * were always asked, so an organization placing automations on a site can\n * never be what stops that site's own actions running. The org half is\n * asked second, against what is left after the site's half, and both\n * count on this site's meter: an org automation's run is this site's\n * run, and the usage card, the usage alerts and the COGS rollup read it\n * there.\n */\n let runSite = false\n let runOrg = false\n // Plan-less orgs resolve as free (AGL-247) — gates always run. Held for\n // the rest of the run so a step's own plan check costs no second read.\n const owner = await getOrgForHost(hostId)\n // The band is the WORKSPACE's (AGL-3472): every site's runs count\n // against it, read off the org's counter — see `run-meter.ts`.\n const meter: RunMeterScope = {\n firestore,\n hostRef,\n orgId: owner?.orgId,\n counter: 'actionRuns',\n month: runMonthKey(),\n }\n // A suspended site runs nothing (AGL-3356): see `eventRunSuspension`.\n if (await eventRunSuspension(hostRef, owner?.org)) return alerts\n {\n const org = owner?.org\n if (!checkEntitlement(org as any, 'actions')) return alerts\n webhooksAllowed = checkEntitlement(org as any, 'webhooks')\n const limit = resolveOrgEntitlements(\n org as any,\n ).actionRunsPerMonth\n const used = await runsUsedThisMonth(meter)\n // Asked as \"would it go over\", the question this gate has always\n // asked — so a counter that reads as no number refuses nothing, as\n // before, rather than refusing everything.\n runSite = actions.length > 0 && !(used + actions.length > limit)\n /*\n * Only for the organization the site belongs to NOW: the automations\n * were found through the site's org, and a site that changed hands\n * between the two reads runs none of its old organization's.\n */\n runOrg =\n Boolean(placed) &&\n placed?.orgId === owner?.orgId &&\n !(\n used + (runSite ? actions.length : 0) + (placed?.docs.length ?? 0) >\n limit\n )\n if (!runSite && !runOrg) return alerts\n }\n\n const env: ActionRunEnv = {\n hostId,\n hostRef,\n alerts,\n // Admitted by the `actions` gate above.\n actionsAllowed: true,\n webhooksAllowed,\n depth,\n org: owner?.org ?? null,\n orgId: owner?.orgId ?? null,\n loadWorkflowContext: makeWorkflowContextLoader(hostRef),\n }\n\n let executed = 0\n for (const doc of runSite ? actions : []) {\n const action = doc.data() as HostAction\n const filter = action.trigger?.filter?.trim()\n if (filter) {\n const unrunnable = triggerFilterProblem(filter)\n if (unrunnable) {\n await recordSkippedRun(hostRef, doc.id, action, event, 'action', unrunnable)\n continue\n }\n try {\n if (!evaluateExpression(filter, { event, ...payload })) continue\n } catch {\n continue // A field the filter names is not on this event.\n }\n }\n // Structured payload conditions (AGL-557; AND/OR chaining AGL-565):\n // same scope as the filter; unmet conditions skip the action and\n // never count as a run against the quota.\n if (!evaluateTriggerConditions(action.trigger, { event, ...payload })) {\n // …but they are RECORDED now (AGL-2171). This was a bare\n // `continue`, so \"why didn't my automation fire?\" — the most\n // common support question about automations — had no answer\n // anywhere in the product, while `/product/workflows` advertises\n // an amber `Skipped` row that answers it.\n if (!conditionsNameAnotherForm(action.trigger, payload)) {\n await recordSkippedRun(hostRef, doc.id, action, event)\n }\n continue\n }\n executed += 1\n await executeAction(env, doc.id, action, event, payload)\n }\n /*\n * The org automations, judged by the same filter and conditions a site\n * action is, skipped and recorded the same way, and run on this site.\n */\n for (const doc of runOrg && placed ? placed.docs : []) {\n const automation = doc.data() as OrgAutomation\n const filter = automation.trigger?.filter?.trim()\n if (filter) {\n const unrunnable = triggerFilterProblem(filter)\n if (unrunnable) {\n await recordSkippedRun(hostRef, doc.id, automation, event, 'orgAutomation', unrunnable)\n continue\n }\n try {\n if (!evaluateExpression(filter, { event, ...payload })) continue\n } catch {\n continue // A field the filter names is not on this event.\n }\n }\n if (\n !evaluateTriggerConditions(automation.trigger, { event, ...payload })\n ) {\n if (!conditionsNameAnotherForm(automation.trigger, payload)) {\n await recordSkippedRun(hostRef, doc.id, automation, event, 'orgAutomation')\n }\n continue\n }\n executed += 1\n await executeAction(env, doc.id, automation, event, payload, {\n orgAutomation: { orgId: placed?.orgId ?? '' },\n })\n }\n await recordRuns({ ...meter, count: executed })\n } catch (error) {\n console.error('runEventActions failed', hostId, event, error)\n }\n return alerts\n}\n\n/**\n * Why a dispatched action ran nothing (AGL-3309).\n *\n * A page's dispatch has nobody to tell: a visitor's scroll is not answered\n * with \"the plan lapsed\", so for it every refusal is the same empty list of\n * alerts. The console's test run does have somebody to tell, and a test that\n * ran nothing must not read as one that ran.\n */\nexport type SingleActionSkip =\n // No such action, or it was deleted.\n | 'missing'\n // Switched off.\n | 'disabled'\n // The dispatch named an event other than the action's trigger.\n | 'event'\n // The trigger's conditions are not met by the payload.\n | 'conditions'\n // The site's plan does not carry the `actions` feature.\n | 'plan'\n // The month's action runs are spent.\n | 'allowance'\n // A read before the first step threw; it was logged. Steps never throw\n // (`runServerStep` records a throw as a failed step), so nothing ran.\n | 'failed'\n\n/** What one dispatched action did, for a caller that has to say so. */\nexport interface SingleActionOutcome {\n /** Its steps ran, and the run counted on the site's and workspace's meters. */\n ran: boolean\n /** Why nothing ran; `null` when it ran. */\n skipped: SingleActionSkip | null\n /** The site alerts its steps produced. */\n alerts: HostActionAlert[]\n /** The month's allowance, when the allowance is what refused. */\n limit?: number\n}\n\n/**\n * Runs ONE action's server steps (AGL-256): the tenant page runtime\n * evaluates site-event trigger conditions (scroll thresholds, selectors)\n * client-side and dispatches the specific action here — re-matching by\n * event name would wrongly fire sibling actions with different\n * thresholds. Same gates and metering as the event runner.\n */\nexport async function runSingleAction(\n hostId: string,\n actionId: string,\n event: string,\n payload: HostEventPayload = {},\n): Promise<HostActionAlert[]> {\n return (await runSingleActionOutcome(hostId, actionId, event, payload)).alerts\n}\n\n/**\n * {@link runSingleAction}, answering what it did as well as the alerts: whether\n * the steps ran and, when they did not, which gate stopped them.\n *\n * One body for both, so the console's test run meets exactly the gates a\n * page's dispatch meets — the switch, the event, the conditions, the plan and\n * the month's allowance — and is metered and recorded the same way.\n */\nexport async function runSingleActionOutcome(\n hostId: string,\n actionId: string,\n event: string,\n payload: HostEventPayload = {},\n): Promise<SingleActionOutcome> {\n const alerts: HostActionAlert[] = []\n const skipped = (\n reason: SingleActionSkip,\n limit?: number,\n ): SingleActionOutcome => ({\n ran: false,\n skipped: reason,\n alerts,\n ...(limit === undefined ? {} : { limit }),\n })\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const doc = await hostRef.collection('actions').doc(actionId).get()\n if (!doc.exists || doc.get('deletedAt')) return skipped('missing')\n if (doc.get('enabled') === false) return skipped('disabled')\n const action = doc.data() as HostAction\n // Only site-event actions may be dispatched externally — server\n // events flow through their own emitters.\n if (String(action.trigger?.event ?? '') !== String(event)) {\n return skipped('event')\n }\n // Structured payload conditions (AGL-557; AND/OR chaining AGL-565):\n // the single-action dispatch path honors them too, so\n // client-evaluated triggers can't bypass them.\n if (!evaluateTriggerConditions(action.trigger, { event, ...payload })) {\n return skipped('conditions')\n }\n\n let webhooksAllowed = true\n // Held for the rest of the run, as in `runEventActions` above.\n const owner = await getOrgForHost(hostId)\n // The workspace's band, as in `runEventActions` above (AGL-3472).\n const meter: RunMeterScope = {\n firestore,\n hostRef,\n orgId: owner?.orgId,\n counter: 'actionRuns',\n month: runMonthKey(),\n }\n {\n const org = owner?.org\n if (!checkEntitlement(org as any, 'actions')) return skipped('plan')\n webhooksAllowed = checkEntitlement(org as any, 'webhooks')\n const limit = resolveOrgEntitlements(\n org as any,\n ).actionRunsPerMonth\n const used = await runsUsedThisMonth(meter)\n if (used + 1 > limit) return skipped('allowance', limit)\n }\n\n const env: ActionRunEnv = {\n hostId,\n hostRef,\n alerts,\n // Admitted by the `actions` gate above.\n actionsAllowed: true,\n webhooksAllowed,\n depth: 0,\n org: owner?.org ?? null,\n orgId: owner?.orgId ?? null,\n loadWorkflowContext: makeWorkflowContextLoader(hostRef),\n }\n await executeAction(env, doc.id, action, event, payload)\n await recordRuns({ ...meter, count: 1 })\n return { ran: true, skipped: null, alerts }\n } catch (error) {\n console.error('runSingleAction failed', hostId, actionId, error)\n return skipped('failed')\n }\n}\n\n/**\n * Ends an enrollment its automation may no longer run, and says why in the\n * run history — the automation was deleted or switched off, or the plan that\n * carried it lapsed. Shared by every kind of enrollment, so \"stopped mid-wait\"\n * reads the same for an action, a workflow and an org automation.\n */\nexport async function stopFlowEnrollment(\n enrollment: FlowEnrollment,\n ref: FirebaseFirestore.DocumentReference,\n reason: string,\n): Promise<'stopped'> {\n const hostRef = firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(enrollment.hostId)\n await endFlowEnrollment(ref)\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n action: `Flow stopped mid-wait: ${reason}`.slice(0, 300),\n result: 'skipped',\n trigger: enrollment.event,\n ...runSummaryFields(reason.slice(0, 300)),\n target: {\n type:\n enrollment.automation === 'org'\n ? ORG_AUTOMATION_RUN_TARGET\n : 'workflow',\n id: enrollment.actionId,\n name: enrollment.actionName ?? '',\n },\n searchTokens: activitySearchTokens({ target: { name: enrollment.actionName } }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n return 'stopped'\n}\n\n/**\n * Continues one enrollment from where its wait ended.\n *\n * The enrollment carries everything the run needs except the gates: the step\n * list it entered with, the position inside it, the payload the trigger\n * produced, and who it is about. What it deliberately does NOT carry is\n * permission — the `actions` entitlement, the site's lockdown and the\n * action's own enabled flag are all re-asked here, because a flow that waits\n * three days is a flow that can outlive the plan, the site and the merchant's\n * decision to run it.\n */\nexport async function resumeFlowEnrollment(\n enrollment: FlowEnrollment,\n ref: FirebaseFirestore.DocumentReference,\n options?: { timedOut?: boolean; nowMs?: number },\n): Promise<'ran' | 'waiting' | 'exited' | 'deferred' | 'stopped'> {\n const nowMs = options?.nowMs ?? Date.now()\n // A workflow's enrollment continues the workflow, against the workflow's\n // own kill switch and meter; every enrollment without the field is an\n // action's, which is every one written before a workflow could wait.\n if (enrollment.automation === 'workflow') {\n return await resumeWorkflowEnrollment(enrollment, ref, {\n ...options,\n nowMs,\n })\n }\n // An org automation's continues against the ORGANIZATION's document, which\n // is its kill switch — deleted, switched off, taken off this site or paused\n // here all stop it (AGL-3302).\n if (enrollment.automation === 'org') {\n return await resumeOrgAutomationEnrollment(enrollment, ref, {\n ...options,\n nowMs,\n })\n }\n const hostId = enrollment.hostId\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n\n const stop = (reason: string) => stopFlowEnrollment(enrollment, ref, reason)\n\n const doc = await hostRef\n .collection('actions')\n .doc(enrollment.actionId)\n .get()\n .catch(() => null)\n /*\n * A KILL SWITCH HAS TO KILL, including for the people already inside.\n *\n * Editing a flow leaves an enrollment alone — it runs the snapshot it\n * entered with. DELETING or DISABLING one does not: \"off\" that keeps mailing\n * the queue for the next three days is not off, and it is the control a\n * merchant reaches for when a flow is doing something wrong. So the two\n * cases are deliberately different, and this is the one that stops.\n */\n if (!doc?.exists || doc.get('deletedAt') || doc.get('enabled') === false) {\n return await stop('the automation was turned off or deleted')\n }\n const action = doc.data() as HostAction\n const owner = await getOrgForHost(hostId).catch(() => null)\n if (!checkEntitlement(owner?.org as any, 'actions')) {\n return await stop('this site’s plan no longer includes automations')\n }\n\n const env: ActionRunEnv = {\n hostId,\n hostRef,\n alerts: [],\n // Admitted by the `actions` gate above.\n actionsAllowed: true,\n webhooksAllowed: checkEntitlement(owner?.org as any, 'webhooks'),\n depth: 0,\n org: owner?.org ?? null,\n orgId: owner?.orgId ?? null,\n loadWorkflowContext: makeWorkflowContextLoader(hostRef),\n }\n\n const payload: HostEventPayload = {\n ...(enrollment.payload ?? {}),\n // The timeout BRANCH. A `waitForEvent` resumes either way, and this is\n // the one field that says which — so the step after it carries a `when`\n // naming it, and the author gets a timeout path with no nested step list.\n ...(options?.timedOut ? { [FLOW_TIMED_OUT_FIELD]: true } : {}),\n }\n\n const ending = await executeAction(\n env,\n enrollment.actionId,\n action,\n enrollment.event,\n payload,\n {\n startIndex: enrollment.nextStepIndex,\n // An action's enrollment holds only Actions steps: its snapshot is the\n // action's own list.\n steps: enrollment.steps as HostActionStep[],\n enrollmentRef: ref,\n },\n )\n\n if (ending === 'deferred') {\n /*\n * Pushed a beat down the queue rather than retried immediately: the two\n * refusals that reach here are an hour-long platform ceiling and a\n * day-long per-person window, so retrying in sixty seconds would spend a\n * read to be told the same thing sixty more times.\n */\n await deferFlowEnrollment(ref, nowMs + FLOW_CLAIM_RETRY_MS, nowMs)\n return ending\n }\n if (ending !== 'waiting') await endFlowEnrollment(ref)\n\n /*\n * COUNTED, NEVER REFUSED.\n *\n * A resume is real work and belongs on the run meter, so the usage card and\n * the COGS rollup see it. It is not GATED on `actionRunsPerMonth` the way a\n * new run is, because the person is already inside the flow: refusing here\n * would abandon somebody half-way through a sequence, which is a capacity\n * limit enforced against a person rather than against the decision that\n * added them. The gate belongs at enrollment, and that is where it is.\n */\n await recordRuns({\n firestore,\n hostRef,\n orgId: owner?.orgId,\n counter: 'actionRuns',\n month: runMonthKey(nowMs),\n count: 1,\n })\n return ending\n}\n\n/** How long a deferred enrollment waits before the next attempt. */\nexport const FLOW_CLAIM_RETRY_MS = 15 * 60_000\n\n/**\n * The job beat's entry point: resume every flow whose wait has ended.\n *\n * Thin on purpose. The scheduling contract — due-ness, the transactional\n * claim, the scan budget, the lockdown skip — is `sweepDueFlowEnrollments`,\n * and the work is `resumeFlowEnrollment`; this is the wire between them, so\n * neither has to import the other's dependencies to be tested.\n */\nexport async function runDueFlowEnrollments(\n gate: PluginJobHostGate,\n options?: {\n nowMs?: number\n scanBudget?: number\n cursor?: FlowSweepCursor | null\n },\n): Promise<FlowSweepResult> {\n return await sweepDueFlowEnrollments(gate, {\n ...(options?.nowMs !== undefined ? { nowMs: options.nowMs } : {}),\n ...(options?.scanBudget !== undefined\n ? { scanBudget: options.scanBudget }\n : {}),\n ...(options?.cursor ? { cursor: options.cursor } : {}),\n resume: async (enrollment, ref) => {\n await resumeFlowEnrollment(enrollment, ref, {\n // Reaching the sweep IS the timeout for a `waitForEvent`: the event\n // it was watching for never arrived before `resumeAtMs`. A plain\n // `wait` has no timeout to report, so the flag rides the presence of\n // an awaited event rather than the fact of being swept.\n timedOut: Boolean(enrollment.awaitingEvent),\n ...(options?.nowMs !== undefined ? { nowMs: options.nowMs } : {}),\n })\n },\n })\n}\n\n/**\n * Wakes the flows this person's event was being waited for.\n *\n * NOT A POLL. Nothing here scans the enrolled population asking whose turn it\n * is — the event arrives already naming a person, and the lookup is three\n * equality filters against that person's key. A site with ten thousand people\n * waiting costs the same as one with ten.\n *\n * The COST GUARD is the caller's, and it is why this takes an email rather\n * than reading one: `runEventActions` fires on every page view of every\n * published site, so it asks only for events that name a person. A page view\n * does not, and pays nothing.\n */\nasync function wakeFlowsAwaitingEvent(\n hostId: string,\n event: string,\n payload: HostEventPayload,\n): Promise<void> {\n const email = String((payload as any)?.['email'] ?? '').trim()\n if (!email || !email.includes('@')) return\n const waiting = await findFlowEnrollmentsAwaiting({ hostId, event, email })\n for (const doc of waiting) {\n const claimed = await claimFlowEnrollment(doc.ref).catch(() => null)\n if (!claimed) continue\n try {\n /*\n * The awaited event ARRIVED, so this is not the timeout branch — and\n * the arriving payload joins the carried one, so a step after the wait\n * can read the order total the flow was waiting for.\n */\n await resumeFlowEnrollment(\n {\n ...claimed,\n payload: { ...(claimed.payload ?? {}), ...(payload ?? {}) },\n },\n doc.ref,\n { timedOut: false },\n )\n } catch (error) {\n console.error('[flow] event wake failed', doc.ref.path, error)\n await deferFlowEnrollment(doc.ref, Date.now() + FLOW_CLAIM_RETRY_MS)\n }\n }\n}\n\nexport default runEventActions\n"],"names":["ACTION_MAX_STEPS","ACTIONS_MAX_PER_HOST","checkEntitlement","consentGroupForHost","planLabelGrantingFeature","evaluateExpression","evaluateStepGuard","evaluateTriggerConditions","flowSubscriptionTopicId","hostPublicOrigin","isClientActionStep","normalizeTriggerConditions","resolveOrgEntitlements","isDeferrableSendResult","isEmailConfigured","sendEmail","sendFailureReason","enrollListMember","filterSendableForHost","firebaseAdmin","flowEmailRefusal","getOrgForHost","hostDisplayName","hostSendingIdentity","meterHostEmail","notifyHostManagers","consentGroupForSite","resolveOrgIdForHost","activitySearchTokens","visibleToHost","triggerFilterProblem","filePluginPersonUnder","findPluginPerson","findOrgContainersByName","readOrgContainers","screenOutboundSend","renderSiteTextEmail","createHmac","FieldValue","runSummaryFields","ACTION_MAX_EVENT_DEPTH","runWorkflow","WORKFLOW_MAX_STEPS","describeStepOutcome","WEBHOOK_URL_PATTERN","eventRunSuspension","triggeredDocsForEvent","resolveStepEmailMerge","stepEmailMergeContext","recordRuns","runMonthKey","runsUsedThisMonth","advanceFlowEnrollment","claimFlowEnrollment","deferFlowEnrollment","endFlowEnrollment","enrollInFlow","findFlowEnrollmentsAwaiting","sweepDueFlowEnrollments","resumeWorkflowEnrollment","runEventWorkflows","ORG_AUTOMATION_RUN_TARGET","orgAutomationStepRefusal","findOrgAutomationsForEvent","resumeOrgAutomationEnrollment","isWorkflowActionStep","workflowActionStepRefusal","workflowHasActionSteps","workflowStepTypeLabel","runTriggeredByFields","pluginServerStepExecutor","preparePluginRecordEmail","FLOW_TIMED_OUT_FIELD","isFlowSuspendingStep","sendEmailIsTransactionalReply","MAX_TRIGGERED_ACTIONS","liveActionsForEvent","hostRef","event","collection","keep","doc","get","max","maxReads","automationRunEnv","input","org","owner","hostId","alerts","actionsAllowed","webhooksAllowed","depth","orgId","loadWorkflowContext","makeWorkflowContextLoader","workflowContext","functionDocs","variableDocs","workflowDocs","Promise","all","limit","workflowContextFromDocs","docs","byName","map","data","deletedAt","id","name","workflows","withId","$id","functions","variables","tallyStep","tally","type","verdict","kind","outcomes","push","detail","errors","error","ending","raiseEarnedEvent","env","emit","payload","fromWorkflows","fromActions","runEventActions","runServerStep","run","step","context","enrollmentRef","failed","when","scope","nested","suspended","suspendFlow","steps","nextStepIndex","index","message","String","slice","severity","workflow","workflowId","trim","workflowName","executeWorkflow","length","join","evaluated","ok","eventName","hookDoc","webhookId","where","webhookName","hook","exists","undefined","enabled","direction","url","test","body","JSON","stringify","sentAt","Date","toISOString","signature","secret","update","digest","delivered","lastStatus","attempt","response","fetch","method","headers","signal","AbortSignal","timeout","status","resolve","setTimeout","title","link","screened","to","toField","includes","hostData","catch","siteBase","transactional","afterWait","Boolean","topicId","consentGroup","sendable","gate","email","authoredSubject","subject","authoredText","mergeContext","contactGroupId","groupId","siteName","mergePerson","emailSubject","text","replace","emailText","path","host","bodies","outcome","reference","recordEmail","contactId","framed","app","firestore","result","html","sendingIdentity","tags","audience","releasedReviewId","priority","marketing","consentHostIds","hostIds","consentAwaitsConfirmation","awaitsConfirmation","refusal","sendError","sent","file","sourceRef","toLowerCase","listsRef","listDoc","listId","listName","listRef","ref","group","source","contact","onlyVisibleToSite","campaignLabel","campaignName","campaignId","campaignOrgId","containerStore","usable","container","live","visibleTo","namedId","campaign","find","filed","record","containerKind","ids","executor","answer","executeAction","actionId","action","options","orgRun","orgAutomation","startIndex","Math","stepErrors","noun","summary","add","actorId","actorEmail","trigger","target","searchTokens","createdAt","serverTimestamp","Object","results","value","stepPayload","call","resultName","functionName","functionId","returnName","returnValue","named","values","at","request","minutes","Number","delayMinutes","timeoutMinutes","isFinite","nowMs","now","resumeAtMs","awaitingEvent","enrolled","automation","reason","SKIP_LOG_EXCLUDED_EVENTS","Set","conditionsNameAnotherForm","submitted","clauses","another","clause","field","op","combinator","some","every","recordSkippedRun","because","has","condition","filter","wakeFlowsAwaitingEvent","actions","placed","runSite","runOrg","meter","counter","month","actionRunsPerMonth","used","executed","unrunnable","count","console","runSingleAction","runSingleActionOutcome","skipped","ran","stopFlowEnrollment","enrollment","actionName","resumeFlowEnrollment","stop","timedOut","FLOW_CLAIM_RETRY_MS","runDueFlowEnrollments","scanBudget","cursor","resume","waiting","claimed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/run-event-actions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ACTION_MAX_STEPS,\n ACTIONS_MAX_PER_HOST,\n checkEntitlement,\n consentGroupForHost,\n planLabelGrantingFeature,\n evaluateExpression,\n evaluateStepGuard,\n evaluateTriggerConditions,\n flowSubscriptionTopicId,\n hostPublicOrigin,\n isClientActionStep,\n type HostEventType,\n type HostFunction,\n type HostVariable,\n normalizeTriggerConditions,\n type PluginJobHostGate,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/server'\nimport {\n isDeferrableSendResult,\n isEmailConfigured,\n sendEmail,\n sendFailureReason,\n} from '@aglyn/shared-util-email'\nimport {\n enrollListMember,\n filterSendableForHost,\n firebaseAdmin,\n flowEmailRefusal,\n getOrgForHost,\n hostDisplayName,\n hostSendingIdentity,\n meterHostEmail,\n notifyHostManagers,\n consentGroupForSite,\n resolveOrgIdForHost,\n} from '@aglyn/tenant-data-admin'\nimport { activitySearchTokens } from '@aglyn/aglyn/app-utils/activity-search'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { triggerFilterProblem } from '@aglyn/aglyn/app-utils/site-interactions'\n// The person behind an address, and their filing under a campaign, through\n// the plugin that keeps people (AGL-3080): the engine opens none of its\n// collections.\nimport {\n filePluginPersonUnder,\n findPluginPerson,\n} from '@aglyn/aglyn/plugin-manager/plugin-person-records'\nimport {\n findOrgContainersByName,\n readOrgContainers,\n type OrgContainerRecord,\n} from '@aglyn/tenant-data-admin/server/org-containers'\n// The leaf again, for the phishing screen's hold (AGL-3356): the control\n// that stops a send must be the real one under a spec that mocks the barrel.\nimport { screenOutboundSend } from '@aglyn/tenant-data-admin/server/outbound-send-review'\nimport { renderSiteTextEmail } from '@aglyn/tenant-data-admin/server/host-email-tokens'\nimport { createHmac } from 'crypto'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { runSummaryFields } from '../model/run-history'\nimport {\n ACTION_MAX_EVENT_DEPTH,\n type HostWorkflow,\n type HostWorkflowStep,\n runWorkflow,\n WORKFLOW_MAX_STEPS,\n} from '../model/workflows'\nimport { describeStepOutcome } from '../model/step-outcomes'\nimport type { HostWebhook } from '../model/webhooks'\nimport { eventRunSuspension } from './site-suspension'\nimport { triggeredDocsForEvent } from './triggered-docs'\nimport { resolveStepEmailMerge, stepEmailMergeContext } from './email-merge'\nimport {\n recordRuns,\n type RunMeterScope,\n runMonthKey,\n runsUsedThisMonth,\n} from './run-meter'\nimport {\n advanceFlowEnrollment,\n claimFlowEnrollment,\n deferFlowEnrollment,\n endFlowEnrollment,\n enrollInFlow,\n findFlowEnrollmentsAwaiting,\n type FlowEnrollment,\n type FlowSweepCursor,\n type FlowSweepResult,\n sweepDueFlowEnrollments,\n} from './flow-enrollments'\n// The runtime's leaves rather than its barrel: the engine's specs substitute\n// each leaf, and a mock of the barrel would take the rest of it down too.\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\nimport {\n resumeWorkflowEnrollment,\n runEventWorkflows,\n} from './run-event-workflows'\nimport {\n ORG_AUTOMATION_RUN_TARGET,\n type OrgAutomation,\n orgAutomationStepRefusal,\n} from '../model/org-automations'\nimport {\n findOrgAutomationsForEvent,\n resumeOrgAutomationEnrollment,\n} from './run-org-automations'\nimport {\n type AutomationWorkflow,\n isWorkflowActionStep,\n type WorkflowStep,\n workflowActionStepRefusal,\n workflowHasActionSteps,\n workflowStepTypeLabel,\n} from './workflow-steps'\nimport { runTriggeredByFields } from './run-trigger-actor'\n// By path, not the barrel: only a server run asks.\nimport { pluginServerStepExecutor } from '@aglyn/aglyn/plugin-manager/plugin-server-steps'\nimport { preparePluginRecordEmail } from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport {\n FLOW_TIMED_OUT_FIELD,\n type HostAction,\n type HostActionAlert,\n type HostActionStep,\n type HostActionStepType,\n isFlowSuspendingStep,\n sendEmailIsTransactionalReply,\n} from '../model/host-actions'\n\n/**\n * Every live, switched-on action a site holds for an event runs, in\n * document-id order (AGL-3458) — up to the most live actions a site may hold\n * at all, so no matching action is ever left out. See `triggered-docs.ts`.\n */\nconst MAX_TRIGGERED_ACTIONS = ACTIONS_MAX_PER_HOST\n\n/** The live, switched-on actions a site holds for `event`, in document-id order. */\nexport function liveActionsForEvent(\n hostRef: FirebaseFirestore.DocumentReference,\n event: string,\n): Promise<FirebaseFirestore.QueryDocumentSnapshot[]> {\n return triggeredDocsForEvent(hostRef.collection('actions'), event, {\n keep: (doc) => !doc.get('deletedAt') && doc.get('enabled') !== false,\n max: MAX_TRIGGERED_ACTIONS,\n maxReads: MAX_TRIGGERED_ACTIONS * 4,\n })\n}\n\nexport interface ActionRunEnv {\n hostId: string\n hostRef: FirebaseFirestore.DocumentReference\n alerts: HostActionAlert[]\n /**\n * Whether the owning org holds the actions builder (`actions`, Pro and up)\n * — the gate every Actions step takes. An action run is admitted by it\n * before its first step, so it is always true there; a workflow run is not,\n * because its function calls need no such plan, so the workflow executor\n * asks it of each Actions step it reaches.\n */\n actionsAllowed: boolean\n webhooksAllowed: boolean\n depth: number\n /**\n * The owning org's billing doc, already read by the entitlement gate that\n * admitted this run, and the org's id beside it.\n *\n * Carried rather than re-read: both entry points resolve `getOrgForHost`\n * before they build this, so a step that gates on the plan — another\n * plugin's step, handed both — costs no extra org read.\n * Null only when the host has no resolvable org, which the gate treats as\n * the free plan.\n */\n org: unknown\n orgId: string | null\n loadWorkflowContext: () => Promise<WorkflowContext>\n}\n\n/**\n * The site's functions, variables and workflows, double-keyed by document id\n * and by name (AGL-261). A workflow value also carries its document id as\n * `$id`, so a step that names a workflow by its name can still run it as the\n * automation it is.\n */\nexport interface WorkflowContext {\n functions: Record<string, HostFunction>\n variables: Record<string, HostVariable>\n workflows: Record<string, HostWorkflow & { $id?: string }>\n}\n\n/**\n * The automation a run belongs to, as the step executors need it: which kind,\n * its document id, and its name for the history and the enrollment.\n */\nexport interface AutomationRun {\n /**\n * `orgAutomation` for an org automation (AGL-3302) — a run on the event's\n * site of an automation the organization placed there.\n */\n kind: 'action' | 'workflow' | 'orgAutomation'\n id: string\n name: string\n /** The owning organization, on an `orgAutomation` run: a wait enrolls with it. */\n orgId?: string\n}\n\n/**\n * The run environment for one automation run: the org's plan gates resolved\n * once, from the org document the caller already read.\n */\nexport function automationRunEnv(input: {\n hostId: string\n hostRef: FirebaseFirestore.DocumentReference\n owner: { org?: unknown; orgId?: string | null } | null | undefined\n depth: number\n alerts?: HostActionAlert[]\n loadWorkflowContext?: ActionRunEnv['loadWorkflowContext']\n}): ActionRunEnv {\n const org = input.owner?.org ?? null\n return {\n hostId: input.hostId,\n hostRef: input.hostRef,\n alerts: input.alerts ?? [],\n actionsAllowed: checkEntitlement(org as any, 'actions'),\n webhooksAllowed: checkEntitlement(org as any, 'webhooks'),\n depth: input.depth,\n org,\n orgId: input.owner?.orgId ?? null,\n loadWorkflowContext:\n input.loadWorkflowContext ?? makeWorkflowContextLoader(input.hostRef),\n }\n}\n\nexport function makeWorkflowContextLoader(\n hostRef: FirebaseFirestore.DocumentReference,\n) {\n let workflowContext: WorkflowContext | null = null\n return async (): Promise<WorkflowContext> => {\n if (workflowContext) return workflowContext\n const [functionDocs, variableDocs, workflowDocs] = await Promise.all([\n hostRef.collection('functions').limit(100).get(),\n hostRef.collection('variables').limit(100).get(),\n hostRef.collection('workflows').limit(100).get(),\n ])\n workflowContext = workflowContextFromDocs(\n functionDocs.docs,\n variableDocs.docs,\n workflowDocs.docs,\n )\n return workflowContext\n }\n}\n\n/**\n * The working set a run reads, from documents already in hand — for a door\n * that read them itself and must not pay for them twice.\n */\nexport function workflowContextFromDocs(\n functionDocs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n variableDocs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n workflowDocs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n): WorkflowContext {\n // Double-keyed by doc id AND name (AGL-261): id references are\n // rename-safe; legacy name references keep resolving.\n const byName = <T extends { name?: string; deletedAt?: unknown }>(\n docs: readonly FirebaseFirestore.QueryDocumentSnapshot[],\n ) => {\n const map: Record<string, T> = {}\n for (const doc of docs) {\n const data = doc.data() as T\n if (data.deletedAt) continue\n map[doc.id] = data\n if (data?.name) map[data.name] = data\n }\n return map\n }\n // A workflow is carried with its id so a step naming it by its legacy\n // name still runs it as the automation it is (see `WorkflowContext`).\n const workflows: WorkflowContext['workflows'] = {}\n for (const doc of workflowDocs) {\n const data = doc.data() as HostWorkflow\n if ((data as { deletedAt?: unknown }).deletedAt) continue\n const withId = { ...data, $id: doc.id }\n workflows[doc.id] = withId\n if (data?.name) workflows[data.name] = withId\n }\n return {\n functions: byName<HostFunction>(functionDocs),\n variables: byName<HostVariable>(variableDocs),\n workflows,\n }\n}\n\n/** How a run of an action's step list ended. */\ntype ActionRunEnding = 'ran' | 'waiting' | 'exited' | 'deferred'\n\ninterface ExecuteActionOptions {\n /**\n * The step to start at. Non-zero only on a resume, where it is the\n * enrollment's `nextStepIndex`.\n */\n startIndex?: number\n /**\n * The step list to run.\n *\n * A resume passes the enrollment's SNAPSHOT rather than the action's\n * current steps — see `FlowEnrollment.steps` for why a position in a list\n * is meaningless against a list that has since been edited.\n */\n steps?: readonly HostActionStep[]\n /**\n * The enrollment this run belongs to. Present only on a resume; a first run\n * mints one if it reaches a wait.\n */\n enrollmentRef?: FirebaseFirestore.DocumentReference | null\n /**\n * Present for a run of an ORG automation (AGL-3302), naming the\n * organization that owns it. The steps run on this site exactly as a site\n * action's do; what changes is that each is held to the org vocabulary as\n * it runs, a wait enrolls under the org automation's own id, and the\n * history row is filed under it.\n */\n orgAutomation?: { orgId: string }\n}\n\n/** What one step is run against: its place, the list it is in, the event. */\ninterface ServerStepContext {\n /** The step's index in {@link steps}. */\n index: number\n /** The list being run — the snapshot a wait enrolls with. */\n steps: readonly WorkflowStep[]\n event: string\n /** The payload the step reads its values from. */\n payload: HostEventPayload\n /** The scope a step's `when` is evaluated against. */\n scope: Record<string, unknown>\n enrollmentRef: FirebaseFirestore.DocumentReference | null\n /**\n * Set when this run is itself a step of another automation — a workflow a\n * `runWorkflow` step started. Such a run finishes inside its caller's run,\n * so it has no enrollment of its own to wait in.\n */\n nested?: boolean\n}\n\n/**\n * What one Actions step did, for the run that holds it.\n *\n * - `skipped` — its `when` was not met, or it is a step the visitor's page\n * runs; nothing is recorded.\n * - `done` — it did its work; `detail` is the fact the history line carries.\n * - `failed` — it did not; the run records the error and continues.\n * - `exited` — an `exitFlow`: the run ends here.\n * - `waiting` — a wait enrolled the person; the rest runs from the beat.\n * - `halted` — a wait that could not enroll; the run ends with the error.\n * - `deferred` — a resumed send was refused for now; the enrollment retries.\n */\nexport type ServerStepVerdict =\n | { kind: 'skipped' }\n | { kind: 'done'; detail?: string }\n | { kind: 'failed'; error: string }\n | { kind: 'exited' }\n | { kind: 'waiting'; detail?: string }\n | { kind: 'halted'; error: string }\n | { kind: 'deferred' }\n\n/** A run's tally so far: how it is ending, its errors, what its steps did. */\ninterface RunTally {\n ending: ActionRunEnding\n errors: string[]\n outcomes: string[]\n}\n\n/**\n * Folds one step's verdict into its run's tally, in the run-history phrasing\n * every automation shares; true when the run stops at this step.\n */\nfunction tallyStep(\n tally: RunTally,\n type: HostActionStepType,\n verdict: ServerStepVerdict,\n): boolean {\n switch (verdict.kind) {\n case 'skipped':\n return false\n case 'done':\n tally.outcomes.push(describeStepOutcome(type, verdict.detail))\n return false\n case 'failed':\n tally.errors.push(verdict.error)\n return false\n case 'exited':\n tally.ending = 'exited'\n tally.outcomes.push(describeStepOutcome(type))\n return true\n case 'waiting':\n tally.ending = 'waiting'\n tally.outcomes.push(describeStepOutcome(type, verdict.detail))\n return true\n case 'halted':\n tally.errors.push(verdict.error)\n return true\n case 'deferred':\n tally.ending = 'deferred'\n return true\n }\n}\n\n/**\n * Raises an event a step's write earned — a stage an automation set IS a\n * stage change, and whatever listens for one must hear it.\n *\n * Fanned out here, one level deeper under the same depth guard a\n * `customEvent` chain runs under, rather than through `emitHostEvent`, which\n * starts every chain at depth zero and would let an automation that causes\n * the event it listens for run forever. Workflows take the guard too.\n */\nasync function raiseEarnedEvent(\n env: ActionRunEnv,\n emit: { event: string; payload: Record<string, unknown> },\n): Promise<void> {\n const payload = emit.payload as HostEventPayload\n const [fromWorkflows, fromActions] = await Promise.all([\n // A step names any event a listener may hear: a declared one or a custom one.\n runEventWorkflows(env.hostId, emit.event as HostEventType, payload, env.depth + 1),\n runEventActions(env.hostId, emit.event, payload, env.depth + 1),\n ])\n env.alerts.push(...fromActions, ...fromWorkflows)\n}\n\n/**\n * Runs ONE Actions step on the server: the executor every automation shares.\n *\n * An action's step list runs through it one step at a time, and so does\n * every Actions step inside a workflow — so the two engines cannot disagree\n * about what `sendEmail` or `webhookPost` does, what a step is gated on, or\n * how its outcome reads in the run history. There is no second copy of any\n * branch below.\n *\n * Client-side steps (AGL-257) are skipped — the tenant page runtime runs\n * those in the visitor's browser.\n *\n * Never throws: a step that throws is a failed step, and the run continues.\n */\nasync function runServerStep(\n env: ActionRunEnv,\n run: AutomationRun,\n step: HostActionStep,\n context: ServerStepContext,\n): Promise<ServerStepVerdict> {\n const { hostId, hostRef, alerts, depth } = env\n const { event, payload, enrollmentRef } = context\n /**\n * The one fact worth carrying into the summary — the webhook's status, or\n * whatever a plugin's step answers. Set by the branch that knows it.\n */\n let detail: string | undefined\n const failed = (error: string): ServerStepVerdict => ({ kind: 'failed', error })\n try {\n /*\n * BRANCHING INSIDE A FLOW: the step's own condition, evaluated against\n * the same scope the trigger's is. An unmet guard skips this step and\n * only this step — the run continues, which is what makes \"wait three\n * days, then, only if they have not ordered, send the reminder\" a thing\n * an author can write without a second action.\n */\n if (!evaluateStepGuard(step.when, context.scope)) return { kind: 'skipped' }\n if (isClientActionStep(step) && step.type !== 'siteAlert') {\n return { kind: 'skipped' } // Runs in the visitor's page (AGL-257).\n }\n if (step.type === 'exitFlow') return { kind: 'exited' }\n if (isFlowSuspendingStep(step)) {\n // A run inside another automation's run finishes inside it: there is\n // no enrollment of its own for the person to wait in.\n if (context.nested) {\n return {\n kind: 'halted',\n error:\n 'a workflow run as a step of another automation cannot wait — ' +\n 'give it its own trigger instead',\n }\n }\n const suspended = await suspendFlow(env, run, {\n step,\n steps: context.steps,\n nextStepIndex: context.index + 1,\n event,\n payload,\n enrollmentRef,\n })\n if (suspended.error) return { kind: 'halted', error: suspended.error }\n return { kind: 'waiting', detail: suspended.detail }\n }\n if (step.type === 'siteAlert') {\n alerts.push({\n message: String(step.message ?? '').slice(0, 300),\n severity: step.severity ?? 'info',\n })\n } else if (step.type === 'runWorkflow') {\n const workflowContext = await env.loadWorkflowContext()\n const workflow =\n workflowContext.workflows[step.workflowId?.trim() ?? ''] ??\n workflowContext.workflows[step.workflowName?.trim() ?? '']\n if (!workflow) {\n return failed(`unknown workflow \"${step.workflowName || step.workflowId}\"`)\n }\n if (workflowHasActionSteps(workflow)) {\n /*\n * A workflow with Actions steps is PERFORMED, not evaluated: its\n * steps run here, inside this run, one level deeper under the same\n * guard a custom-event chain runs under. It is part of this run, so\n * it is not metered or recorded as a run of its own.\n */\n if (depth + 1 > ACTION_MAX_EVENT_DEPTH) {\n return failed(`workflow \"${workflow.name}\" is nested too deeply`)\n }\n const nested = await executeWorkflow(\n { ...env, depth: depth + 1 },\n {\n kind: 'workflow',\n id: workflow.$id ?? step.workflowId?.trim() ?? '',\n name: workflow.name ?? '',\n },\n workflow as AutomationWorkflow,\n event,\n payload,\n { nested: true },\n )\n if (nested.errors.length) {\n return failed(\n `workflow \"${workflow.name}\": ${nested.errors.join('; ')}`.slice(0, 300),\n )\n }\n } else {\n const evaluated = runWorkflow(\n workflow,\n workflowContext.functions,\n workflowContext.variables,\n { event, ...payload },\n { workflows: workflowContext.workflows },\n )\n if (evaluated.ok === false) return failed(evaluated.error)\n }\n } else if (step.type === 'customEvent') {\n const nested = await runEventActions(\n hostId,\n step.eventName.trim(),\n payload,\n depth + 1,\n )\n alerts.push(...nested)\n } else if (step.type === 'webhookPost') {\n if (!env.webhooksAllowed) return failed('webhooks require a Business plan')\n // Id-first lookup (AGL-261); the name query is the legacy path.\n const hookDoc = step.webhookId?.trim()\n ? await hostRef\n .collection('webhooks')\n .doc(step.webhookId.trim())\n .get()\n : (\n await hostRef\n .collection('webhooks')\n .where('name', '==', step.webhookName?.trim() ?? '')\n .limit(1)\n .get()\n ).docs[0]\n const hook = hookDoc?.exists\n ? (hookDoc.data() as HostWebhook)\n : undefined\n if (\n !hook ||\n hookDoc.get('deletedAt') ||\n hook.enabled === false ||\n hook.direction !== 'outbound' ||\n !hook.url\n ) {\n return failed(`unknown webhook \"${step.webhookName || step.webhookId}\"`)\n }\n const body = JSON.stringify({\n event,\n payload,\n sentAt: new Date().toISOString(),\n })\n const signature = hook.secret\n ? createHmac('sha256', hook.secret).update(body).digest('hex')\n : ''\n // Two quick retries — serverless-friendly; longer retry queues\n // are a follow-up.\n //\n // The hook's URL is tenant-typed and the step runs on any visitor's\n // form submission, so it goes out through the configured-URL fetch\n // (AGL-3363), never a bare `fetch`: https only, the name resolved to\n // PUBLIC addresses with the socket pinned to the one checked, and no\n // redirect followed — a 3xx is a failed delivery. That is what refuses\n // `[::1]`, `[::ffff:127.0.0.1]`, a decimal `2130706433`, a name that\n // resolves inward, and a public host answering `302` to the metadata\n // endpoint, none of which a pattern over the URL text can see.\n //\n // Loaded here, not at the top: see `webhook-delivery.ts`.\n const { describeConfiguredUrlRefusal, fetchConfiguredPublicUrl } =\n await import('./webhook-delivery')\n let delivered = false\n let lastStatus: number | undefined\n for (let attempt = 0; attempt < 3 && !delivered; attempt += 1) {\n try {\n const result = await fetchConfiguredPublicUrl(hook.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(signature && { 'X-Aglyn-Signature': signature }),\n },\n body,\n signal: AbortSignal.timeout(5000),\n })\n if ('refusal' in result) {\n // A refusal is the URL's, not the network's: retrying it would\n // only re-resolve the same answer.\n return failed(\n `webhook \"${step.webhookName || step.webhookId}\" refused: ${describeConfiguredUrlRefusal(result.refusal)}`,\n )\n }\n lastStatus = result.status\n delivered = result.status >= 200 && result.status < 300\n } catch {\n // Retry below.\n }\n if (!delivered && attempt < 2) {\n await new Promise((resolve) =>\n setTimeout(resolve, 500 * (attempt + 1)),\n )\n }\n }\n if (!delivered) {\n return failed(\n `webhook \"${step.webhookName || step.webhookId}\" delivery failed`,\n )\n }\n // The status the mockup prints — discarded on the line it arrived\n // until AGL-2171. A 200 and a 204 are both `ok`, and knowing which is\n // the whole reason anyone opens a run history after a webhook.\n detail = String(lastStatus ?? '')\n } else if (step.type === 'notifyAdmins') {\n await notifyHostManagers(hostId, {\n type: 'system.announcement',\n title: String(step.title ?? '').slice(0, 200),\n ...(step.body ? { body: String(step.body).slice(0, 500) } : {}),\n link: `/${hostId}`,\n })\n } else if (step.type === 'sendEmail') {\n const to = String(\n (payload as any)[step.toField?.trim() || 'email'] ?? '',\n ).trim()\n if (!isEmailConfigured()) return failed('email is not configured')\n if (!to || !to.includes('@')) {\n return failed('no recipient email in the event payload')\n }\n // The site's own origin, for the unsubscribe link. Read here rather\n // than carried on the run env because most action runs send no email\n // at all, and a document read every workflow pays for is a read on\n // the hot path for a link nine runs in ten never need.\n const hostData =\n ((await hostRef.get().catch(() => null))?.data() as\n | Record<string, unknown>\n | undefined) ?? null\n const siteBase = hostPublicOrigin(hostData as never) ?? ''\n /*\n * MARKETING, unless it is a transactional reply (below). The subject\n * and body are merchant-authored and the recipient comes out of the\n * event payload — which, for the collect route, is a write triggered\n * by an anonymous visitor. So a step that is not a reply is a site\n * mailing an address on the merchant's say-so, and it owes what every\n * other such message owes: the unsubscribe header pair and a visible\n * link, both suppression lists, and a share of the ceiling on how much\n * one person receives from this site.\n *\n * Priority stays transactional. An action run is not resumable — the\n * event has already happened and there is no beat that comes back for\n * it — and the rule on `'bulk'` is that only a resumable sweep may\n * refuse in a way the recipient survives.\n *\n * A merchant who wants an internal alert that no suppression can stop\n * uses the `notifyAdmins` step beside this one: it reaches managers\n * in the console rather than the shared sending domain, which is the\n * right instrument for a notification nobody consented to receive.\n */\n /*\n * A STEP THAT RUNS AFTER A WAIT IS A CAMPAIGN, not a reply.\n *\n * The paragraph above is exactly right about an IMMEDIATE step: the\n * event has already happened, the recipient just did something, and\n * the message is the response to it. None of that survives a three-day\n * delay. Everything after a wait goes out on the merchant's schedule,\n * to somebody who did one thing once — which is `marketing-send.ts`'s\n * own definition of marketing mail, and it earns the full consent\n * split and the default stream, exactly as a campaign does.\n *\n * An immediate step is not ungated, which it used to be. `to` is read\n * out of the event payload — an anonymous visitor's write on the\n * collect route — so nothing here establishes that whoever typed the\n * address is whoever receives the mail, and a person with a RECORDED\n * REFUSAL on this site was mailed merchant-authored content because a\n * third party entered their address in a form. `'immediate'` asks the\n * narrower question that catches exactly that and cannot refuse a\n * new visitor their auto-response: see `FlowEmailScope`.\n *\n * Refused BEFORE `sendEmail` rather than inside it, because these two\n * are the merchant's own policy over their own audience, where the\n * ones the seam asks are platform controls over the shared sending\n * domain. Both refusals are permanent for this message, so the\n * enrollment moves on rather than retrying.\n */\n const scope = enrollmentRef ? 'scheduled' : 'immediate'\n /*\n * A TRANSACTIONAL REPLY (AGL-3458) — the answer to what the recipient\n * just did: the run started on their own form submission, booking or\n * sign-up, this step runs at once, names no topic, and goes to the\n * address the event carries. Such a message is not a mailing, so it\n * leaves with no unsubscribe header and no unsubscribe link, and asks\n * no marketing-consent question — the reply the Inbox sends to a\n * submission is the same kind of message, and CAN-SPAM exempts both.\n * What it keeps is the suppression check below: a hard bounce, a spam\n * complaint or a person who asked this site to stop is not mailed by\n * anything. The author can switch a qualifying step back to a mailing\n * (`transactional: false`); a step that does not qualify is a mailing\n * whatever it says.\n */\n const transactional = sendEmailIsTransactionalReply(step, {\n event,\n afterWait: Boolean(enrollmentRef),\n })\n /*\n * The stream this message belongs to, resolved ONCE and read twice:\n * the gate below filters on it, and it rides the `marketing` context\n * so the opt-out link the seam mints names it. Without that the\n * preference page opens on a list of every stream the site has, and\n * the recipient has to find the one they were trying to leave.\n */\n const topicId = flowSubscriptionTopicId(step.topicId, scope)\n const consentGroup = consentGroupForHost(\n (env.org as Record<string, unknown> | null) ?? null,\n hostId,\n )\n if (transactional) {\n // Both suppression lists — the platform's bounces and complaints, and\n // this site's (and its consent group's) opt-outs — fail CLOSED.\n const sendable = await filterSendableForHost(hostId, [to], undefined, consentGroup)\n if (!sendable.length) return failed('the recipient is unsubscribed or suppressed')\n } else {\n const gate = await flowEmailRefusal({\n hostId,\n email: to,\n topicId: step.topicId ?? null,\n org: env.org,\n scope,\n })\n if (gate) {\n return failed(\n gate !== 'consent-withheld'\n ? 'the recipient has left this email topic'\n : // Named by what was actually asked. An immediate step refuses\n // only on a stated refusal, so reporting it as a missing\n // record would send a merchant looking for a consent field to\n // fill in that would change nothing.\n enrollmentRef\n ? 'the recipient has no marketing consent record on this site'\n : 'the recipient has declined marketing from this site',\n )\n }\n }\n /*\n * THE WORDS AS AUTHORED, and as sent (AGL-3458). The merge tags — the\n * campaign's `{{firstName|there}}` and the CRM's `{{contact.firstName}}`\n * — are filled from the contact or the lead the run is about, else from\n * the name and address the event carried. The phishing screen below\n * reads the AUTHORED words: it judges what the business wrote, once per\n * automation, not one copy per recipient.\n */\n const authoredSubject = String(step.subject ?? '').slice(0, 200)\n const authoredText = String(step.body ?? '').slice(0, 5000)\n const mergeContext = await stepEmailMergeContext({\n hostId,\n email: String(payload['email'] ?? '').trim() || to,\n contactGroupId: consentGroup.groupId,\n siteName: hostDisplayName(hostData ?? undefined, hostId),\n })\n const mergePerson = {\n email: to,\n name: String(payload['name'] ?? payload['fullName'] ?? '').trim(),\n }\n const emailSubject = resolveStepEmailMerge(authoredSubject, mergeContext, mergePerson)\n .text.replace(/[\\r\\n]+/g, ' ')\n .slice(0, 200)\n const emailText = resolveStepEmailMerge(authoredText, mergeContext, mergePerson).text.slice(\n 0,\n 5000,\n )\n /*\n * THE PHISHING SCREEN, for a workspace in its first fortnight\n * (AGL-3356). The incident's second message was exactly this step: a\n * `contactCreated` workflow mailing \"Poshmark Order … has finally\n * sold\" with a link to a Poshmark lookalike, once per imported contact.\n *\n * A hold files one review row per (automation, content) in the abuse\n * queue and stops the message. After a wait the step DEFERS, so the\n * enrollment sends the same step once staff release it; an immediate\n * step has no later beat to come back on, so this run records the hold\n * as its failure and the automation sends again from its next event\n * once released. See `outbound-send-review.ts`.\n */\n const screened = await screenOutboundSend({\n kind: run.kind,\n path:\n run.kind === 'orgAutomation'\n ? `orgs/${run.orgId ?? env.orgId ?? ''}/automations/${run.id}`\n : `hosts/${hostId}/${run.kind === 'workflow' ? 'workflows' : 'actions'}/${run.id}`,\n hostId,\n orgId: env.orgId,\n org: (env.org as Record<string, unknown> | null) ?? null,\n host: hostData,\n subject: authoredSubject,\n bodies: [authoredText],\n })\n if (screened.outcome === 'rejected') {\n return failed(\n `this email was rejected by staff review (${screened.reference})`,\n )\n }\n if (screened.outcome === 'held') {\n if (enrollmentRef) return { kind: 'deferred' }\n return failed(\n 'this email is held for staff review because it may impersonate ' +\n `another business (${screened.reference})`,\n )\n }\n /*\n * THE TIMELINE ENTRY (AGL-2615). A message addressed to the person the\n * event is about is filed on that person's timeline — the same entry a\n * member's own send files — so an automated welcome shows beside the\n * calls a rep made, with its delivery state. Whether the message earns\n * an entry, and where, is the record system's to say: it is offered\n * the message first, because the entry's tags have to ride the message\n * for the delivery webhook to find it, and files it only after the\n * provider accepted. No record system, or a message that earns no\n * entry, sends untagged.\n */\n const recordEmail = env.orgId\n ? await preparePluginRecordEmail({\n orgId: env.orgId,\n hostId,\n to,\n link: {\n ...(String(payload['contactId'] ?? '').trim()\n ? { contactId: String(payload['contactId']).trim() }\n : {}),\n ...(String(payload['email'] ?? '').trim()\n ? { email: String(payload['email']).trim() }\n : {}),\n },\n org: env.org,\n })\n : null\n // In the site's header and footer (AGL-3370): a workflow's email is the\n // site writing to its contact, in words the site owner typed.\n const framed = await renderSiteTextEmail(\n firebaseAdmin.app().firestore(),\n hostId,\n 'workflow-email',\n { subject: emailSubject, text: emailText },\n ).catch(() => null)\n const result = await sendEmail({\n to,\n subject: emailSubject,\n text: framed?.text || emailText,\n ...(framed?.html ? { html: framed.html } : {}),\n sendingIdentity: await hostSendingIdentity(hostId),\n ...(recordEmail ? { tags: [...recordEmail.tags] } : {}),\n audience: 'tenant',\n context: enrollmentRef ? 'flow step' : 'event action',\n // A step staff released above carries its release to the send\n // seam's screen, which would otherwise hold the same signals again.\n releasedReviewId:\n screened.outcome === 'send' ? (screened.releasedReviewId ?? null) : null,\n /*\n * A resumed step may take `'bulk'` where an immediate one may not,\n * and the reason is the same one the abandoned-checkout sweep gives:\n * only a RESUMABLE sender may be refused in a way the recipient\n * survives. The enrollment is the thing that makes it resumable —\n * a deferral below leaves the row waiting and the next beat sends\n * the same step to the same person.\n */\n ...(enrollmentRef ? { priority: 'bulk' as const } : {}),\n // `topicId` is `''` for a step that belongs to no stream, which\n // every reader of it treats as absent — see `flowSubscriptionTopicId`.\n // The consent group comes off the org the run already holds, so the\n // gate honors an unsubscribe from any site of a declared group —\n // the sender this site mails as — and the org's confirmation switch,\n // at no extra read. A transactional reply is not marketing and\n // carries none of it: no `List-Unsubscribe`, no opt-out line.\n ...(transactional\n ? {}\n : {\n marketing: {\n hostId,\n siteBase,\n topicId,\n consentHostIds: consentGroup.hostIds,\n consentAwaitsConfirmation: consentGroup.awaitsConfirmation,\n },\n }),\n })\n /*\n * DEFERRED IS NOT FAILED, and it is not SENT either.\n *\n * The platform's hourly ceiling and this person's own frequency window\n * are both refusals a later beat can pass. Advancing past this step\n * would turn \"not this hour\" into an email nobody ever receives, which\n * is the defect the campaign processor and the cart sweep each name.\n * So the enrollment is put back with the SAME `nextStepIndex` and the\n * run ends here.\n */\n if (enrollmentRef && isDeferrableSendResult(result)) {\n return { kind: 'deferred' }\n }\n // Named rather than lumped into \"delivery failed\": a suppression and\n // a frequency ceiling are the controls working, and a merchant\n // reading the run's alerts has a different thing to do about each.\n const refusal = sendFailureReason(result)\n const sendError = refusal\n ? refusal === 'suppressed'\n ? 'the recipient is unsubscribed or suppressed'\n : refusal === 'frequency-capped'\n ? 'the recipient has already had today’s limit of email ' +\n 'from this site'\n : refusal === 'held-for-review'\n ? 'this email is held for staff review because it may ' +\n 'impersonate another business'\n : 'email delivery failed'\n : null\n // Cost meter (AGL-1438). A workflow notification is transactional:\n // counted, never capped. `sent` is false when Resend refused or the\n // environment is unconfigured, and an email that never left is not a\n // cost.\n if (result.sent) {\n await meterHostEmail(hostId)\n await recordEmail?.file({\n subject: emailSubject,\n body: emailText,\n to,\n sourceRef: run.id,\n })\n }\n if (sendError) return failed(sendError)\n } else if (step.type === 'enrollList') {\n const orgId = await resolveOrgIdForHost(hostId)\n const email = String((payload as any).email ?? '')\n .trim()\n .toLowerCase()\n if (!orgId || !email || !email.includes('@')) {\n return failed('no email to enroll')\n }\n const listsRef = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection('lists')\n const listDoc = step.listId?.trim()\n ? await listsRef.doc(step.listId.trim()).get()\n : (\n await listsRef\n .where('name', '==', step.listName?.trim() ?? '')\n .limit(1)\n .get()\n ).docs[0]\n if (!listDoc?.exists) {\n return failed(`unknown list \"${step.listName || step.listId}\"`)\n }\n // `enrollListMember` owns the document id: the commerce newsletter\n // handler enrolls into the same collection, and an id derived here\n // would be a second answer to which document describes which person.\n await enrollListMember({\n listRef: listDoc.ref,\n group: await consentGroupForSite(hostId),\n email,\n // Which automation enrolled them: `action:<id>` or `workflow:<id>`.\n source: `${run.kind}:${run.id}`,\n })\n } else if (step.type === 'assignCampaign') {\n const email = String((payload as any).email ?? '')\n .trim()\n .toLowerCase()\n if (!email || !email.includes('@')) {\n return failed('no contact email to assign')\n }\n // Scoped to this host (AGL-1039): a site must not reach a contact\n // it cannot see, even to tag it onto a campaign. Asked of the plugin\n // that keeps people (AGL-3080), whose lookup answers an address a\n // merge folded into another record (AGL-2633) with the person who now\n // holds it.\n const contact = await findPluginPerson({ hostId, email, onlyVisibleToSite: true })\n if (!contact) return failed(`no contact for ${email}`)\n /*\n * THE CAMPAIGN IS RESOLVED TO A DOCUMENT, exactly as `enrollList`\n * resolves a list one branch above.\n *\n * A step may name a campaign by id or by name — the picker writes the\n * id, an imported automation may carry only the name — and what gets\n * stored is the id either way. Storing whichever of the two the step\n * happened to hold would put names and ids in one array, and every\n * reader of that array resolves ids: a name in it renders as a chip\n * nobody can click and matches no campaign the console can find.\n *\n * An unknown campaign is an ERROR rather than a stored string. The\n * reference audit already reports a step pointing at a campaign that\n * does not exist; a run that wrote the dangling name anyway would\n * make the audit's finding untrue the moment it fired.\n *\n * The containers are the ORGANIZATION's, and this run is one site's,\n * so a campaign counts only while it is live and placed on this site\n * (`visibleTo` holds `org` or `host:{hostId}`) — the same set the\n * site's picker offers. One the console deleted, or one placed only\n * on a sibling site, is refused like a missing one: filing a person\n * under it would put them in a campaign nobody at this site can open.\n * A name lookup reads a few matches rather than one, so a deleted or\n * sibling campaign sharing the name cannot shadow the live one.\n */\n const campaignLabel = step.campaignName || step.campaignId\n const campaignOrgId = env.orgId ?? (await resolveOrgIdForHost(hostId))\n if (!campaignOrgId) return failed(`unknown campaign \"${campaignLabel}\"`)\n // The org's containers of the `campaign` kind, read where the kind's\n // declaration says they are stored.\n const containerStore = firebaseAdmin.app().firestore()\n const usable = (container: OrgContainerRecord | undefined) =>\n Boolean(container?.live && visibleToHost(container.visibleTo, hostId))\n const namedId = step.campaignId?.trim() ?? ''\n const campaign = namedId\n ? (await readOrgContainers(containerStore, 'campaign', campaignOrgId, [namedId]))[0]\n : (\n await findOrgContainersByName(\n containerStore,\n 'campaign',\n campaignOrgId,\n step.campaignName?.trim() ?? '',\n 10,\n )\n ).find(usable)\n if (!campaign?.exists) {\n return failed(`unknown campaign \"${campaignLabel}\"`)\n }\n if (!campaign.live) {\n return failed(`campaign \"${campaignLabel}\" was deleted`)\n }\n if (!usable(campaign)) {\n return failed(`campaign \"${campaignLabel}\" is not placed on this site`)\n }\n /*\n * Filed by the plugin that keeps people, as THIS SITE holds them\n * (AGL-3080): which campaigns a merchant has filed somebody under is\n * that merchant's business record, on the same footing as their notes\n * and their tags, and where it lives on the person is the owner's to\n * know. A person the owner no longer finds for the site is not filed.\n */\n const filed = await filePluginPersonUnder({\n hostId,\n orgId: campaignOrgId,\n record: { kind: contact.kind, id: contact.id },\n containerKind: 'campaign',\n ids: [campaign.id],\n })\n if (!filed?.filed) return failed(`no contact for ${email}`)\n } else {\n /*\n * A STEP ANOTHER PLUGIN RUNS: one that writes that plugin's records,\n * handed to the executor it registered (`plugin-server-steps`). The\n * guard, the order, the history and the nesting cap stay here; the\n * write is the owner's. A declared step whose executor is missing\n * throws, and is this step's failure. A type nobody runs answers\n * `null` and reads as done, as an unknown type always has.\n */\n const executor = await pluginServerStepExecutor(step.type)\n if (executor) {\n const answer = await executor.run({\n hostId,\n org: env.org,\n orgId: env.orgId,\n run: { kind: run.kind, id: run.id, name: run.name },\n event,\n payload: payload as Record<string, unknown>,\n step: step as { type: string; [field: string]: unknown },\n })\n if (answer.error) return failed(answer.error)\n detail = answer.detail\n if (answer.emit) await raiseEarnedEvent(env, answer.emit)\n }\n }\n } catch (error) {\n return failed((error as Error).message)\n }\n return { kind: 'done', detail }\n}\n\n/**\n * Executes one action's SERVER steps in order, collecting per-step errors\n * into the activity summary. Each step runs through {@link runServerStep}.\n *\n * An org automation's run comes through here too (AGL-3302): on the site the\n * event happened on, against that site's run environment, so its email goes\n * from that site and its CRM writes land in that site's facet.\n *\n * A `wait` step ENDS this call and hands the rest of the list to the job\n * beat: everything after the wait belongs to a run that has not happened yet,\n * so nothing below the wait may execute in this request.\n */\nexport async function executeAction(\n env: ActionRunEnv,\n actionId: string,\n action: Pick<HostAction, 'name' | 'steps'>,\n event: string,\n payload: HostEventPayload,\n options: ExecuteActionOptions = {},\n): Promise<ActionRunEnding> {\n const { hostRef } = env\n const orgRun = options.orgAutomation ?? null\n const run: AutomationRun = orgRun\n ? {\n kind: 'orgAutomation',\n id: actionId,\n name: action.name ?? '',\n orgId: orgRun.orgId,\n }\n : {\n kind: 'action',\n id: actionId,\n name: action.name ?? '',\n }\n const enrollmentRef = options.enrollmentRef ?? null\n const steps = (options.steps ?? action.steps ?? []).slice(0, ACTION_MAX_STEPS)\n const startIndex = Math.max(0, options.startIndex ?? 0)\n /**\n * What each step actually DID (AGL-2171). Only failures were recorded,\n * so a run that sent an email, wrote a row and posted a webhook logged\n * the same eight words as a run that did nothing — and\n * `/product/workflows` advertises a `What happened` column reading\n * `Sent email · saved to Leads · webhook 200`.\n */\n const tally: RunTally = { ending: 'ran', errors: [], outcomes: [] }\n const scope = { event, ...payload }\n for (let index = startIndex; index < steps.length; index += 1) {\n const step = steps[index] as HostActionStep\n /*\n * An org automation runs its own vocabulary and nothing else. The save\n * route refuses the rest, so this answers only a document written around\n * it — and answers it before the step can reach this site's workflows or\n * webhooks, which are not the organization's to call.\n */\n const refusal = orgRun ? orgAutomationStepRefusal(step) : null\n if (refusal) {\n tally.errors.push(refusal)\n continue\n }\n const verdict = await runServerStep(env, run, step, {\n index,\n steps,\n event,\n payload,\n scope,\n enrollmentRef,\n })\n if (tallyStep(tally, step.type, verdict)) break\n }\n const { ending, errors: stepErrors, outcomes } = tally\n /*\n * A DEFERRED run writes no history line and leaves the enrollment where it\n * was. Nothing happened that a merchant should read as a run: the step is\n * still ahead of this person, and a row per refused beat would bury the\n * runs that did something under a log of the ceiling working.\n */\n if (ending === 'deferred') return ending\n const noun = orgRun ? 'Org automation' : 'Action'\n const summary = stepErrors.length\n ? `${noun} ran on ${event} with errors: ${stepErrors.join('; ')}`.slice(\n 0,\n 300,\n )\n : ending === 'waiting'\n ? `${noun} is waiting, on ${event}`\n : `${noun} ran on ${event}`\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n // The prose line stays exactly as it was: `activityPrimaryText` and\n // three other renderers read it, and the run table is not the only\n // thing this collection feeds.\n action: summary,\n // The structured half (AGL-2171) — the two columns the advertised\n // run-history table could not otherwise fill.\n result: stepErrors.length ? 'failed' : 'succeeded',\n trigger: event,\n // The summary and its search tokens, written together (AGL-3321).\n ...runSummaryFields(\n (outcomes.length ? outcomes.join(' · ') : 'Ran').slice(0, 300),\n ),\n target: {\n // An org automation's run is filed under its own type, so the site's\n // run history can tell it from a site action sharing the feed.\n type: orgRun ? ORG_AUTOMATION_RUN_TARGET : 'workflow',\n id: actionId,\n name: action.name ?? '',\n },\n // The site log's search finds a run by its action's name (AGL-3321).\n searchTokens: activitySearchTokens({ target: { name: action.name } }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n return ending\n}\n\n/** How a workflow run went, for whoever records or answers for it. */\nexport interface WorkflowExecution {\n ending: ActionRunEnding\n /**\n * Every error, in step order. An Actions step's error is recorded and the\n * run goes on, as it does in an action; a function call's error ENDS the\n * run, as it always has, because the steps after it read its result.\n */\n errors: string[]\n /** What each step that ran did, in the Actions run-history phrasing. */\n outcomes: string[]\n /** The workflow's return value, as the pure evaluator reports it. */\n value: number | string | boolean\n /** Every result the function calls bound, by name. */\n results: Record<string, number | string | boolean>\n}\n\ninterface ExecuteWorkflowOptions {\n /** The step to start at: a resume's `nextStepIndex`. */\n startIndex?: number\n /** The list to run: a resume's snapshot. */\n steps?: readonly WorkflowStep[]\n /** The enrollment a resume belongs to. */\n enrollmentRef?: FirebaseFirestore.DocumentReference | null\n /** A run started by another automation's `runWorkflow` step. */\n nested?: boolean\n}\n\n/**\n * Runs a workflow whose steps include Actions steps: function calls and\n * Actions steps, in order, in one scope.\n *\n * - A FUNCTION CALL is evaluated by the platform's pure evaluator,\n * `runWorkflow`, one call at a time, so its expressions see the event, the\n * site's variables and every result bound before it — exactly the scope a\n * function-only workflow gives it. A call that fails ends the run.\n * - An ACTIONS STEP runs through {@link runServerStep}, the executor actions\n * use, and reads the event payload together with every result bound so\n * far: a workflow can compute a score and write it to a dataset. Each one\n * takes the Actions tier gate (`actions`, Pro and up) on top of its own —\n * `webhookPost` on Business, the CRM steps on the CRM suite — and a step\n * only the visitor's browser can run is refused.\n * - `wait` and `waitForEvent` enroll the person and end this call; the rest\n * of the list, results included, continues from the beat. `exitFlow` ends\n * the run.\n *\n * Records nothing and meters nothing: the caller that admitted the run does\n * both, once.\n */\nexport async function executeWorkflow(\n env: ActionRunEnv,\n run: AutomationRun,\n workflow: AutomationWorkflow,\n event: string,\n payload: HostEventPayload,\n options: ExecuteWorkflowOptions = {},\n): Promise<WorkflowExecution> {\n const steps = options.steps ?? workflow.steps ?? []\n const tally: RunTally = { ending: 'ran', errors: [], outcomes: [] }\n const results: Record<string, number | string | boolean> = {}\n if (steps.length > WORKFLOW_MAX_STEPS) {\n return {\n ...tally,\n errors: [`Workflows are capped at ${WORKFLOW_MAX_STEPS} steps`],\n value: '',\n results,\n }\n }\n const context = await env.loadWorkflowContext()\n const enrollmentRef = options.enrollmentRef ?? null\n const startIndex = Math.max(0, options.startIndex ?? 0)\n /** The payload an Actions step reads: the event's, then every result. */\n const stepPayload = (): HostEventPayload => ({ ...payload, ...results })\n for (let index = startIndex; index < steps.length; index += 1) {\n const step = steps[index]\n if (!isWorkflowActionStep(step)) {\n const call = step as HostWorkflowStep\n const resultName = call.resultName?.trim() || `step${index + 1}`\n const evaluated = runWorkflow(\n { name: workflow.name, steps: [{ ...call, resultName }] },\n context.functions,\n context.variables,\n { event, ...stepPayload() },\n { workflows: context.workflows },\n )\n if (evaluated.ok === false) {\n // The evaluator numbers the one step it was handed; the author\n // numbers it by its place in the workflow.\n tally.errors.push(\n evaluated.error.replace(/^Step 1\\b/, `Step ${index + 1}`),\n )\n break\n }\n results[resultName] = evaluated.results[resultName]\n tally.outcomes.push(\n `ran ${String(call.functionName || call.functionId || 'a function').trim()}`,\n )\n continue\n }\n const refusal = workflowActionStepRefusal(step)\n if (refusal) {\n tally.errors.push(refusal)\n continue\n }\n if (!env.actionsAllowed) {\n tally.errors.push(\n `“${workflowStepTypeLabel(step.type)}” needs the ` +\n `${planLabelGrantingFeature('actions')} plan`,\n )\n continue\n }\n const verdict = await runServerStep(env, run, step, {\n index,\n steps,\n event,\n payload: stepPayload(),\n scope: { event, ...stepPayload() },\n enrollmentRef,\n nested: options.nested,\n })\n if (tallyStep(tally, step.type, verdict)) break\n }\n /*\n * The return value the pure evaluator would give: the named scope entry —\n * a result, a payload field or a variable — when the workflow names one,\n * and the last result otherwise. Asked of the evaluator itself, with no\n * steps, so a variable resolves by the evaluator's own rules.\n */\n const returnName = workflow.returnValue?.trim()\n const value = returnName\n ? (() => {\n const named = runWorkflow(\n { name: workflow.name, steps: [], returnValue: returnName },\n context.functions,\n context.variables,\n { event, ...stepPayload() },\n )\n return named.ok === false ? '' : named.value\n })()\n : (Object.values(results).at(-1) ?? '')\n return { ...tally, value, results }\n}\n\n/**\n * Suspends the run at a `wait` or `waitForEvent` step.\n *\n * One function for both the first suspension and every later one, because the\n * two differ only in whether a row already exists — and writing \"create here,\n * update there\" twice is how the two drift into disagreeing about which\n * fields a waiting enrollment carries.\n */\nasync function suspendFlow(\n env: ActionRunEnv,\n run: AutomationRun,\n request: {\n step: HostActionStep\n steps: readonly WorkflowStep[]\n nextStepIndex: number\n event: string\n payload: HostEventPayload\n enrollmentRef: FirebaseFirestore.DocumentReference | null\n },\n): Promise<{ error?: string; detail?: string }> {\n const { step } = request\n const minutes =\n step.type === 'wait'\n ? Number(step.delayMinutes)\n : step.type === 'waitForEvent'\n ? Number(step.timeoutMinutes)\n : 0\n if (!Number.isFinite(minutes) || minutes <= 0) {\n return { error: 'the wait has no duration' }\n }\n const nowMs = Date.now()\n const resumeAtMs = nowMs + minutes * 60_000\n const awaitingEvent =\n step.type === 'waitForEvent' ? step.eventName.trim() : null\n const detail =\n step.type === 'waitForEvent'\n ? `${awaitingEvent} (up to ${minutes}m)`\n : `${minutes}m`\n\n if (request.enrollmentRef) {\n await advanceFlowEnrollment(\n request.enrollmentRef,\n {\n nextStepIndex: request.nextStepIndex,\n resumeAtMs,\n awaitingEvent,\n payload: request.payload as Record<string, unknown>,\n },\n nowMs,\n )\n return { detail }\n }\n\n const enrolled = await enrollInFlow({\n hostId: env.hostId,\n actionId: run.id,\n // Absent for an action, which is every enrollment written before a\n // workflow could wait — the resume reads absent as `action`.\n ...(run.kind === 'workflow' ? { automation: 'workflow' as const } : {}),\n // An org automation waits on the site it ran on, under its own id prefix,\n // and names the organization whose document the resume re-reads.\n ...(run.kind === 'orgAutomation'\n ? { automation: 'org' as const, orgId: run.orgId ?? env.orgId ?? '' }\n : {}),\n // The SNAPSHOT is the list this run is executing, which on a first run is\n // the automation's own — so one edited later cannot move this person's\n // remaining steps out from under them.\n action: { name: run.name, steps: [...request.steps] },\n email: String((request.payload as any)?.['email'] ?? ''),\n event: request.event,\n payload: (request.payload ?? {}) as Record<string, unknown>,\n nextStepIndex: request.nextStepIndex,\n resumeAtMs,\n awaitingEvent,\n nowMs,\n })\n if (enrolled.enrolled === true) return { detail }\n /*\n * Both refusals are stated rather than swallowed, because both are things\n * an author can act on: a flow that waits needs an address in its trigger\n * payload, and a person already inside this flow is not put through it\n * twice concurrently.\n */\n return {\n error:\n enrolled.reason === 'no-person'\n ? 'a flow that waits needs the person’s email in the event payload'\n : 'this person is already waiting inside this flow',\n }\n}\n\n/**\n * Events too frequent to log a skip for.\n *\n * `runEventActions` fires on EVERY page view of every published site. A\n * Firestore write per visitor per non-matching action is not a run\n * history, it is an outage — and a page-view condition is one an author\n * tunes by watching the site, not by reading a log. The events people\n * actually debug are the server-emitted ones (a form submission, a\n * booking, a sign-up), and those are low-volume by construction.\n */\nexport const SKIP_LOG_EXCLUDED_EVENTS: ReadonlySet<string> = new Set(['pageView'])\n\n/**\n * Whether an action's unmet conditions only say it is ANOTHER FORM'S\n * (AGL-3458) — the case that is noise rather than an answer.\n *\n * A site with one auto-reply per form keyed on `formId equals …` would write\n * a `Skipped` row for every other form's reply on every submission: eight\n * rows a fill on a nine-form site, burying the one row that explains a real\n * miss. A form picked by id cannot be mistyped, so its mismatch against a\n * submission that names a DIFFERENT form says only \"not this form\", and it\n * is not recorded. Every other unmet condition still is: a hand-typed\n * `formName`, a field condition, a submission that names no form at all.\n *\n * With `and`, one such clause is the whole answer; with `or`, only when every\n * clause is about which form it was — `formId` or `formName` equals — since\n * any other clause could have been the one that was meant to match.\n */\nexport function conditionsNameAnotherForm(\n trigger: HostAction['trigger'] | null | undefined,\n payload: HostEventPayload,\n): boolean {\n const submitted = String(payload['formId'] ?? '').trim()\n if (!submitted) return false\n const clauses = normalizeTriggerConditions(trigger)\n const another = (clause: { field?: string; op?: string; value?: string }) =>\n clause.field?.trim() === 'formId' &&\n clause.op === 'equals' &&\n Boolean(clause.value?.trim()) &&\n clause.value?.trim() !== submitted\n if (trigger?.combinator === 'or') {\n return (\n clauses.some(another) &&\n clauses.every(\n (clause) =>\n clause.op === 'equals' && ['formId', 'formName'].includes(clause.field?.trim() ?? ''),\n )\n )\n }\n return clauses.some(another)\n}\n\n/**\n * Writes the `Skipped` row (AGL-2171): which condition stopped the run,\n * so the answer to \"why didn't it fire?\" is in the same place as every\n * run that did.\n *\n * Never counts against `actionRunsPerMonth` — nothing executed, and\n * charging for a condition that said no would be its own bug.\n */\nasync function recordSkippedRun(\n hostRef: FirebaseFirestore.DocumentReference,\n actionId: string,\n action: Pick<HostAction, 'name' | 'trigger'>,\n event: string,\n kind: 'action' | 'orgAutomation' = 'action',\n /** What stopped it, when it was not an unmet condition. */\n because?: string,\n): Promise<void> {\n if (SKIP_LOG_EXCLUDED_EVENTS.has(event)) return\n // `normalizeTriggerConditions`, not `trigger.conditions` — a pre-AGL-565\n // action carries a single `condition` and reading only the array would\n // give every one of them the nameless fallback.\n const named = normalizeTriggerConditions(action.trigger)\n .map((condition) => String(condition?.field ?? '').trim())\n .filter(Boolean)\n const reason =\n because ??\n (named.length\n ? `Condition on ${named.slice(0, 3).join(', ')} not met`\n : 'Trigger condition not met')\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n action: `${kind === 'orgAutomation' ? 'Org automation' : 'Action'} skipped on ${event}`,\n result: 'skipped',\n trigger: event,\n ...runSummaryFields(reason.slice(0, 300)),\n target: {\n type: kind === 'orgAutomation' ? ORG_AUTOMATION_RUN_TARGET : 'workflow',\n id: actionId,\n name: action.name ?? '',\n },\n searchTokens: activitySearchTokens({ target: { name: action.name } }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n}\n\n/**\n * Event-triggered action runner (AGL-148): loads enabled actions whose\n * `trigger.event` matches (built-in, site event, or custom), evaluates\n * optional filters over the payload, and executes each step list in\n * order. Never throws into the emitting request. Paid feature: the\n * `actions` flag gates and `actionRunsPerMonth` meters runs.\n *\n * The organization's automations placed on this site run here too\n * (AGL-3302), after the site's own, on the same run environment — they run\n * AS this site — and on the same meter.\n */\nexport async function runEventActions(\n hostId: string,\n event: string,\n payload: HostEventPayload = {},\n depth = 0,\n): Promise<HostActionAlert[]> {\n const alerts: HostActionAlert[] = []\n if (depth > ACTION_MAX_EVENT_DEPTH) return alerts\n /*\n * A flow waiting for THIS event, for THIS person, resumes here.\n *\n * Above the action lookup and outside its try, because the two are\n * unrelated: an event that matches no action can still be the one a flow\n * has been waiting a week for, and a site with no actions at all can hold\n * enrollments from an action that has since been rewritten.\n */\n await wakeFlowsAwaitingEvent(hostId, event, payload).catch(() => undefined)\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const actions = await liveActionsForEvent(hostRef, event)\n /*\n * THE ORGANIZATION'S AUTOMATIONS placed on this site and not paused here.\n *\n * Asked only for an org trigger — a server event a person is the subject\n * of — so a page view, a custom event and every on-page site event cost\n * nothing more than they did. Never throws: an org lookup that fails runs\n * this site's own actions exactly as before.\n */\n const placed = await findOrgAutomationsForEvent(hostId, event)\n if (!actions.length && !placed) return alerts\n\n // Webhook steps take the higher `webhooks` gate (AGL-149); plan gates\n // ride the owning org's doc (AGL-238).\n let webhooksAllowed = true\n /*\n * Whether each half fits under the month's allowance — all or nothing per\n * half, as the site's own actions always were.\n *\n * The site's actions are asked first and on exactly the question they\n * were always asked, so an organization placing automations on a site can\n * never be what stops that site's own actions running. The org half is\n * asked second, against what is left after the site's half, and both\n * count on this site's meter: an org automation's run is this site's\n * run, and the usage card, the usage alerts and the COGS rollup read it\n * there.\n */\n let runSite = false\n let runOrg = false\n // Plan-less orgs resolve as free (AGL-247) — gates always run. Held for\n // the rest of the run so a step's own plan check costs no second read.\n const owner = await getOrgForHost(hostId)\n // The band is the WORKSPACE's (AGL-3472): every site's runs count\n // against it, read off the org's counter — see `run-meter.ts`.\n const meter: RunMeterScope = {\n firestore,\n hostRef,\n orgId: owner?.orgId,\n counter: 'actionRuns',\n month: runMonthKey(),\n }\n // A suspended site runs nothing (AGL-3356): see `eventRunSuspension`.\n if (await eventRunSuspension(hostRef, owner?.org)) return alerts\n {\n const org = owner?.org\n if (!checkEntitlement(org as any, 'actions')) return alerts\n webhooksAllowed = checkEntitlement(org as any, 'webhooks')\n const limit = resolveOrgEntitlements(\n org as any,\n ).actionRunsPerMonth\n const used = await runsUsedThisMonth(meter)\n // Asked as \"would it go over\", the question this gate has always\n // asked — so a counter that reads as no number refuses nothing, as\n // before, rather than refusing everything.\n runSite = actions.length > 0 && !(used + actions.length > limit)\n /*\n * Only for the organization the site belongs to NOW: the automations\n * were found through the site's org, and a site that changed hands\n * between the two reads runs none of its old organization's.\n */\n runOrg =\n Boolean(placed) &&\n placed?.orgId === owner?.orgId &&\n !(\n used + (runSite ? actions.length : 0) + (placed?.docs.length ?? 0) >\n limit\n )\n if (!runSite && !runOrg) return alerts\n }\n\n const env: ActionRunEnv = {\n hostId,\n hostRef,\n alerts,\n // Admitted by the `actions` gate above.\n actionsAllowed: true,\n webhooksAllowed,\n depth,\n org: owner?.org ?? null,\n orgId: owner?.orgId ?? null,\n loadWorkflowContext: makeWorkflowContextLoader(hostRef),\n }\n\n let executed = 0\n for (const doc of runSite ? actions : []) {\n const action = doc.data() as HostAction\n const filter = action.trigger?.filter?.trim()\n if (filter) {\n const unrunnable = triggerFilterProblem(filter)\n if (unrunnable) {\n await recordSkippedRun(hostRef, doc.id, action, event, 'action', unrunnable)\n continue\n }\n try {\n if (!evaluateExpression(filter, { event, ...payload })) continue\n } catch {\n continue // A field the filter names is not on this event.\n }\n }\n // Structured payload conditions (AGL-557; AND/OR chaining AGL-565):\n // same scope as the filter; unmet conditions skip the action and\n // never count as a run against the quota.\n if (!evaluateTriggerConditions(action.trigger, { event, ...payload })) {\n // …but they are RECORDED now (AGL-2171). This was a bare\n // `continue`, so \"why didn't my automation fire?\" — the most\n // common support question about automations — had no answer\n // anywhere in the product, while `/product/workflows` advertises\n // an amber `Skipped` row that answers it.\n if (!conditionsNameAnotherForm(action.trigger, payload)) {\n await recordSkippedRun(hostRef, doc.id, action, event)\n }\n continue\n }\n executed += 1\n await executeAction(env, doc.id, action, event, payload)\n }\n /*\n * The org automations, judged by the same filter and conditions a site\n * action is, skipped and recorded the same way, and run on this site.\n */\n for (const doc of runOrg && placed ? placed.docs : []) {\n const automation = doc.data() as OrgAutomation\n const filter = automation.trigger?.filter?.trim()\n if (filter) {\n const unrunnable = triggerFilterProblem(filter)\n if (unrunnable) {\n await recordSkippedRun(hostRef, doc.id, automation, event, 'orgAutomation', unrunnable)\n continue\n }\n try {\n if (!evaluateExpression(filter, { event, ...payload })) continue\n } catch {\n continue // A field the filter names is not on this event.\n }\n }\n if (\n !evaluateTriggerConditions(automation.trigger, { event, ...payload })\n ) {\n if (!conditionsNameAnotherForm(automation.trigger, payload)) {\n await recordSkippedRun(hostRef, doc.id, automation, event, 'orgAutomation')\n }\n continue\n }\n executed += 1\n await executeAction(env, doc.id, automation, event, payload, {\n orgAutomation: { orgId: placed?.orgId ?? '' },\n })\n }\n await recordRuns({ ...meter, count: executed })\n } catch (error) {\n console.error('runEventActions failed', hostId, event, error)\n }\n return alerts\n}\n\n/**\n * Why a dispatched action ran nothing (AGL-3309).\n *\n * A page's dispatch has nobody to tell: a visitor's scroll is not answered\n * with \"the plan lapsed\", so for it every refusal is the same empty list of\n * alerts. The console's test run does have somebody to tell, and a test that\n * ran nothing must not read as one that ran.\n */\nexport type SingleActionSkip =\n // No such action, or it was deleted.\n | 'missing'\n // Switched off.\n | 'disabled'\n // The dispatch named an event other than the action's trigger.\n | 'event'\n // The trigger's conditions are not met by the payload.\n | 'conditions'\n // The site's plan does not carry the `actions` feature.\n | 'plan'\n // The month's action runs are spent.\n | 'allowance'\n // A read before the first step threw; it was logged. Steps never throw\n // (`runServerStep` records a throw as a failed step), so nothing ran.\n | 'failed'\n\n/** What one dispatched action did, for a caller that has to say so. */\nexport interface SingleActionOutcome {\n /** Its steps ran, and the run counted on the site's and workspace's meters. */\n ran: boolean\n /** Why nothing ran; `null` when it ran. */\n skipped: SingleActionSkip | null\n /** The site alerts its steps produced. */\n alerts: HostActionAlert[]\n /** The month's allowance, when the allowance is what refused. */\n limit?: number\n}\n\n/**\n * Runs ONE action's server steps (AGL-256): the tenant page runtime\n * evaluates site-event trigger conditions (scroll thresholds, selectors)\n * client-side and dispatches the specific action here — re-matching by\n * event name would wrongly fire sibling actions with different\n * thresholds. Same gates and metering as the event runner.\n */\nexport async function runSingleAction(\n hostId: string,\n actionId: string,\n event: string,\n payload: HostEventPayload = {},\n): Promise<HostActionAlert[]> {\n return (await runSingleActionOutcome(hostId, actionId, event, payload)).alerts\n}\n\n/**\n * {@link runSingleAction}, answering what it did as well as the alerts: whether\n * the steps ran and, when they did not, which gate stopped them.\n *\n * One body for both, so the console's test run meets exactly the gates a\n * page's dispatch meets — the switch, the event, the conditions, the plan and\n * the month's allowance — and is metered and recorded the same way.\n */\nexport async function runSingleActionOutcome(\n hostId: string,\n actionId: string,\n event: string,\n payload: HostEventPayload = {},\n): Promise<SingleActionOutcome> {\n const alerts: HostActionAlert[] = []\n const skipped = (\n reason: SingleActionSkip,\n limit?: number,\n ): SingleActionOutcome => ({\n ran: false,\n skipped: reason,\n alerts,\n ...(limit === undefined ? {} : { limit }),\n })\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const doc = await hostRef.collection('actions').doc(actionId).get()\n if (!doc.exists || doc.get('deletedAt')) return skipped('missing')\n if (doc.get('enabled') === false) return skipped('disabled')\n const action = doc.data() as HostAction\n // Only site-event actions may be dispatched externally — server\n // events flow through their own emitters.\n if (String(action.trigger?.event ?? '') !== String(event)) {\n return skipped('event')\n }\n // Structured payload conditions (AGL-557; AND/OR chaining AGL-565):\n // the single-action dispatch path honors them too, so\n // client-evaluated triggers can't bypass them.\n if (!evaluateTriggerConditions(action.trigger, { event, ...payload })) {\n return skipped('conditions')\n }\n\n let webhooksAllowed = true\n // Held for the rest of the run, as in `runEventActions` above.\n const owner = await getOrgForHost(hostId)\n // The workspace's band, as in `runEventActions` above (AGL-3472).\n const meter: RunMeterScope = {\n firestore,\n hostRef,\n orgId: owner?.orgId,\n counter: 'actionRuns',\n month: runMonthKey(),\n }\n {\n const org = owner?.org\n if (!checkEntitlement(org as any, 'actions')) return skipped('plan')\n webhooksAllowed = checkEntitlement(org as any, 'webhooks')\n const limit = resolveOrgEntitlements(\n org as any,\n ).actionRunsPerMonth\n const used = await runsUsedThisMonth(meter)\n if (used + 1 > limit) return skipped('allowance', limit)\n }\n\n const env: ActionRunEnv = {\n hostId,\n hostRef,\n alerts,\n // Admitted by the `actions` gate above.\n actionsAllowed: true,\n webhooksAllowed,\n depth: 0,\n org: owner?.org ?? null,\n orgId: owner?.orgId ?? null,\n loadWorkflowContext: makeWorkflowContextLoader(hostRef),\n }\n await executeAction(env, doc.id, action, event, payload)\n await recordRuns({ ...meter, count: 1 })\n return { ran: true, skipped: null, alerts }\n } catch (error) {\n console.error('runSingleAction failed', hostId, actionId, error)\n return skipped('failed')\n }\n}\n\n/**\n * Ends an enrollment its automation may no longer run, and says why in the\n * run history — the automation was deleted or switched off, or the plan that\n * carried it lapsed. Shared by every kind of enrollment, so \"stopped mid-wait\"\n * reads the same for an action, a workflow and an org automation.\n */\nexport async function stopFlowEnrollment(\n enrollment: FlowEnrollment,\n ref: FirebaseFirestore.DocumentReference,\n reason: string,\n): Promise<'stopped'> {\n const hostRef = firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(enrollment.hostId)\n await endFlowEnrollment(ref)\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n action: `Flow stopped mid-wait: ${reason}`.slice(0, 300),\n result: 'skipped',\n trigger: enrollment.event,\n ...runSummaryFields(reason.slice(0, 300)),\n target: {\n type:\n enrollment.automation === 'org'\n ? ORG_AUTOMATION_RUN_TARGET\n : 'workflow',\n id: enrollment.actionId,\n name: enrollment.actionName ?? '',\n },\n searchTokens: activitySearchTokens({ target: { name: enrollment.actionName } }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n return 'stopped'\n}\n\n/**\n * Continues one enrollment from where its wait ended.\n *\n * The enrollment carries everything the run needs except the gates: the step\n * list it entered with, the position inside it, the payload the trigger\n * produced, and who it is about. What it deliberately does NOT carry is\n * permission — the `actions` entitlement, the site's lockdown and the\n * action's own enabled flag are all re-asked here, because a flow that waits\n * three days is a flow that can outlive the plan, the site and the merchant's\n * decision to run it.\n */\nexport async function resumeFlowEnrollment(\n enrollment: FlowEnrollment,\n ref: FirebaseFirestore.DocumentReference,\n options?: { timedOut?: boolean; nowMs?: number },\n): Promise<'ran' | 'waiting' | 'exited' | 'deferred' | 'stopped'> {\n const nowMs = options?.nowMs ?? Date.now()\n // A workflow's enrollment continues the workflow, against the workflow's\n // own kill switch and meter; every enrollment without the field is an\n // action's, which is every one written before a workflow could wait.\n if (enrollment.automation === 'workflow') {\n return await resumeWorkflowEnrollment(enrollment, ref, {\n ...options,\n nowMs,\n })\n }\n // An org automation's continues against the ORGANIZATION's document, which\n // is its kill switch — deleted, switched off, taken off this site or paused\n // here all stop it (AGL-3302).\n if (enrollment.automation === 'org') {\n return await resumeOrgAutomationEnrollment(enrollment, ref, {\n ...options,\n nowMs,\n })\n }\n const hostId = enrollment.hostId\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n\n const stop = (reason: string) => stopFlowEnrollment(enrollment, ref, reason)\n\n const doc = await hostRef\n .collection('actions')\n .doc(enrollment.actionId)\n .get()\n .catch(() => null)\n /*\n * A KILL SWITCH HAS TO KILL, including for the people already inside.\n *\n * Editing a flow leaves an enrollment alone — it runs the snapshot it\n * entered with. DELETING or DISABLING one does not: \"off\" that keeps mailing\n * the queue for the next three days is not off, and it is the control a\n * merchant reaches for when a flow is doing something wrong. So the two\n * cases are deliberately different, and this is the one that stops.\n */\n if (!doc?.exists || doc.get('deletedAt') || doc.get('enabled') === false) {\n return await stop('the automation was turned off or deleted')\n }\n const action = doc.data() as HostAction\n const owner = await getOrgForHost(hostId).catch(() => null)\n if (!checkEntitlement(owner?.org as any, 'actions')) {\n return await stop('this site’s plan no longer includes automations')\n }\n\n const env: ActionRunEnv = {\n hostId,\n hostRef,\n alerts: [],\n // Admitted by the `actions` gate above.\n actionsAllowed: true,\n webhooksAllowed: checkEntitlement(owner?.org as any, 'webhooks'),\n depth: 0,\n org: owner?.org ?? null,\n orgId: owner?.orgId ?? null,\n loadWorkflowContext: makeWorkflowContextLoader(hostRef),\n }\n\n const payload: HostEventPayload = {\n ...(enrollment.payload ?? {}),\n // The timeout BRANCH. A `waitForEvent` resumes either way, and this is\n // the one field that says which — so the step after it carries a `when`\n // naming it, and the author gets a timeout path with no nested step list.\n ...(options?.timedOut ? { [FLOW_TIMED_OUT_FIELD]: true } : {}),\n }\n\n const ending = await executeAction(\n env,\n enrollment.actionId,\n action,\n enrollment.event,\n payload,\n {\n startIndex: enrollment.nextStepIndex,\n // An action's enrollment holds only Actions steps: its snapshot is the\n // action's own list.\n steps: enrollment.steps as HostActionStep[],\n enrollmentRef: ref,\n },\n )\n\n if (ending === 'deferred') {\n /*\n * Pushed a beat down the queue rather than retried immediately: the two\n * refusals that reach here are an hour-long platform ceiling and a\n * day-long per-person window, so retrying in sixty seconds would spend a\n * read to be told the same thing sixty more times.\n */\n await deferFlowEnrollment(ref, nowMs + FLOW_CLAIM_RETRY_MS, nowMs)\n return ending\n }\n if (ending !== 'waiting') await endFlowEnrollment(ref)\n\n /*\n * COUNTED, NEVER REFUSED.\n *\n * A resume is real work and belongs on the run meter, so the usage card and\n * the COGS rollup see it. It is not GATED on `actionRunsPerMonth` the way a\n * new run is, because the person is already inside the flow: refusing here\n * would abandon somebody half-way through a sequence, which is a capacity\n * limit enforced against a person rather than against the decision that\n * added them. The gate belongs at enrollment, and that is where it is.\n */\n await recordRuns({\n firestore,\n hostRef,\n orgId: owner?.orgId,\n counter: 'actionRuns',\n month: runMonthKey(nowMs),\n count: 1,\n })\n return ending\n}\n\n/** How long a deferred enrollment waits before the next attempt. */\nexport const FLOW_CLAIM_RETRY_MS = 15 * 60_000\n\n/**\n * The job beat's entry point: resume every flow whose wait has ended.\n *\n * Thin on purpose. The scheduling contract — due-ness, the transactional\n * claim, the scan budget, the lockdown skip — is `sweepDueFlowEnrollments`,\n * and the work is `resumeFlowEnrollment`; this is the wire between them, so\n * neither has to import the other's dependencies to be tested.\n */\nexport async function runDueFlowEnrollments(\n gate: PluginJobHostGate,\n options?: {\n nowMs?: number\n scanBudget?: number\n cursor?: FlowSweepCursor | null\n },\n): Promise<FlowSweepResult> {\n return await sweepDueFlowEnrollments(gate, {\n ...(options?.nowMs !== undefined ? { nowMs: options.nowMs } : {}),\n ...(options?.scanBudget !== undefined\n ? { scanBudget: options.scanBudget }\n : {}),\n ...(options?.cursor ? { cursor: options.cursor } : {}),\n resume: async (enrollment, ref) => {\n await resumeFlowEnrollment(enrollment, ref, {\n // Reaching the sweep IS the timeout for a `waitForEvent`: the event\n // it was watching for never arrived before `resumeAtMs`. A plain\n // `wait` has no timeout to report, so the flag rides the presence of\n // an awaited event rather than the fact of being swept.\n timedOut: Boolean(enrollment.awaitingEvent),\n ...(options?.nowMs !== undefined ? { nowMs: options.nowMs } : {}),\n })\n },\n })\n}\n\n/**\n * Wakes the flows this person's event was being waited for.\n *\n * NOT A POLL. Nothing here scans the enrolled population asking whose turn it\n * is — the event arrives already naming a person, and the lookup is three\n * equality filters against that person's key. A site with ten thousand people\n * waiting costs the same as one with ten.\n *\n * The COST GUARD is the caller's, and it is why this takes an email rather\n * than reading one: `runEventActions` fires on every page view of every\n * published site, so it asks only for events that name a person. A page view\n * does not, and pays nothing.\n */\nasync function wakeFlowsAwaitingEvent(\n hostId: string,\n event: string,\n payload: HostEventPayload,\n): Promise<void> {\n const email = String((payload as any)?.['email'] ?? '').trim()\n if (!email || !email.includes('@')) return\n const waiting = await findFlowEnrollmentsAwaiting({ hostId, event, email })\n for (const doc of waiting) {\n const claimed = await claimFlowEnrollment(doc.ref).catch(() => null)\n if (!claimed) continue\n try {\n /*\n * The awaited event ARRIVED, so this is not the timeout branch — and\n * the arriving payload joins the carried one, so a step after the wait\n * can read the order total the flow was waiting for.\n */\n await resumeFlowEnrollment(\n {\n ...claimed,\n payload: { ...(claimed.payload ?? {}), ...(payload ?? {}) },\n },\n doc.ref,\n { timedOut: false },\n )\n } catch (error) {\n console.error('[flow] event wake failed', doc.ref.path, error)\n await deferFlowEnrollment(doc.ref, Date.now() + FLOW_CLAIM_RETRY_MS)\n }\n }\n}\n\nexport default runEventActions\n"],"names":["ACTION_MAX_STEPS","ACTIONS_MAX_PER_HOST","checkEntitlement","consentGroupForHost","planLabelGrantingFeature","evaluateExpression","evaluateStepGuard","evaluateTriggerConditions","flowSubscriptionTopicId","hostPublicOrigin","isClientActionStep","normalizeTriggerConditions","resolveOrgEntitlements","isDeferrableSendResult","isEmailConfigured","sendEmail","sendFailureReason","enrollListMember","filterSendableForHost","firebaseAdmin","flowEmailRefusal","getOrgForHost","hostDisplayName","hostSendingIdentity","meterHostEmail","notifyHostManagers","consentGroupForSite","resolveOrgIdForHost","activitySearchTokens","visibleToHost","triggerFilterProblem","filePluginPersonUnder","findPluginPerson","findOrgContainersByName","readOrgContainers","screenOutboundSend","renderSiteTextEmail","createHmac","FieldValue","runSummaryFields","ACTION_MAX_EVENT_DEPTH","runWorkflow","WORKFLOW_MAX_STEPS","describeStepOutcome","eventRunSuspension","triggeredDocsForEvent","resolveStepEmailMerge","stepEmailMergeContext","recordRuns","runMonthKey","runsUsedThisMonth","advanceFlowEnrollment","claimFlowEnrollment","deferFlowEnrollment","endFlowEnrollment","enrollInFlow","findFlowEnrollmentsAwaiting","sweepDueFlowEnrollments","resumeWorkflowEnrollment","runEventWorkflows","ORG_AUTOMATION_RUN_TARGET","orgAutomationStepRefusal","findOrgAutomationsForEvent","resumeOrgAutomationEnrollment","isWorkflowActionStep","workflowActionStepRefusal","workflowHasActionSteps","workflowStepTypeLabel","runTriggeredByFields","pluginServerStepExecutor","preparePluginRecordEmail","FLOW_TIMED_OUT_FIELD","isFlowSuspendingStep","sendEmailIsTransactionalReply","MAX_TRIGGERED_ACTIONS","liveActionsForEvent","hostRef","event","collection","keep","doc","get","max","maxReads","automationRunEnv","input","org","owner","hostId","alerts","actionsAllowed","webhooksAllowed","depth","orgId","loadWorkflowContext","makeWorkflowContextLoader","workflowContext","functionDocs","variableDocs","workflowDocs","Promise","all","limit","workflowContextFromDocs","docs","byName","map","data","deletedAt","id","name","workflows","withId","$id","functions","variables","tallyStep","tally","type","verdict","kind","outcomes","push","detail","errors","error","ending","raiseEarnedEvent","env","emit","payload","fromWorkflows","fromActions","runEventActions","runServerStep","run","step","context","enrollmentRef","failed","when","scope","nested","suspended","suspendFlow","steps","nextStepIndex","index","message","String","slice","severity","workflow","workflowId","trim","workflowName","executeWorkflow","length","join","evaluated","ok","eventName","hookDoc","webhookId","where","webhookName","hook","exists","undefined","enabled","direction","url","body","JSON","stringify","sentAt","Date","toISOString","signature","secret","update","digest","describeConfiguredUrlRefusal","fetchConfiguredPublicUrl","delivered","lastStatus","attempt","result","method","headers","signal","AbortSignal","timeout","refusal","status","resolve","setTimeout","title","link","screened","to","toField","includes","hostData","catch","siteBase","transactional","afterWait","Boolean","topicId","consentGroup","sendable","gate","email","authoredSubject","subject","authoredText","mergeContext","contactGroupId","groupId","siteName","mergePerson","emailSubject","text","replace","emailText","path","host","bodies","outcome","reference","recordEmail","contactId","framed","app","firestore","html","sendingIdentity","tags","audience","releasedReviewId","priority","marketing","consentHostIds","hostIds","consentAwaitsConfirmation","awaitsConfirmation","sendError","sent","file","sourceRef","toLowerCase","listsRef","listDoc","listId","listName","listRef","ref","group","source","contact","onlyVisibleToSite","campaignLabel","campaignName","campaignId","campaignOrgId","containerStore","usable","container","live","visibleTo","namedId","campaign","find","filed","record","containerKind","ids","executor","answer","executeAction","actionId","action","options","orgRun","orgAutomation","startIndex","Math","stepErrors","noun","summary","add","actorId","actorEmail","trigger","target","searchTokens","createdAt","serverTimestamp","Object","results","value","stepPayload","call","resultName","functionName","functionId","returnName","returnValue","named","values","at","request","minutes","Number","delayMinutes","timeoutMinutes","isFinite","nowMs","now","resumeAtMs","awaitingEvent","enrolled","automation","reason","SKIP_LOG_EXCLUDED_EVENTS","Set","conditionsNameAnotherForm","submitted","clauses","another","clause","field","op","combinator","some","every","recordSkippedRun","because","has","condition","filter","wakeFlowsAwaitingEvent","actions","placed","runSite","runOrg","meter","counter","month","actionRunsPerMonth","used","executed","unrunnable","count","console","runSingleAction","runSingleActionOutcome","skipped","ran","stopFlowEnrollment","enrollment","actionName","resumeFlowEnrollment","stop","timedOut","FLOW_CLAIM_RETRY_MS","runDueFlowEnrollments","scanBudget","cursor","resume","waiting","claimed"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The `webhookPost` step's transport (AGL-3628), behind its own module so
|
|
19
|
+
* the engine can load it lazily: it brings `undici`, which only that step
|
|
20
|
+
* needs and which cannot load under jsdom. The engine imports
|
|
21
|
+
* `@aglyn/tenant-data-admin` statically elsewhere, so it must lazy-load a
|
|
22
|
+
* module of its own rather than that library directly.
|
|
23
|
+
*/
|
|
24
|
+
export { describeConfiguredUrlRefusal, fetchConfiguredPublicUrl, } from '@aglyn/tenant-data-admin/server/configured-url-fetch';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The `webhookPost` step's transport (AGL-3628), behind its own module so
|
|
18
|
+
* the engine can load it lazily: it brings `undici`, which only that step
|
|
19
|
+
* needs and which cannot load under jsdom. The engine imports
|
|
20
|
+
* `@aglyn/tenant-data-admin` statically elsewhere, so it must lazy-load a
|
|
21
|
+
* module of its own rather than that library directly.
|
|
22
|
+
*/ export { describeConfiguredUrlRefusal, fetchConfiguredPublicUrl } from "@aglyn/tenant-data-admin/server/configured-url-fetch";
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=webhook-delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/webhook-delivery.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The `webhookPost` step's transport (AGL-3628), behind its own module so\n * the engine can load it lazily: it brings `undici`, which only that step\n * needs and which cannot load under jsdom. The engine imports\n * `@aglyn/tenant-data-admin` statically elsewhere, so it must lazy-load a\n * module of its own rather than that library directly.\n */\nexport {\n describeConfiguredUrlRefusal,\n fetchConfiguredPublicUrl,\n} from '@aglyn/tenant-data-admin/server/configured-url-fetch'\n"],"names":["describeConfiguredUrlRefusal","fetchConfiguredPublicUrl"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GACD,SACEA,4BAA4B,EAC5BC,wBAAwB,QACnB,uDAAsD"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
export interface HostWebhook {
|
|
25
25
|
name: string;
|
|
26
26
|
direction: 'outbound' | 'inbound';
|
|
27
|
-
/** Outbound delivery URL (https only; checked
|
|
27
|
+
/** Outbound delivery URL (https only; resolved and checked at send time). */
|
|
28
28
|
url?: string;
|
|
29
29
|
/** Shared secret: signs outbound bodies, verifies inbound callers. */
|
|
30
30
|
secret?: string;
|
|
@@ -32,5 +32,11 @@ export interface HostWebhook {
|
|
|
32
32
|
workflowName?: string;
|
|
33
33
|
enabled?: boolean;
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* A hint for the webhooks card's URL field: https, and not an obvious private
|
|
37
|
+
* IPv4 literal. It is NOT the SSRF guard — it reads only the URL's text, so
|
|
38
|
+
* an IPv6 literal, a decimal IPv4 or a name that resolves inward all pass it.
|
|
39
|
+
* Delivery goes through `fetchConfiguredPublicUrl` (`@aglyn/tenant-data-admin`),
|
|
40
|
+
* which resolves, pins and refuses redirects.
|
|
41
|
+
*/
|
|
36
42
|
export declare const WEBHOOK_URL_PATTERN: RegExp;
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
* inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run
|
|
20
20
|
* a workflow with the posted JSON in scope. Business tier (`webhooks`
|
|
21
21
|
* flag); Pro can be enabled per-tenant via entitlement overrides.
|
|
22
|
-
*/ /**
|
|
22
|
+
*/ /**
|
|
23
|
+
* A hint for the webhooks card's URL field: https, and not an obvious private
|
|
24
|
+
* IPv4 literal. It is NOT the SSRF guard — it reads only the URL's text, so
|
|
25
|
+
* an IPv6 literal, a decimal IPv4 or a name that resolves inward all pass it.
|
|
26
|
+
* Delivery goes through `fetchConfiguredPublicUrl` (`@aglyn/tenant-data-admin`),
|
|
27
|
+
* which resolves, pins and refuses redirects.
|
|
28
|
+
*/ export const WEBHOOK_URL_PATTERN = /^https:\/\/(?!localhost)(?!127\.)(?!0\.)(?!10\.)(?!172\.(1[6-9]|2\d|3[01])\.)(?!192\.168\.)(?!169\.254\.)[^\s]+$/i;
|
|
23
29
|
|
|
24
30
|
//# sourceMappingURL=webhooks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/webhooks.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Webhooks (AGL-149) at `hosts/{hostId}/webhooks/{id}`. Outbound entries\n * are targets a `webhookPost` action step delivers to (HMAC-signed);\n * inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run\n * a workflow with the posted JSON in scope. Business tier (`webhooks`\n * flag); Pro can be enabled per-tenant via entitlement overrides.\n */\nexport interface HostWebhook {\n name: string\n direction: 'outbound' | 'inbound'\n /** Outbound delivery URL (https only; checked
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/webhooks.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Webhooks (AGL-149) at `hosts/{hostId}/webhooks/{id}`. Outbound entries\n * are targets a `webhookPost` action step delivers to (HMAC-signed);\n * inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run\n * a workflow with the posted JSON in scope. Business tier (`webhooks`\n * flag); Pro can be enabled per-tenant via entitlement overrides.\n */\nexport interface HostWebhook {\n name: string\n direction: 'outbound' | 'inbound'\n /** Outbound delivery URL (https only; resolved and checked at send time). */\n url?: string\n /** Shared secret: signs outbound bodies, verifies inbound callers. */\n secret?: string\n /** Inbound: workflow (by name) enrolled with the payload in scope. */\n workflowName?: string\n enabled?: boolean\n}\n\n/**\n * A hint for the webhooks card's URL field: https, and not an obvious private\n * IPv4 literal. It is NOT the SSRF guard — it reads only the URL's text, so\n * an IPv6 literal, a decimal IPv4 or a name that resolves inward all pass it.\n * Delivery goes through `fetchConfiguredPublicUrl` (`@aglyn/tenant-data-admin`),\n * which resolves, pins and refuses redirects.\n */\nexport const WEBHOOK_URL_PATTERN =\n /^https:\\/\\/(?!localhost)(?!127\\.)(?!0\\.)(?!10\\.)(?!172\\.(1[6-9]|2\\d|3[01])\\.)(?!192\\.168\\.)(?!169\\.254\\.)[^\\s]+$/i\n"],"names":["WEBHOOK_URL_PATTERN"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GAaD;;;;;;CAMC,GACD,OAAO,MAAMA,sBACX,oHAAmH"}
|