@aglyn/plugins-workflows 1.0.0-beta.183 → 1.0.0-beta.184
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/README.md +3 -1
- package/package.json +12 -12
- package/src/lib/components/host-actions-card.component.js +15 -10
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/engine/run-event-actions.d.ts +29 -0
- package/src/lib/engine/run-event-actions.js +38 -11
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/model/action-test-run.d.ts +36 -0
- package/src/lib/model/action-test-run.js +49 -0
- package/src/lib/model/action-test-run.js.map +1 -0
- package/src/lib/server/action-test-run-route.d.ts +91 -0
- package/src/lib/server/action-test-run-route.js +243 -0
- package/src/lib/server/action-test-run-route.js.map +1 -0
- package/src/lib/server-console.d.ts +2 -2
- package/src/lib/server-console.js +5 -2
- package/src/lib/server-console.js.map +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
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 ACTIONS CARD'S TEST RUN (AGL-3309), as both ends of it spell it.
|
|
19
|
+
*
|
|
20
|
+
* Pure and client-safe: the card posts to the path, and the console route
|
|
21
|
+
* registers at it and builds the payload the action's steps see.
|
|
22
|
+
*/
|
|
23
|
+
/** The console door, under the plugin's `automations` API prefix. */
|
|
24
|
+
export declare const ACTION_TEST_RUN_API_ROUTE = "automations/actions/test-run";
|
|
25
|
+
/** The page a test run reports when the caller names none. */
|
|
26
|
+
export declare const ACTION_TEST_RUN_PATH = "/console-test";
|
|
27
|
+
/**
|
|
28
|
+
* The payload a test run's steps see.
|
|
29
|
+
*
|
|
30
|
+
* The caller's fields, bounded as the page runtime's dispatch bounds a
|
|
31
|
+
* visitor's — identifier names, twenty at most, each value cut to 500
|
|
32
|
+
* characters — over a sample `path`, and `test: 'true'` whatever the caller
|
|
33
|
+
* sent, so a filter, a condition or a step's template can tell a test from a
|
|
34
|
+
* visitor and nothing can pass one off as the other.
|
|
35
|
+
*/
|
|
36
|
+
export declare function actionTestRunPayload(raw: unknown): Record<string, string>;
|
|
@@ -0,0 +1,49 @@
|
|
|
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 ACTIONS CARD'S TEST RUN (AGL-3309), as both ends of it spell it.
|
|
18
|
+
*
|
|
19
|
+
* Pure and client-safe: the card posts to the path, and the console route
|
|
20
|
+
* registers at it and builds the payload the action's steps see.
|
|
21
|
+
*/ /** The console door, under the plugin's `automations` API prefix. */ export const ACTION_TEST_RUN_API_ROUTE = 'automations/actions/test-run';
|
|
22
|
+
/** The page a test run reports when the caller names none. */ export const ACTION_TEST_RUN_PATH = '/console-test';
|
|
23
|
+
/** A payload field name: an identifier, as the page runtime's dispatch takes one. */ const PAYLOAD_KEY = /^[a-zA-Z][a-zA-Z0-9_]{0,39}$/;
|
|
24
|
+
/** How many of the caller's fields are read. */ const PAYLOAD_MAX_FIELDS = 20;
|
|
25
|
+
/** How long one field's value may be. */ const PAYLOAD_MAX_VALUE_LENGTH = 500;
|
|
26
|
+
/**
|
|
27
|
+
* The payload a test run's steps see.
|
|
28
|
+
*
|
|
29
|
+
* The caller's fields, bounded as the page runtime's dispatch bounds a
|
|
30
|
+
* visitor's — identifier names, twenty at most, each value cut to 500
|
|
31
|
+
* characters — over a sample `path`, and `test: 'true'` whatever the caller
|
|
32
|
+
* sent, so a filter, a condition or a step's template can tell a test from a
|
|
33
|
+
* visitor and nothing can pass one off as the other.
|
|
34
|
+
*/ export function actionTestRunPayload(raw) {
|
|
35
|
+
const payload = {
|
|
36
|
+
path: ACTION_TEST_RUN_PATH
|
|
37
|
+
};
|
|
38
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
39
|
+
for (const [key, value] of Object.entries(raw).slice(0, PAYLOAD_MAX_FIELDS)){
|
|
40
|
+
if (PAYLOAD_KEY.test(key)) {
|
|
41
|
+
payload[key] = String(value).slice(0, PAYLOAD_MAX_VALUE_LENGTH);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
payload['test'] = 'true';
|
|
46
|
+
return payload;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=action-test-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/action-test-run.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 ACTIONS CARD'S TEST RUN (AGL-3309), as both ends of it spell it.\n *\n * Pure and client-safe: the card posts to the path, and the console route\n * registers at it and builds the payload the action's steps see.\n */\n\n/** The console door, under the plugin's `automations` API prefix. */\nexport const ACTION_TEST_RUN_API_ROUTE = 'automations/actions/test-run'\n\n/** The page a test run reports when the caller names none. */\nexport const ACTION_TEST_RUN_PATH = '/console-test'\n\n/** A payload field name: an identifier, as the page runtime's dispatch takes one. */\nconst PAYLOAD_KEY = /^[a-zA-Z][a-zA-Z0-9_]{0,39}$/\n\n/** How many of the caller's fields are read. */\nconst PAYLOAD_MAX_FIELDS = 20\n\n/** How long one field's value may be. */\nconst PAYLOAD_MAX_VALUE_LENGTH = 500\n\n/**\n * The payload a test run's steps see.\n *\n * The caller's fields, bounded as the page runtime's dispatch bounds a\n * visitor's — identifier names, twenty at most, each value cut to 500\n * characters — over a sample `path`, and `test: 'true'` whatever the caller\n * sent, so a filter, a condition or a step's template can tell a test from a\n * visitor and nothing can pass one off as the other.\n */\nexport function actionTestRunPayload(raw: unknown): Record<string, string> {\n const payload: Record<string, string> = { path: ACTION_TEST_RUN_PATH }\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n for (const [key, value] of Object.entries(raw).slice(0, PAYLOAD_MAX_FIELDS)) {\n if (PAYLOAD_KEY.test(key)) {\n payload[key] = String(value).slice(0, PAYLOAD_MAX_VALUE_LENGTH)\n }\n }\n }\n payload['test'] = 'true'\n return payload\n}\n"],"names":["ACTION_TEST_RUN_API_ROUTE","ACTION_TEST_RUN_PATH","PAYLOAD_KEY","PAYLOAD_MAX_FIELDS","PAYLOAD_MAX_VALUE_LENGTH","actionTestRunPayload","raw","payload","path","Array","isArray","key","value","Object","entries","slice","test","String"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;CAKC,GAED,mEAAmE,GACnE,OAAO,MAAMA,4BAA4B,+BAA8B;AAEvE,4DAA4D,GAC5D,OAAO,MAAMC,uBAAuB,gBAAe;AAEnD,mFAAmF,GACnF,MAAMC,cAAc;AAEpB,8CAA8C,GAC9C,MAAMC,qBAAqB;AAE3B,uCAAuC,GACvC,MAAMC,2BAA2B;AAEjC;;;;;;;;CAQC,GACD,OAAO,SAASC,qBAAqBC,GAAY;IAC/C,MAAMC,UAAkC;QAAEC,MAAMP;IAAqB;IACrE,IAAIK,OAAO,OAAOA,QAAQ,YAAY,CAACG,MAAMC,OAAO,CAACJ,MAAM;QACzD,KAAK,MAAM,CAACK,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACR,KAAKS,KAAK,CAAC,GAAGZ,oBAAqB;YAC3E,IAAID,YAAYc,IAAI,CAACL,MAAM;gBACzBJ,OAAO,CAACI,IAAI,GAAGM,OAAOL,OAAOG,KAAK,CAAC,GAAGX;YACxC;QACF;IACF;IACAG,OAAO,CAAC,OAAO,GAAG;IAClB,OAAOA;AACT"}
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
* TESTING ONE OF A SITE'S ACTIONS FROM THE CONSOLE (AGL-3309).
|
|
19
|
+
*
|
|
20
|
+
* The Actions card's Test button runs an action with an in-page trigger as
|
|
21
|
+
* though the trigger had fired on the site, and says what came back. A
|
|
22
|
+
* published page reaches the same engine path through `events/dispatch`,
|
|
23
|
+
* which is a TENANT route — served on the site's own origin, to anonymous
|
|
24
|
+
* visitors — and the console is another app on another origin whose
|
|
25
|
+
* dispatcher never loads it. This is the console's door to that path, and
|
|
26
|
+
* unlike the page's it knows who is asking.
|
|
27
|
+
*
|
|
28
|
+
* ## Who may
|
|
29
|
+
*
|
|
30
|
+
* A site admin or editor, read off the host's `memberRoles`, or staff. The
|
|
31
|
+
* publish roles rather than the content roles, because a test run is a LIVE
|
|
32
|
+
* run: its emails are sent, its webhooks posted and its rows written, as a
|
|
33
|
+
* visitor's would be. An author edits and makes nothing live, and so does not
|
|
34
|
+
* run automations either.
|
|
35
|
+
*
|
|
36
|
+
* The site's plugin switch is the dispatcher's: the request names its site as
|
|
37
|
+
* a top-level `hostId`, so a site with Automation switched off has no such
|
|
38
|
+
* door.
|
|
39
|
+
*
|
|
40
|
+
* ## What runs
|
|
41
|
+
*
|
|
42
|
+
* The stored action, through `runSingleActionOutcome` — the single-action path
|
|
43
|
+
* a page's dispatch takes — so the switch, the conditions, the plan gate and
|
|
44
|
+
* the month's run allowance are the ones a real run meets, the run is metered
|
|
45
|
+
* on the site's `actionRuns`, and it lands in the action's Runs history like
|
|
46
|
+
* any other. It is marked rather than simulated: see `actionTestRunPayload`.
|
|
47
|
+
* When a gate stops it, the refusal names the gate, because a test that ran
|
|
48
|
+
* nothing must not read as one that ran.
|
|
49
|
+
*
|
|
50
|
+
* Only an in-page trigger: a server event (a form submitted, an order placed)
|
|
51
|
+
* carries fields no sample stands in for, and its runs come from the doors
|
|
52
|
+
* that raise it.
|
|
53
|
+
*/
|
|
54
|
+
import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
55
|
+
import type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners';
|
|
56
|
+
import type { SingleActionOutcome } from '../engine/run-event-actions';
|
|
57
|
+
export { ACTION_TEST_RUN_API_ROUTE } from '../model/action-test-run';
|
|
58
|
+
/**
|
|
59
|
+
* Test runs one person may start on one site in a minute.
|
|
60
|
+
*
|
|
61
|
+
* Every test is a real run with real sends, so this is the feature's own
|
|
62
|
+
* bucket beneath the console dispatcher's general one: a person iterating on
|
|
63
|
+
* an action tests every few seconds at most, and past this is a script.
|
|
64
|
+
*/
|
|
65
|
+
export declare const ACTION_TEST_RUNS_PER_MINUTE = 10;
|
|
66
|
+
/** What the door reaches outside this module; specs hand in their own. */
|
|
67
|
+
export interface ActionTestRunDeps {
|
|
68
|
+
firestore(): FirebaseFirestore.Firestore;
|
|
69
|
+
verifyIdToken(token: string): Promise<{
|
|
70
|
+
uid: string;
|
|
71
|
+
staff?: unknown;
|
|
72
|
+
}>;
|
|
73
|
+
consumeRateLimit(key: string, options: {
|
|
74
|
+
limit: number;
|
|
75
|
+
windowMs: number;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
allowed: boolean;
|
|
78
|
+
resetMs: number;
|
|
79
|
+
}>;
|
|
80
|
+
runAction(hostId: string, actionId: string, event: string, payload: HostEventPayload): Promise<SingleActionOutcome>;
|
|
81
|
+
now(): number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `automations/actions/test-run`: run one of a site's actions now.
|
|
85
|
+
*
|
|
86
|
+
* Body `{ hostId, actionId, payload? }`. Answers `{ ok: true, alerts }` when
|
|
87
|
+
* the action's steps ran — the site alerts they produced, for the card to show
|
|
88
|
+
* — and otherwise an `error` saying what stopped it.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createActionTestRunHandler(overrides?: Partial<ActionTestRunDeps>): PluginApiHandler;
|
|
91
|
+
export declare const actionTestRunHandler: PluginApiHandler;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /**
|
|
18
|
+
* TESTING ONE OF A SITE'S ACTIONS FROM THE CONSOLE (AGL-3309).
|
|
19
|
+
*
|
|
20
|
+
* The Actions card's Test button runs an action with an in-page trigger as
|
|
21
|
+
* though the trigger had fired on the site, and says what came back. A
|
|
22
|
+
* published page reaches the same engine path through `events/dispatch`,
|
|
23
|
+
* which is a TENANT route — served on the site's own origin, to anonymous
|
|
24
|
+
* visitors — and the console is another app on another origin whose
|
|
25
|
+
* dispatcher never loads it. This is the console's door to that path, and
|
|
26
|
+
* unlike the page's it knows who is asking.
|
|
27
|
+
*
|
|
28
|
+
* ## Who may
|
|
29
|
+
*
|
|
30
|
+
* A site admin or editor, read off the host's `memberRoles`, or staff. The
|
|
31
|
+
* publish roles rather than the content roles, because a test run is a LIVE
|
|
32
|
+
* run: its emails are sent, its webhooks posted and its rows written, as a
|
|
33
|
+
* visitor's would be. An author edits and makes nothing live, and so does not
|
|
34
|
+
* run automations either.
|
|
35
|
+
*
|
|
36
|
+
* The site's plugin switch is the dispatcher's: the request names its site as
|
|
37
|
+
* a top-level `hostId`, so a site with Automation switched off has no such
|
|
38
|
+
* door.
|
|
39
|
+
*
|
|
40
|
+
* ## What runs
|
|
41
|
+
*
|
|
42
|
+
* The stored action, through `runSingleActionOutcome` — the single-action path
|
|
43
|
+
* a page's dispatch takes — so the switch, the conditions, the plan gate and
|
|
44
|
+
* the month's run allowance are the ones a real run meets, the run is metered
|
|
45
|
+
* on the site's `actionRuns`, and it lands in the action's Runs history like
|
|
46
|
+
* any other. It is marked rather than simulated: see `actionTestRunPayload`.
|
|
47
|
+
* When a gate stops it, the refusal names the gate, because a test that ran
|
|
48
|
+
* nothing must not read as one that ran.
|
|
49
|
+
*
|
|
50
|
+
* Only an in-page trigger: a server event (a form submitted, an order placed)
|
|
51
|
+
* carries fields no sample stands in for, and its runs come from the doors
|
|
52
|
+
* that raise it.
|
|
53
|
+
*/ import { hostRoleCanPublish, isSiteEventType, planLabelGrantingFeature } from "@aglyn/aglyn/server";
|
|
54
|
+
/*
|
|
55
|
+
* The MODULES, not the barrel, as for the org automation doors beside this
|
|
56
|
+
* one: `@aglyn/tenant-data-admin`'s index reaches the Next render cache.
|
|
57
|
+
*/ import { isDocumentId } from "@aglyn/tenant-data-admin/server/document-id";
|
|
58
|
+
import firebaseAdmin from "@aglyn/tenant-data-admin/server/firebase-admin";
|
|
59
|
+
import { isRefusedIdToken } from "@aglyn/tenant-data-admin/server/id-token-refusal";
|
|
60
|
+
import { consumeRateLimit } from "@aglyn/tenant-data-admin/server/rate-limit-store";
|
|
61
|
+
import { actionTestRunPayload } from "../model/action-test-run.js";
|
|
62
|
+
export { ACTION_TEST_RUN_API_ROUTE } from "../model/action-test-run.js";
|
|
63
|
+
/**
|
|
64
|
+
* Test runs one person may start on one site in a minute.
|
|
65
|
+
*
|
|
66
|
+
* Every test is a real run with real sends, so this is the feature's own
|
|
67
|
+
* bucket beneath the console dispatcher's general one: a person iterating on
|
|
68
|
+
* an action tests every few seconds at most, and past this is a script.
|
|
69
|
+
*/ export const ACTION_TEST_RUNS_PER_MINUTE = 10;
|
|
70
|
+
const ACTION_TEST_RUN_WINDOW_MS = 60000;
|
|
71
|
+
function defaultDeps() {
|
|
72
|
+
return {
|
|
73
|
+
firestore: ()=>firebaseAdmin.app().firestore(),
|
|
74
|
+
verifyIdToken: async (token)=>{
|
|
75
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(token);
|
|
76
|
+
return {
|
|
77
|
+
uid: decoded.uid,
|
|
78
|
+
staff: decoded['staff']
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
consumeRateLimit: (key, options)=>consumeRateLimit(key, options),
|
|
82
|
+
// Imported by the first test run rather than with the console surface,
|
|
83
|
+
// as the host-event listener imports it.
|
|
84
|
+
runAction: async (hostId, actionId, event, payload)=>{
|
|
85
|
+
const { runSingleActionOutcome } = await import("../engine/run-event-actions.js");
|
|
86
|
+
return await runSingleActionOutcome(hostId, actionId, event, payload);
|
|
87
|
+
},
|
|
88
|
+
now: ()=>Date.now()
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** The bearer token a request carries, or null. */ function bearerToken(headers) {
|
|
92
|
+
var _headers_authorization;
|
|
93
|
+
const authorization = String((_headers_authorization = headers['authorization']) != null ? _headers_authorization : '');
|
|
94
|
+
return authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) || null : null;
|
|
95
|
+
}
|
|
96
|
+
/** The status and words for each gate that can stop a test run. */ function refusalFor(skipped, outcome, payload) {
|
|
97
|
+
switch(skipped){
|
|
98
|
+
case 'missing':
|
|
99
|
+
return {
|
|
100
|
+
status: 404,
|
|
101
|
+
error: 'That action no longer exists'
|
|
102
|
+
};
|
|
103
|
+
case 'disabled':
|
|
104
|
+
return {
|
|
105
|
+
status: 409,
|
|
106
|
+
error: 'Switch the action on to test it'
|
|
107
|
+
};
|
|
108
|
+
case 'event':
|
|
109
|
+
return {
|
|
110
|
+
status: 409,
|
|
111
|
+
error: 'The action changed while it was being tested — try again'
|
|
112
|
+
};
|
|
113
|
+
case 'conditions':
|
|
114
|
+
return {
|
|
115
|
+
status: 422,
|
|
116
|
+
error: `Nothing ran: a test run on ${String(payload['path'])} does not ` + 'meet this action’s conditions'
|
|
117
|
+
};
|
|
118
|
+
case 'plan':
|
|
119
|
+
var _planLabelGrantingFeature;
|
|
120
|
+
return {
|
|
121
|
+
status: 403,
|
|
122
|
+
error: `Actions need the ${(_planLabelGrantingFeature = planLabelGrantingFeature('actions')) != null ? _planLabelGrantingFeature : 'Pro'} ` + 'plan — see Billing to upgrade'
|
|
123
|
+
};
|
|
124
|
+
case 'allowance':
|
|
125
|
+
return {
|
|
126
|
+
status: 402,
|
|
127
|
+
error: `This site has used its ${outcome.limit} action runs for the month`
|
|
128
|
+
};
|
|
129
|
+
default:
|
|
130
|
+
return {
|
|
131
|
+
status: 500,
|
|
132
|
+
error: 'The test run could not be completed. Try again.'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* `automations/actions/test-run`: run one of a site's actions now.
|
|
138
|
+
*
|
|
139
|
+
* Body `{ hostId, actionId, payload? }`. Answers `{ ok: true, alerts }` when
|
|
140
|
+
* the action's steps ran — the site alerts they produced, for the card to show
|
|
141
|
+
* — and otherwise an `error` saying what stopped it.
|
|
142
|
+
*/ export function createActionTestRunHandler(overrides = {}) {
|
|
143
|
+
return async (req, res)=>{
|
|
144
|
+
var _ref, _ref1;
|
|
145
|
+
var _req_body, _req_body1;
|
|
146
|
+
const deps = _extends({}, defaultDeps(), overrides);
|
|
147
|
+
if (req.method !== 'POST') {
|
|
148
|
+
return res.status(405).json({
|
|
149
|
+
error: 'Method not allowed'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
153
|
+
const actionId = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.actionId) != null ? _ref1 : '');
|
|
154
|
+
if (!isDocumentId(hostId)) {
|
|
155
|
+
return res.status(400).json({
|
|
156
|
+
error: 'Invalid hostId'
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (!isDocumentId(actionId)) {
|
|
160
|
+
return res.status(400).json({
|
|
161
|
+
error: 'Invalid actionId'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const token = bearerToken(req.headers);
|
|
165
|
+
if (!token) return res.status(401).json({
|
|
166
|
+
error: 'Unauthenticated'
|
|
167
|
+
});
|
|
168
|
+
let caller;
|
|
169
|
+
try {
|
|
170
|
+
caller = await deps.verifyIdToken(token);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// A refused credential is the caller's 401; a failure to check one is
|
|
173
|
+
// ours and keeps a 5xx (AGL-2852).
|
|
174
|
+
if (isRefusedIdToken(error)) {
|
|
175
|
+
return res.status(401).json({
|
|
176
|
+
error: 'Unauthenticated'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
console.error('[workflows] test run could not verify the caller', error);
|
|
180
|
+
return res.status(500).json({
|
|
181
|
+
error: 'The sign-in could not be checked. Try again.'
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
var _host_get, _ref2, _outcome_skipped;
|
|
186
|
+
var _stored_data_trigger, _stored_data, _req_body2;
|
|
187
|
+
const firestore = deps.firestore();
|
|
188
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
189
|
+
const host = await hostRef.get();
|
|
190
|
+
if (!host.exists) return res.status(404).json({
|
|
191
|
+
error: 'Unknown site'
|
|
192
|
+
});
|
|
193
|
+
const role = ((_host_get = host.get('memberRoles')) != null ? _host_get : {})[caller.uid];
|
|
194
|
+
if (caller.staff !== true && !hostRoleCanPublish(role)) {
|
|
195
|
+
return res.status(403).json({
|
|
196
|
+
error: 'Only a site admin or editor can test an action'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const rate = await deps.consumeRateLimit(`workflows-test-run:${hostId}:${caller.uid}`, {
|
|
200
|
+
limit: ACTION_TEST_RUNS_PER_MINUTE,
|
|
201
|
+
windowMs: ACTION_TEST_RUN_WINDOW_MS
|
|
202
|
+
});
|
|
203
|
+
if (!rate.allowed) {
|
|
204
|
+
res.setHeader('Retry-After', String(Math.max(1, Math.ceil((rate.resetMs - deps.now()) / 1000))));
|
|
205
|
+
return res.status(429).json({
|
|
206
|
+
error: 'Too many test runs — wait a minute and try again'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const stored = await hostRef.collection('actions').doc(actionId).get();
|
|
210
|
+
if (!stored.exists || stored.get('deletedAt')) {
|
|
211
|
+
return res.status(404).json({
|
|
212
|
+
error: 'That action no longer exists'
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const event = String((_ref2 = (_stored_data = stored.data()) == null ? void 0 : (_stored_data_trigger = _stored_data.trigger) == null ? void 0 : _stored_data_trigger.event) != null ? _ref2 : '');
|
|
216
|
+
if (!isSiteEventType(event)) {
|
|
217
|
+
return res.status(400).json({
|
|
218
|
+
error: 'Only an action with an in-page trigger can be tested here'
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const payload = actionTestRunPayload((_req_body2 = req.body) == null ? void 0 : _req_body2.payload);
|
|
222
|
+
const outcome = await deps.runAction(hostId, actionId, event, payload);
|
|
223
|
+
if (outcome.ran) {
|
|
224
|
+
return res.status(200).json({
|
|
225
|
+
ok: true,
|
|
226
|
+
alerts: outcome.alerts
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const refusal = refusalFor((_outcome_skipped = outcome.skipped) != null ? _outcome_skipped : 'failed', outcome, payload);
|
|
230
|
+
return res.status(refusal.status).json({
|
|
231
|
+
error: refusal.error
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('[workflows] test run failed', hostId, actionId, error);
|
|
235
|
+
return res.status(500).json({
|
|
236
|
+
error: 'The test run could not be completed. Try again.'
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
export const actionTestRunHandler = createActionTestRunHandler();
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=action-test-run-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/server/action-test-run-route.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 * TESTING ONE OF A SITE'S ACTIONS FROM THE CONSOLE (AGL-3309).\n *\n * The Actions card's Test button runs an action with an in-page trigger as\n * though the trigger had fired on the site, and says what came back. A\n * published page reaches the same engine path through `events/dispatch`,\n * which is a TENANT route — served on the site's own origin, to anonymous\n * visitors — and the console is another app on another origin whose\n * dispatcher never loads it. This is the console's door to that path, and\n * unlike the page's it knows who is asking.\n *\n * ## Who may\n *\n * A site admin or editor, read off the host's `memberRoles`, or staff. The\n * publish roles rather than the content roles, because a test run is a LIVE\n * run: its emails are sent, its webhooks posted and its rows written, as a\n * visitor's would be. An author edits and makes nothing live, and so does not\n * run automations either.\n *\n * The site's plugin switch is the dispatcher's: the request names its site as\n * a top-level `hostId`, so a site with Automation switched off has no such\n * door.\n *\n * ## What runs\n *\n * The stored action, through `runSingleActionOutcome` — the single-action path\n * a page's dispatch takes — so the switch, the conditions, the plan gate and\n * the month's run allowance are the ones a real run meets, the run is metered\n * on the site's `actionRuns`, and it lands in the action's Runs history like\n * any other. It is marked rather than simulated: see `actionTestRunPayload`.\n * When a gate stops it, the refusal names the gate, because a test that ran\n * nothing must not read as one that ran.\n *\n * Only an in-page trigger: a server event (a form submitted, an order placed)\n * carries fields no sample stands in for, and its runs come from the doors\n * that raise it.\n */\n\nimport {\n type HostAction,\n hostRoleCanPublish,\n isSiteEventType,\n type PluginApiHandler,\n planLabelGrantingFeature,\n} from '@aglyn/aglyn/server'\n/*\n * The MODULES, not the barrel, as for the org automation doors beside this\n * one: `@aglyn/tenant-data-admin`'s index reaches the Next render cache.\n */\nimport { isDocumentId } from '@aglyn/tenant-data-admin/server/document-id'\nimport firebaseAdmin from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { consumeRateLimit } from '@aglyn/tenant-data-admin/server/rate-limit-store'\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\nimport type {\n SingleActionOutcome,\n SingleActionSkip,\n} from '../engine/run-event-actions'\nimport { actionTestRunPayload } from '../model/action-test-run'\n\nexport { ACTION_TEST_RUN_API_ROUTE } from '../model/action-test-run'\n\n/**\n * Test runs one person may start on one site in a minute.\n *\n * Every test is a real run with real sends, so this is the feature's own\n * bucket beneath the console dispatcher's general one: a person iterating on\n * an action tests every few seconds at most, and past this is a script.\n */\nexport const ACTION_TEST_RUNS_PER_MINUTE = 10\n\nconst ACTION_TEST_RUN_WINDOW_MS = 60_000\n\n/** What the door reaches outside this module; specs hand in their own. */\nexport interface ActionTestRunDeps {\n firestore(): FirebaseFirestore.Firestore\n verifyIdToken(token: string): Promise<{ uid: string; staff?: unknown }>\n consumeRateLimit(\n key: string,\n options: { limit: number; windowMs: number },\n ): Promise<{ allowed: boolean; resetMs: number }>\n runAction(\n hostId: string,\n actionId: string,\n event: string,\n payload: HostEventPayload,\n ): Promise<SingleActionOutcome>\n now(): number\n}\n\nfunction defaultDeps(): ActionTestRunDeps {\n return {\n firestore: () =>\n firebaseAdmin.app().firestore() as unknown as FirebaseFirestore.Firestore,\n verifyIdToken: async (token) => {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(token)\n return { uid: decoded.uid, staff: decoded['staff'] }\n },\n consumeRateLimit: (key, options) => consumeRateLimit(key, options),\n // Imported by the first test run rather than with the console surface,\n // as the host-event listener imports it.\n runAction: async (hostId, actionId, event, payload) => {\n const { runSingleActionOutcome } = await import('../engine/run-event-actions')\n return await runSingleActionOutcome(hostId, actionId, event, payload)\n },\n now: () => Date.now(),\n }\n}\n\n/** The bearer token a request carries, or null. */\nfunction bearerToken(headers: Partial<Record<string, string | string[]>>): string | null {\n const authorization = String(headers['authorization'] ?? '')\n return authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length) || null\n : null\n}\n\n/** The status and words for each gate that can stop a test run. */\nfunction refusalFor(\n skipped: SingleActionSkip,\n outcome: SingleActionOutcome,\n payload: HostEventPayload,\n): { status: number; error: string } {\n switch (skipped) {\n case 'missing':\n return { status: 404, error: 'That action no longer exists' }\n case 'disabled':\n return { status: 409, error: 'Switch the action on to test it' }\n case 'event':\n return {\n status: 409,\n error: 'The action changed while it was being tested — try again',\n }\n case 'conditions':\n return {\n status: 422,\n error:\n `Nothing ran: a test run on ${String(payload['path'])} does not ` +\n 'meet this action’s conditions',\n }\n case 'plan':\n return {\n status: 403,\n error:\n `Actions need the ${planLabelGrantingFeature('actions') ?? 'Pro'} ` +\n 'plan — see Billing to upgrade',\n }\n case 'allowance':\n return {\n status: 402,\n error: `This site has used its ${outcome.limit} action runs for the month`,\n }\n default:\n return { status: 500, error: 'The test run could not be completed. Try again.' }\n }\n}\n\n/**\n * `automations/actions/test-run`: run one of a site's actions now.\n *\n * Body `{ hostId, actionId, payload? }`. Answers `{ ok: true, alerts }` when\n * the action's steps ran — the site alerts they produced, for the card to show\n * — and otherwise an `error` saying what stopped it.\n */\nexport function createActionTestRunHandler(\n overrides: Partial<ActionTestRunDeps> = {},\n): PluginApiHandler {\n return async (req, res) => {\n const deps = { ...defaultDeps(), ...overrides }\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const actionId = String(req.body?.actionId ?? '')\n if (!isDocumentId(hostId)) {\n return res.status(400).json({ error: 'Invalid hostId' })\n }\n if (!isDocumentId(actionId)) {\n return res.status(400).json({ error: 'Invalid actionId' })\n }\n const token = bearerToken(req.headers)\n if (!token) return res.status(401).json({ error: 'Unauthenticated' })\n\n let caller: { uid: string; staff?: unknown }\n try {\n caller = await deps.verifyIdToken(token)\n } catch (error) {\n // A refused credential is the caller's 401; a failure to check one is\n // ours and keeps a 5xx (AGL-2852).\n if (isRefusedIdToken(error)) {\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n console.error('[workflows] test run could not verify the caller', error)\n return res\n .status(500)\n .json({ error: 'The sign-in could not be checked. Try again.' })\n }\n\n try {\n const firestore = deps.firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n if (!host.exists) return res.status(404).json({ error: 'Unknown site' })\n const role = (host.get('memberRoles') ?? {})[caller.uid]\n if (caller.staff !== true && !hostRoleCanPublish(role)) {\n return res\n .status(403)\n .json({ error: 'Only a site admin or editor can test an action' })\n }\n\n const rate = await deps.consumeRateLimit(\n `workflows-test-run:${hostId}:${caller.uid}`,\n { limit: ACTION_TEST_RUNS_PER_MINUTE, windowMs: ACTION_TEST_RUN_WINDOW_MS },\n )\n if (!rate.allowed) {\n res.setHeader(\n 'Retry-After',\n String(Math.max(1, Math.ceil((rate.resetMs - deps.now()) / 1000))),\n )\n return res\n .status(429)\n .json({ error: 'Too many test runs — wait a minute and try again' })\n }\n\n const stored = await hostRef.collection('actions').doc(actionId).get()\n if (!stored.exists || stored.get('deletedAt')) {\n return res.status(404).json({ error: 'That action no longer exists' })\n }\n const event = String(\n (stored.data() as HostAction | undefined)?.trigger?.event ?? '',\n )\n if (!isSiteEventType(event)) {\n return res.status(400).json({\n error: 'Only an action with an in-page trigger can be tested here',\n })\n }\n\n const payload = actionTestRunPayload(req.body?.payload)\n const outcome = await deps.runAction(hostId, actionId, event, payload)\n if (outcome.ran) {\n return res.status(200).json({ ok: true, alerts: outcome.alerts })\n }\n const refusal = refusalFor(outcome.skipped ?? 'failed', outcome, payload)\n return res.status(refusal.status).json({ error: refusal.error })\n } catch (error) {\n console.error('[workflows] test run failed', hostId, actionId, error)\n return res\n .status(500)\n .json({ error: 'The test run could not be completed. Try again.' })\n }\n }\n}\n\nexport const actionTestRunHandler = createActionTestRunHandler()\n"],"names":["hostRoleCanPublish","isSiteEventType","planLabelGrantingFeature","isDocumentId","firebaseAdmin","isRefusedIdToken","consumeRateLimit","actionTestRunPayload","ACTION_TEST_RUN_API_ROUTE","ACTION_TEST_RUNS_PER_MINUTE","ACTION_TEST_RUN_WINDOW_MS","defaultDeps","firestore","app","verifyIdToken","token","decoded","auth","uid","staff","key","options","runAction","hostId","actionId","event","payload","runSingleActionOutcome","now","Date","bearerToken","headers","authorization","String","startsWith","slice","length","refusalFor","skipped","outcome","status","error","limit","createActionTestRunHandler","overrides","req","res","deps","method","json","body","caller","console","host","stored","hostRef","collection","doc","get","exists","role","rate","windowMs","allowed","setHeader","Math","max","ceil","resetMs","data","trigger","ran","ok","alerts","refusal","actionTestRunHandler"],"mappings":"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"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* does: the automation draft writer another plugin reaches through the core's
|
|
23
23
|
* resource-drafts seam, which the AI jobs that write through it — inline at a
|
|
24
24
|
* door and on the beat — find registered in the console process alone
|
|
25
|
-
* (AGL-3026);
|
|
26
|
-
* (AGL-3302).
|
|
25
|
+
* (AGL-3026); the doors that write the organization's automations
|
|
26
|
+
* (AGL-3302); and the Actions card's test run (AGL-3309).
|
|
27
27
|
*/
|
|
28
28
|
export declare function registerWorkflowsConsoleApi(): void;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/ import { registerPluginApiRoute } from "@aglyn/aglyn/server";
|
|
17
17
|
import { registerWorkflowsServerDeclarations } from "./declarations.server.js";
|
|
18
18
|
import { registerAutomationDraftWriter } from "./server-automation-drafts.js";
|
|
19
|
+
import { ACTION_TEST_RUN_API_ROUTE, actionTestRunHandler } from "./server/action-test-run-route.js";
|
|
19
20
|
import { ORG_AUTOMATION_API_ROUTES, orgAutomationPauseHandler, orgAutomationsManageHandler, orgAutomationsManageSubject } from "./server/org-automations-routes.js";
|
|
20
21
|
/**
|
|
21
22
|
* The workflows plugin's console server surface (AGL-2919).
|
|
@@ -25,8 +26,8 @@ import { ORG_AUTOMATION_API_ROUTES, orgAutomationPauseHandler, orgAutomationsMan
|
|
|
25
26
|
* does: the automation draft writer another plugin reaches through the core's
|
|
26
27
|
* resource-drafts seam, which the AI jobs that write through it — inline at a
|
|
27
28
|
* door and on the beat — find registered in the console process alone
|
|
28
|
-
* (AGL-3026);
|
|
29
|
-
* (AGL-3302).
|
|
29
|
+
* (AGL-3026); the doors that write the organization's automations
|
|
30
|
+
* (AGL-3302); and the Actions card's test run (AGL-3309).
|
|
30
31
|
*/ export function registerWorkflowsConsoleApi() {
|
|
31
32
|
// The console raises host events too — a CRM stage moved, a deal won, a
|
|
32
33
|
// task completed — so the engine's listener is registered here as well as
|
|
@@ -40,6 +41,8 @@ import { ORG_AUTOMATION_API_ROUTES, orgAutomationPauseHandler, orgAutomationsMan
|
|
|
40
41
|
});
|
|
41
42
|
// A site's own pause names the site, and is gated by that site's switch.
|
|
42
43
|
registerPluginApiRoute(ORG_AUTOMATION_API_ROUTES.pause, orgAutomationPauseHandler);
|
|
44
|
+
// So does a test run: a site with Automation switched off has no such door.
|
|
45
|
+
registerPluginApiRoute(ACTION_TEST_RUN_API_ROUTE, actionTestRunHandler);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
//# sourceMappingURL=server-console.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/server-console.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerWorkflowsServerDeclarations } from './declarations.server'\nimport { registerAutomationDraftWriter } from './server-automation-drafts'\nimport {\n ORG_AUTOMATION_API_ROUTES,\n orgAutomationPauseHandler,\n orgAutomationsManageHandler,\n orgAutomationsManageSubject,\n} from './server/org-automations-routes'\n\n/**\n * The workflows plugin's console server surface (AGL-2919).\n *\n * The tenant surface (`registerWorkflowsApi`) serves the inbound webhook a\n * visitor-facing integration calls. This one serves what only the console\n * does: the automation draft writer another plugin reaches through the core's\n * resource-drafts seam, which the AI jobs that write through it — inline at a\n * door and on the beat — find registered in the console process alone\n * (AGL-3026);
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/server-console.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerWorkflowsServerDeclarations } from './declarations.server'\nimport { registerAutomationDraftWriter } from './server-automation-drafts'\nimport {\n ACTION_TEST_RUN_API_ROUTE,\n actionTestRunHandler,\n} from './server/action-test-run-route'\nimport {\n ORG_AUTOMATION_API_ROUTES,\n orgAutomationPauseHandler,\n orgAutomationsManageHandler,\n orgAutomationsManageSubject,\n} from './server/org-automations-routes'\n\n/**\n * The workflows plugin's console server surface (AGL-2919).\n *\n * The tenant surface (`registerWorkflowsApi`) serves the inbound webhook a\n * visitor-facing integration calls. This one serves what only the console\n * does: the automation draft writer another plugin reaches through the core's\n * resource-drafts seam, which the AI jobs that write through it — inline at a\n * door and on the beat — find registered in the console process alone\n * (AGL-3026); the doors that write the organization's automations\n * (AGL-3302); and the Actions card's test run (AGL-3309).\n */\nexport function registerWorkflowsConsoleApi(): void {\n // The console raises host events too — a CRM stage moved, a deal won, a\n // task completed — so the engine's listener is registered here as well as\n // at boot (see `declarations.server.ts`).\n registerWorkflowsServerDeclarations()\n registerAutomationDraftWriter()\n // The organization hub names its org and no site, so the route names that\n // org as its subject for the release gate.\n registerPluginApiRoute(\n ORG_AUTOMATION_API_ROUTES.manage,\n orgAutomationsManageHandler,\n { subject: orgAutomationsManageSubject },\n )\n // A site's own pause names the site, and is gated by that site's switch.\n registerPluginApiRoute(ORG_AUTOMATION_API_ROUTES.pause, orgAutomationPauseHandler)\n // So does a test run: a site with Automation switched off has no such door.\n registerPluginApiRoute(ACTION_TEST_RUN_API_ROUTE, actionTestRunHandler)\n}\n"],"names":["registerPluginApiRoute","registerWorkflowsServerDeclarations","registerAutomationDraftWriter","ACTION_TEST_RUN_API_ROUTE","actionTestRunHandler","ORG_AUTOMATION_API_ROUTES","orgAutomationPauseHandler","orgAutomationsManageHandler","orgAutomationsManageSubject","registerWorkflowsConsoleApi","manage","subject","pause"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,sBAAsB,QAAQ,sBAAqB;AAC5D,SAASC,mCAAmC,QAAQ,2BAAuB;AAC3E,SAASC,6BAA6B,QAAQ,gCAA4B;AAC1E,SACEC,yBAAyB,EACzBC,oBAAoB,QACf,oCAAgC;AACvC,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,2BAA2B,EAC3BC,2BAA2B,QACtB,qCAAiC;AAExC;;;;;;;;;;CAUC,GACD,OAAO,SAASC;IACd,wEAAwE;IACxE,0EAA0E;IAC1E,0CAA0C;IAC1CR;IACAC;IACA,0EAA0E;IAC1E,2CAA2C;IAC3CF,uBACEK,0BAA0BK,MAAM,EAChCH,6BACA;QAAEI,SAASH;IAA4B;IAEzC,yEAAyE;IACzER,uBAAuBK,0BAA0BO,KAAK,EAAEN;IACxD,4EAA4E;IAC5EN,uBAAuBG,2BAA2BC;AACpD"}
|