@aglyn/plugins-workflows 1.0.0-beta.182 → 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.
@@ -14,7 +14,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { ACTION_MAX_EVENT_DEPTH, ACTION_MAX_STEPS, checkEntitlement, planLabelGrantingFeature, checkQuota, WEBHOOK_URL_PATTERN, evaluateExpression, evaluateStepGuard, evaluateTriggerConditions, FLOW_TIMED_OUT_FIELD, flowEmailTopicId, hostPublicOrigin, isClientActionStep, isCrmActionStep, isFlowSuspendingStep, buildDatasetRecordValues, datasetDisplayName, contactCampaignFieldPath, datasetIntegrityFields, datasetIntegrityUpdate, describeStepOutcome, effectiveDatasetModel, normalizeTriggerConditions, resolveOrgEntitlements, runWorkflow, WORKFLOW_MAX_STEPS } from "@aglyn/aglyn/server";
17
+ */ import { ACTION_MAX_EVENT_DEPTH, ACTION_MAX_STEPS, checkEntitlement, consentGroupForHost, planLabelGrantingFeature, checkQuota, WEBHOOK_URL_PATTERN, evaluateExpression, evaluateStepGuard, evaluateTriggerConditions, FLOW_TIMED_OUT_FIELD, flowEmailTopicId, hostPublicOrigin, isClientActionStep, isCrmActionStep, isFlowSuspendingStep, buildDatasetRecordValues, datasetDisplayName, contactCampaignFieldPath, datasetIntegrityFields, datasetIntegrityUpdate, describeStepOutcome, effectiveDatasetModel, normalizeTriggerConditions, resolveOrgEntitlements, runWorkflow, WORKFLOW_MAX_STEPS } from "@aglyn/aglyn/server";
18
18
  import { isDeferrableSendResult, isEmailConfigured, sendEmail, sendFailureReason } from "@aglyn/shared-util-email";
19
19
  import { dataStorageRefusal, enrollListMember, firebaseAdmin, flowEmailRefusal, getOrgForHost, hostSendingIdentity, meterHostEmail, notifyHostManagers, consentGroupForSite, orgDataCollectionForHost, orgDataQueryForHost, resolveOrgIdForHost } from "@aglyn/tenant-data-admin";
20
20
  import { announceDatasetRecordChange } from "@aglyn/tenant-data-admin/server/dataset-live-pages";
@@ -465,7 +465,7 @@ async function datasetAppendRefusal(env, datasetRef) {
465
465
  link: `/${hostId}`
466
466
  }));
467
467
  } else if (step.type === 'sendEmail') {
468
- var _payload_, _hostPublicOrigin, _step_topicId, _step_subject, _step_body;
468
+ var _payload_, _hostPublicOrigin, _step_topicId, _step_subject, _step_body, _env_org;
469
469
  var _step_toField, _this;
470
470
  const to = String((_payload_ = payload[((_step_toField = step.toField) == null ? void 0 : _step_toField.trim()) || 'email']) != null ? _payload_ : '').trim();
471
471
  if (!isEmailConfigured()) return failed('email is not configured');
@@ -572,10 +572,14 @@ async function datasetAppendRefusal(env, datasetRef) {
572
572
  } : {}, {
573
573
  // `topicId` is `''` for a step that belongs to no stream, which
574
574
  // every reader of it treats as absent — see `flowEmailTopicId`.
575
+ // The consent group comes off the org the run already holds, so the
576
+ // gate honors an unsubscribe from any site of a declared group —
577
+ // the sender this site mails as — at no extra read.
575
578
  marketing: {
576
579
  hostId,
577
580
  siteBase,
578
- topicId
581
+ topicId,
582
+ consentHostIds: consentGroupForHost((_env_org = env.org) != null ? _env_org : null, hostId).hostIds
579
583
  }
580
584
  }));
581
585
  /*
@@ -1249,27 +1253,45 @@ async function datasetAppendRefusal(env, datasetRef) {
1249
1253
  * event name would wrongly fire sibling actions with different
1250
1254
  * thresholds. Same gates and metering as the event runner.
1251
1255
  */ export async function runSingleAction(hostId, actionId, event, payload = {}) {
1256
+ return (await runSingleActionOutcome(hostId, actionId, event, payload)).alerts;
1257
+ }
1258
+ /**
1259
+ * {@link runSingleAction}, answering what it did as well as the alerts: whether
1260
+ * the steps ran and, when they did not, which gate stopped them.
1261
+ *
1262
+ * One body for both, so the console's test run meets exactly the gates a
1263
+ * page's dispatch meets — the switch, the event, the conditions, the plan and
1264
+ * the month's allowance — and is metered and recorded the same way.
1265
+ */ export async function runSingleActionOutcome(hostId, actionId, event, payload = {}) {
1252
1266
  const alerts = [];
1267
+ const skipped = (reason, limit)=>_extends({
1268
+ ran: false,
1269
+ skipped: reason,
1270
+ alerts
1271
+ }, limit === undefined ? {} : {
1272
+ limit
1273
+ });
1253
1274
  try {
1254
1275
  var _ref, _ref1, _ref2;
1255
1276
  var _action_trigger;
1256
1277
  const firestore = firebaseAdmin.app().firestore();
1257
1278
  const hostRef = firestore.collection('hosts').doc(hostId);
1258
1279
  const doc = await hostRef.collection('actions').doc(actionId).get();
1259
- if (!doc.exists || doc.get('deletedAt') || doc.get('enabled') === false) {
1260
- return alerts;
1261
- }
1280
+ if (!doc.exists || doc.get('deletedAt')) return skipped('missing');
1281
+ if (doc.get('enabled') === false) return skipped('disabled');
1262
1282
  const action = doc.data();
1263
1283
  // Only site-event actions may be dispatched externally — server
1264
1284
  // events flow through their own emitters.
1265
- if (String((_ref = (_action_trigger = action.trigger) == null ? void 0 : _action_trigger.event) != null ? _ref : '') !== String(event)) return alerts;
1285
+ if (String((_ref = (_action_trigger = action.trigger) == null ? void 0 : _action_trigger.event) != null ? _ref : '') !== String(event)) {
1286
+ return skipped('event');
1287
+ }
1266
1288
  // Structured payload conditions (AGL-557; AND/OR chaining AGL-565):
1267
1289
  // the single-action dispatch path honors them too, so
1268
1290
  // client-evaluated triggers can't bypass them.
1269
1291
  if (!evaluateTriggerConditions(action.trigger, _extends({
1270
1292
  event
1271
1293
  }, payload))) {
1272
- return alerts;
1294
+ return skipped('conditions');
1273
1295
  }
1274
1296
  const monthKey = new Date().toISOString().slice(0, 7);
1275
1297
  const runCounterRef = hostRef.collection('counters').doc('actionRuns');
@@ -1280,13 +1302,13 @@ async function datasetAppendRefusal(env, datasetRef) {
1280
1302
  {
1281
1303
  var _counterSnapshot_get;
1282
1304
  const org = owner == null ? void 0 : owner.org;
1283
- if (!checkEntitlement(org, 'actions')) return alerts;
1305
+ if (!checkEntitlement(org, 'actions')) return skipped('plan');
1284
1306
  webhooksAllowed = checkEntitlement(org, 'webhooks');
1285
1307
  crmAllowed = checkEntitlement(org, 'crm');
1286
1308
  const limit = resolveOrgEntitlements(org).actionRunsPerMonth;
1287
1309
  const counterSnapshot = await runCounterRef.get();
1288
1310
  const used = Number((_counterSnapshot_get = counterSnapshot.get(monthKey)) != null ? _counterSnapshot_get : 0);
1289
- if (used + 1 > limit) return alerts;
1311
+ if (used + 1 > limit) return skipped('allowance', limit);
1290
1312
  }
1291
1313
  const env = {
1292
1314
  hostId,
@@ -1307,10 +1329,15 @@ async function datasetAppendRefusal(env, datasetRef) {
1307
1329
  }, {
1308
1330
  merge: true
1309
1331
  }).catch(()=>undefined);
1332
+ return {
1333
+ ran: true,
1334
+ skipped: null,
1335
+ alerts
1336
+ };
1310
1337
  } catch (error) {
1311
1338
  console.error('runSingleAction failed', hostId, actionId, error);
1339
+ return skipped('failed');
1312
1340
  }
1313
- return alerts;
1314
1341
  }
1315
1342
  /**
1316
1343
  * Ends an enrollment its automation may no longer run, and says why in the