@aglyn/tenant-data-admin 1.0.0-beta.218 → 1.0.0-beta.220

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.
Files changed (156) hide show
  1. package/package.json +8 -8
  2. package/src/lib/server/api-http.js +5 -1
  3. package/src/lib/server/api-http.js.map +1 -1
  4. package/src/lib/server/api-v1-description.d.ts +112 -0
  5. package/src/lib/server/api-v1-description.js +126 -0
  6. package/src/lib/server/api-v1-description.js.map +1 -0
  7. package/src/lib/server/api-v1-kit.d.ts +163 -0
  8. package/src/lib/server/api-v1-kit.js +191 -0
  9. package/src/lib/server/api-v1-kit.js.map +1 -0
  10. package/src/lib/server/api-v1-resources.d.ts +143 -0
  11. package/src/lib/server/api-v1-resources.js +125 -0
  12. package/src/lib/server/api-v1-resources.js.map +1 -0
  13. package/src/lib/server/campaign-conversion-attribution.d.ts +1 -1
  14. package/src/lib/server/campaign-conversion-attribution.js +3 -3
  15. package/src/lib/server/campaign-conversion-attribution.js.map +1 -1
  16. package/src/lib/server/card-payment-velocity.d.ts +9 -0
  17. package/src/lib/server/card-payment-velocity.js +28 -2
  18. package/src/lib/server/card-payment-velocity.js.map +1 -1
  19. package/src/lib/server/consent-group-carry.js +1 -1
  20. package/src/lib/server/consent-group-carry.js.map +1 -1
  21. package/src/lib/server/consent-group-change.d.ts +2 -1
  22. package/src/lib/server/consent-group-change.js +43 -18
  23. package/src/lib/server/consent-group-change.js.map +1 -1
  24. package/src/lib/server/contact-email-index.d.ts +22 -0
  25. package/src/lib/server/contact-email-index.js +108 -0
  26. package/src/lib/server/contact-email-index.js.map +1 -1
  27. package/src/lib/server/crm-email-activity.d.ts +1 -1
  28. package/src/lib/server/crm-email-activity.js +1 -1
  29. package/src/lib/server/crm-email-activity.js.map +1 -1
  30. package/src/lib/server/crm-records.d.ts +9 -1
  31. package/src/lib/server/crm-records.js +19 -2
  32. package/src/lib/server/crm-records.js.map +1 -1
  33. package/src/lib/server/cron-auth.d.ts +44 -0
  34. package/src/lib/server/cron-auth.js +76 -0
  35. package/src/lib/server/cron-auth.js.map +1 -0
  36. package/src/lib/server/dataset-live-pages.js +6 -6
  37. package/src/lib/server/dataset-live-pages.js.map +1 -1
  38. package/src/lib/server/duplicate-resource.d.ts +26 -0
  39. package/src/lib/server/duplicate-resource.js +47 -25
  40. package/src/lib/server/duplicate-resource.js.map +1 -1
  41. package/src/lib/server/dynamic-list-materialize.js +6 -3
  42. package/src/lib/server/dynamic-list-materialize.js.map +1 -1
  43. package/src/lib/server/email-flow-gate.js +2 -2
  44. package/src/lib/server/email-flow-gate.js.map +1 -1
  45. package/src/lib/server/email-suppression.d.ts +11 -0
  46. package/src/lib/server/email-suppression.js +37 -2
  47. package/src/lib/server/email-suppression.js.map +1 -1
  48. package/src/lib/server/email-unsubscribe-link.d.ts +8 -5
  49. package/src/lib/server/email-unsubscribe-link.js +7 -7
  50. package/src/lib/server/email-unsubscribe-link.js.map +1 -1
  51. package/src/lib/server/erase.d.ts +19 -33
  52. package/src/lib/server/erase.js +37 -162
  53. package/src/lib/server/erase.js.map +1 -1
  54. package/src/lib/server/host-email-tokens.js +1 -1
  55. package/src/lib/server/host-email-tokens.js.map +1 -1
  56. package/src/lib/server/host-memberships.js +3 -3
  57. package/src/lib/server/host-memberships.js.map +1 -1
  58. package/src/lib/server/host-visitor-records.d.ts +32 -4
  59. package/src/lib/server/host-visitor-records.js +73 -14
  60. package/src/lib/server/host-visitor-records.js.map +1 -1
  61. package/src/lib/server/hosted-page-review.d.ts +27 -0
  62. package/src/lib/server/hosted-page-review.js +149 -12
  63. package/src/lib/server/hosted-page-review.js.map +1 -1
  64. package/src/lib/server/media-cdn-rate-limit.d.ts +6 -3
  65. package/src/lib/server/media-cdn-rate-limit.js +6 -3
  66. package/src/lib/server/media-cdn-rate-limit.js.map +1 -1
  67. package/src/lib/server/member-email-aliases.d.ts +10 -2
  68. package/src/lib/server/member-email-aliases.js +26 -2
  69. package/src/lib/server/member-email-aliases.js.map +1 -1
  70. package/src/lib/server/new-account-announcement.d.ts +62 -0
  71. package/src/lib/server/new-account-announcement.js +77 -0
  72. package/src/lib/server/new-account-announcement.js.map +1 -0
  73. package/src/lib/server/notifications.d.ts +22 -2
  74. package/src/lib/server/notifications.js +40 -3
  75. package/src/lib/server/notifications.js.map +1 -1
  76. package/src/lib/server/operator-alerts.d.ts +7 -1
  77. package/src/lib/server/operator-alerts.js +5 -3
  78. package/src/lib/server/operator-alerts.js.map +1 -1
  79. package/src/lib/server/org-containers.d.ts +66 -0
  80. package/src/lib/server/org-containers.js +95 -0
  81. package/src/lib/server/org-containers.js.map +1 -0
  82. package/src/lib/server/org-member-notice.js +3 -2
  83. package/src/lib/server/org-member-notice.js.map +1 -1
  84. package/src/lib/server/payment-fraud-signal.js +5 -1
  85. package/src/lib/server/payment-fraud-signal.js.map +1 -1
  86. package/src/lib/server/{connect-payout-failure.d.ts → payment-provider-stripe-connect-payouts.d.ts} +3 -0
  87. package/src/lib/server/{connect-payout-failure.js → payment-provider-stripe-connect-payouts.js} +36 -2
  88. package/src/lib/server/payment-provider-stripe-connect-payouts.js.map +1 -0
  89. package/src/lib/server/{connect-account-status.d.ts → payment-provider-stripe-connect-status.d.ts} +3 -0
  90. package/src/lib/server/{connect-account-status.js → payment-provider-stripe-connect-status.js} +1 -1
  91. package/src/lib/server/payment-provider-stripe-connect-status.js.map +1 -0
  92. package/src/lib/server/{stripe-account-mode.d.ts → payment-provider-stripe-connect.d.ts} +45 -70
  93. package/src/lib/server/{stripe-account-mode.js → payment-provider-stripe-connect.js} +78 -52
  94. package/src/lib/server/payment-provider-stripe-connect.js.map +1 -0
  95. package/src/lib/server/payment-provider.d.ts +173 -0
  96. package/src/lib/server/payment-provider.js +70 -0
  97. package/src/lib/server/payment-provider.js.map +1 -0
  98. package/src/lib/server/payment-risk-record.d.ts +1 -1
  99. package/src/lib/server/payment-risk-record.js +3 -1
  100. package/src/lib/server/payment-risk-record.js.map +1 -1
  101. package/src/lib/server/personal-data-export.js +1 -1
  102. package/src/lib/server/personal-data-export.js.map +1 -1
  103. package/src/lib/server/platform-marketing-consent.d.ts +1 -1
  104. package/src/lib/server/platform-marketing-consent.js +1 -1
  105. package/src/lib/server/platform-marketing-consent.js.map +1 -1
  106. package/src/lib/server/rate-limit-store.js +13 -3
  107. package/src/lib/server/rate-limit-store.js.map +1 -1
  108. package/src/lib/server/realm-plugins.js +8 -3
  109. package/src/lib/server/realm-plugins.js.map +1 -1
  110. package/src/lib/server/retained-refusals.d.ts +142 -0
  111. package/src/lib/server/retained-refusals.js +325 -0
  112. package/src/lib/server/retained-refusals.js.map +1 -0
  113. package/src/lib/server/risk-notice.d.ts +32 -1
  114. package/src/lib/server/risk-notice.js +90 -28
  115. package/src/lib/server/risk-notice.js.map +1 -1
  116. package/src/lib/server/sending-domains.d.ts +17 -0
  117. package/src/lib/server/sending-domains.js +28 -4
  118. package/src/lib/server/sending-domains.js.map +1 -1
  119. package/src/lib/server/sso-enforcement.js +7 -2
  120. package/src/lib/server/sso-enforcement.js.map +1 -1
  121. package/src/lib/server/staff-alert-email.js +4 -2
  122. package/src/lib/server/staff-alert-email.js.map +1 -1
  123. package/src/lib/server/test-firestore-queries.d.ts +7 -0
  124. package/src/lib/server/test-firestore-queries.js +70 -27
  125. package/src/lib/server/test-firestore-queries.js.map +1 -1
  126. package/src/lib/server/{email-topic-confirmation.js → topic-subscriptions.js} +15 -11
  127. package/src/lib/server/topic-subscriptions.js.map +1 -0
  128. package/src/lib/server/upsert-contact.d.ts +46 -1
  129. package/src/lib/server/upsert-contact.js +87 -20
  130. package/src/lib/server/upsert-contact.js.map +1 -1
  131. package/src/tenancy.d.ts +5 -8
  132. package/src/tenancy.js +12 -13
  133. package/src/tenancy.js.map +1 -1
  134. package/src/lib/server/connect-account-status.js.map +0 -1
  135. package/src/lib/server/connect-payout-failure.js.map +0 -1
  136. package/src/lib/server/contact-email-engagement.d.ts +0 -32
  137. package/src/lib/server/contact-email-engagement.js +0 -145
  138. package/src/lib/server/contact-email-engagement.js.map +0 -1
  139. package/src/lib/server/contact-lifecycle-floor.d.ts +0 -89
  140. package/src/lib/server/contact-lifecycle-floor.js +0 -91
  141. package/src/lib/server/contact-lifecycle-floor.js.map +0 -1
  142. package/src/lib/server/contact-merge.d.ts +0 -55
  143. package/src/lib/server/contact-merge.js +0 -288
  144. package/src/lib/server/contact-merge.js.map +0 -1
  145. package/src/lib/server/crm-booking-activity.d.ts +0 -86
  146. package/src/lib/server/crm-booking-activity.js +0 -257
  147. package/src/lib/server/crm-booking-activity.js.map +0 -1
  148. package/src/lib/server/crm-inbound-email.d.ts +0 -145
  149. package/src/lib/server/crm-inbound-email.js +0 -297
  150. package/src/lib/server/crm-inbound-email.js.map +0 -1
  151. package/src/lib/server/email-topic-confirmation.js.map +0 -1
  152. package/src/lib/server/form-dataset-binding-token.d.ts +0 -35
  153. package/src/lib/server/form-dataset-binding-token.js +0 -107
  154. package/src/lib/server/form-dataset-binding-token.js.map +0 -1
  155. package/src/lib/server/stripe-account-mode.js.map +0 -1
  156. /package/src/lib/server/{email-topic-confirmation.d.ts → topic-subscriptions.d.ts} +0 -0
@@ -57,8 +57,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
57
57
  * short "more are arriving" note in-app; when the hour closes,
58
58
  * {@link flushRiskNoticeDigests} (hourly cron, and the next notice after the
59
59
  * window) sends ONE summary. A workspace under attack gets a digest, not
60
- * five hundred emails. Locks, lifts and cancellations (`neverDigest`) always
61
- * go out on their own.
60
+ * five hundred emails. Locks, lifts, cancellations, disputes and card-testing
61
+ * warnings (`neverDigest`) always go out on their own.
62
62
  *
63
63
  * ## Never throws
64
64
  *
@@ -68,7 +68,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
68
68
  *=========================================*/ import { createHash } from "crypto";
69
69
  import { ABUSE_REPORT_COLLECTION, abuseReportContactEmail } from "@aglyn/aglyn/app-utils/abuse-report";
70
70
  import { DOCS_BASE_URL } from "@aglyn/aglyn/app-utils/docs-help";
71
- import { isRiskEventKind, renderOwnerRiskNotice, renderRiskNoticeText, renderStaffRiskNotice, resolveOwnerRiskActions, RISK_NOTICE_CATALOG, RISK_NOTICE_CLOSING_KINDS, RISK_NOTICE_DIGEST_EMAIL_KEY, RISK_NOTICE_HELP_PATH, RISK_NOTICE_WORKSPACE_BRANDED, riskNoticeEmailKey } from "@aglyn/shared-util-email/risk-notice-catalog";
71
+ import { PLATFORM_BRAND_MERGE_TOKENS } from "@aglyn/aglyn/app-utils/system-email-chrome";
72
+ import { isRiskEventKind, renderOwnerRiskNotice, renderRiskNoticeText, renderStaffRiskNotice, resolveOwnerRiskActions, RISK_NOTICE_CATALOG, RISK_NOTICE_CLOSING_KINDS, RISK_NOTICE_DIGEST_EMAIL_KEY, RISK_NOTICE_HELP_PATH, RISK_NOTICE_WORKSPACE_BRANDED, riskNoticeEmailKey, riskPayoutDelayText } from "@aglyn/shared-util-email/risk-notice-catalog";
72
73
  import { sendEmail as sendEmailImpl } from "@aglyn/shared-util-email";
73
74
  import { heldPageDetails, heldPageLabel, heldPageTargets, heldPageVisitorSentence, isOpenPageHoldStatus, isPageHoldNoticeKind, PAGE_HOLD_NOTICE_KINDS, pageHoldChip, parseHeldPageItemPath } from "@aglyn/shared-util-email/held-page";
74
75
  import { FieldValue } from "firebase-admin/firestore";
@@ -82,6 +83,14 @@ import { RISK_REVIEW_REQUESTED_EMAIL_KEY } from "@aglyn/shared-util-email/risk-n
82
83
  /** Admin-SDK only; no client rule opens it. One document per notice. */ export const RISK_NOTICE_COLLECTION = 'riskNotices';
83
84
  /** Admin-SDK only: each workspace's burst window and folded notices. */ export const RISK_NOTICE_LEDGER_COLLECTION = 'riskNoticeLedger';
84
85
  /** The in-app notification type every owner notice is written as. */ export const RISK_NOTICE_NOTIFICATION_TYPE = 'system.riskNotice';
86
+ /**
87
+ * The level an owner notice is drawn at (AGL-3437), from its kind's
88
+ * severity: a release or a lift is `info`, not the red of the hold it ends.
89
+ */ export const RISK_NOTICE_LEVELS = {
90
+ urgent: 'critical',
91
+ warning: 'warning',
92
+ info: 'info'
93
+ };
85
94
  /**
86
95
  * The burst allowance: how many notices one workspace's owners, and staff
87
96
  * about one workspace, receive one by one in {@link RISK_NOTICE_BURST.windowMs}
@@ -249,12 +258,19 @@ function consoleOrigin() {
249
258
  var _input_orgId;
250
259
  let orgId = (_input_orgId = input.orgId) != null ? _input_orgId : null;
251
260
  let hostSubdomain = null;
261
+ let siteLabel = null;
252
262
  let siteManagerUids = [];
253
263
  if (input.hostId) {
254
264
  const host = await firestore.collection('hosts').doc(input.hostId).get().catch(()=>null);
255
265
  if (host == null ? void 0 : host.exists) {
256
- var _host_get, _host_get1, _host_get2;
266
+ var _host_get, _map_find, _host_get1, _host_get2;
257
267
  hostSubdomain = String((_host_get = host.get('subdomain')) != null ? _host_get : '') || null;
268
+ const siteName = (_map_find = [
269
+ host.get('displayName'),
270
+ host.get('name'),
271
+ hostSubdomain
272
+ ].map((value)=>typeof value === 'string' ? value.trim() : '').find(Boolean)) != null ? _map_find : '';
273
+ siteLabel = siteName ? `the site "${siteName.replace(/"/g, '”')}"` : null;
258
274
  orgId = orgId != null ? orgId : String((_host_get1 = host.get('orgId')) != null ? _host_get1 : '') || null;
259
275
  const roles = (_host_get2 = host.get('memberRoles')) != null ? _host_get2 : {};
260
276
  siteManagerUids = Object.entries(roles).filter(([, role])=>role === 'admin' || role === 'editor').map(([uid])=>uid);
@@ -275,6 +291,7 @@ function consoleOrigin() {
275
291
  orgName,
276
292
  orgSlug,
277
293
  hostSubdomain,
294
+ siteLabel,
278
295
  siteManagerUids
279
296
  };
280
297
  }
@@ -285,6 +302,15 @@ function consoleOrigin() {
285
302
  */ export async function riskNoticeEmailBrand(kind, orgId) {
286
303
  return kind && RISK_NOTICE_WORKSPACE_BRANDED.has(kind) ? orgSystemEmailBrand(orgId) : systemEmailBrand(null);
287
304
  }
305
+ /**
306
+ * The product name a notice's own words use (`your Aglyn account`): the one
307
+ * the email it rides in is branded with, so the sentence and the header
308
+ * never disagree — the platform's for platform mail, a white-label
309
+ * workspace's own for the notices sent in its brand.
310
+ */ export function riskNoticeProductName(brand) {
311
+ var _brand_merge_brandproductName, _PLATFORM_BRAND_MERGE_TOKENS_brandproductName;
312
+ return String((_brand_merge_brandproductName = brand.merge['brand.productName']) != null ? _brand_merge_brandproductName : '').trim() || String((_PLATFORM_BRAND_MERGE_TOKENS_brandproductName = PLATFORM_BRAND_MERGE_TOKENS['brand.productName']) != null ? _PLATFORM_BRAND_MERGE_TOKENS_brandproductName : '').trim();
313
+ }
288
314
  /**
289
315
  * Renders one notice email through the System emails seam (AGL-3367): the
290
316
  * published design for the kind's own `risk-…` key, or its built-in copy,
@@ -429,7 +455,11 @@ function emptyDelivery(skipped = null) {
429
455
  if (owner.email) emails[owner.uid] = owner.email;
430
456
  }
431
457
  }
432
- if (definition.includeSiteManagers) for (const uid of siteManagerUids)uids.add(uid);
458
+ // A closing kind is shared by openings with different audiences, so it
459
+ // follows the one it closes: a site manager told a review opened is told
460
+ // it closed.
461
+ const audience = isRiskEventKind(input.openedAs) ? RISK_NOTICE_CATALOG[input.openedAs] : definition;
462
+ if (audience.includeSiteManagers) for (const uid of siteManagerUids)uids.add(uid);
433
463
  return {
434
464
  uids: [
435
465
  ...uids
@@ -469,7 +499,7 @@ function emptyDelivery(skipped = null) {
469
499
  const noticeId = riskNoticeId(riskNoticeDedupeKey(input, occurredAtMs));
470
500
  result.noticeId = noticeId;
471
501
  try {
472
- var _abuseReportContactEmail, _ref1, _input_reference, _ref2, _ref3, _input_amount, _context_orgName, _input_staffEvidence, _ref4, _input_reviewId, _input_hostId, _input_stripeUrl, _ref5, _ref6, _ownerActions_find;
502
+ var _abuseReportContactEmail, _ref1, _input_reference, _ref2, _ref3, _input_amount, _input_paymentEvent, _context_orgName, _input_staffEvidence, _ref4, _input_reviewId, _input_hostId, _input_stripeUrl, _ref5, _ref6, _ownerActions_find;
473
503
  var _input_item, _input_lock, _input_lock1, _input_item1, _input_lock2, _input_lock3;
474
504
  const context = await readContext(deps.firestore, input);
475
505
  const orgId = context.orgId;
@@ -480,8 +510,12 @@ function emptyDelivery(skipped = null) {
480
510
  * The values the OWNER half renders with. `staff.evidence` is absent by
481
511
  * construction: the owner record and the owner channels never hold it.
482
512
  */ const supportEmail = (_abuseReportContactEmail = abuseReportContactEmail('support')) != null ? _abuseReportContactEmail : '';
513
+ // The brand the email is sent in, read once: its product name is also a
514
+ // word in the notice (an account lock says whose account it is).
515
+ const brand = await riskNoticeEmailBrand(input.kind, orgId);
483
516
  const ownerValues = {
484
517
  'workspace.name': context.orgName,
518
+ 'site.label': context.siteLabel,
485
519
  'item.label': (_ref1 = (_input_item = input.item) == null ? void 0 : _input_item.label) != null ? _ref1 : null,
486
520
  occurredAt: formatRiskNoticeTime(occurredAtMs),
487
521
  reference: (_input_reference = input.reference) != null ? _input_reference : null,
@@ -489,7 +523,11 @@ function emptyDelivery(skipped = null) {
489
523
  'lock.message': (_ref2 = (_input_lock = input.lock) == null ? void 0 : _input_lock.message) != null ? _ref2 : null,
490
524
  'lock.affected': (_ref3 = (_input_lock1 = input.lock) == null ? void 0 : _input_lock1.affected) != null ? _ref3 : null,
491
525
  amount: (_input_amount = input.amount) != null ? _input_amount : null,
492
- 'evidence.dueBy': input.evidenceDueByMs ? formatRiskNoticeTime(input.evidenceDueByMs) : null
526
+ 'payment.event': (_input_paymentEvent = input.paymentEvent) != null ? _input_paymentEvent : null,
527
+ 'evidence.dueBy': input.evidenceDueByMs ? formatRiskNoticeTime(input.evidenceDueByMs) : null,
528
+ 'payout.delay': input.payout ? riskPayoutDelayText(input.payout) : null,
529
+ 'page.visitors': input.page ? heldPageVisitorSentence(input.page) : null,
530
+ 'brand.productName': riskNoticeProductName(brand) || null
493
531
  };
494
532
  const staffValues = _extends({}, ownerValues, {
495
533
  'workspace.name': (_context_orgName = context.orgName) != null ? _context_orgName : orgId,
@@ -578,6 +616,7 @@ function emptyDelivery(skipped = null) {
578
616
  type: RISK_NOTICE_NOTIFICATION_TYPE,
579
617
  title: owner.title,
580
618
  body: `${owner.summary} ${owner.meaning}`.slice(0, 1000),
619
+ level: RISK_NOTICE_LEVELS[definition.severity],
581
620
  link: (_ref9 = primary == null ? void 0 : primary.href) != null ? _ref9 : riskHoldsPath(noticeId)
582
621
  }, orgId ? {
583
622
  orgId
@@ -613,7 +652,7 @@ function emptyDelivery(skipped = null) {
613
652
  workspaceName: context.orgName,
614
653
  lockMessage: (_ref10 = (_input_lock4 = input.lock) == null ? void 0 : _input_lock4.message) != null ? _ref10 : null
615
654
  });
616
- const rendered = await renderRiskNoticeEmail(riskNoticeEmailKey(input.kind), merge, await riskNoticeEmailBrand(input.kind, orgId));
655
+ const rendered = await renderRiskNoticeEmail(riskNoticeEmailKey(input.kind), merge, brand);
617
656
  const delivery = await emailPeople(deps, recipients, rendered, supportEmail, {
618
657
  lockNotice: LOCK_NOTICE_KINDS.has(input.kind)
619
658
  });
@@ -629,6 +668,7 @@ function emptyDelivery(skipped = null) {
629
668
  await deps.notifyUsers(recipients.uids, _extends({
630
669
  type: RISK_NOTICE_NOTIFICATION_TYPE,
631
670
  title: 'More account notices are arriving',
671
+ level: 'warning',
632
672
  body: 'Several items on your workspace were held or flagged in the last hour. ' + 'We will send one summary instead of a message for each. Every item is listed on Holds & reviews.',
633
673
  link: riskHoldsPath()
634
674
  }, orgId ? {
@@ -818,18 +858,17 @@ function isAlreadyExists(error) {
818
858
  if (count > listed) lines.push(`• …and ${count - listed} more`);
819
859
  return lines.join('\n');
820
860
  };
861
+ // The workspace by name, for both halves: staff read a digest about
862
+ // `Harbor View`, not about a ledger key.
863
+ const context = orgId ? await readContext(deps.firestore, {
864
+ kind: 'email-held',
865
+ orgId
866
+ }) : {
867
+ orgName: null,
868
+ orgSlug: null
869
+ };
821
870
  if (taken.ownerCount > 0) {
822
871
  var _context_orgName;
823
- const context = orgId ? await readContext(deps.firestore, {
824
- kind: 'email-held',
825
- orgId
826
- }) : {
827
- orgName: null,
828
- orgSlug: null,
829
- siteManagerUids: [],
830
- orgId: null,
831
- hostSubdomain: null
832
- };
833
872
  const recipients = scope === 'user' ? {
834
873
  uids: [
835
874
  id
@@ -842,7 +881,7 @@ function isAlreadyExists(error) {
842
881
  uids: [],
843
882
  emails: {}
844
883
  };
845
- const title = `Summary: more items on ${(_context_orgName = context.orgName) != null ? _context_orgName : 'your workspace'} were held or flagged`;
884
+ const title = `Summary: more notices about ${(_context_orgName = context.orgName) != null ? _context_orgName : 'your workspace'} in the last hour`;
846
885
  const summary = summarize(taken.owners, taken.ownerCount);
847
886
  if (recipients.uids.length) {
848
887
  var _context_orgName1, _abuseReportContactEmail;
@@ -850,6 +889,7 @@ function isAlreadyExists(error) {
850
889
  type: RISK_NOTICE_NOTIFICATION_TYPE,
851
890
  title,
852
891
  body: summary.slice(0, 1000),
892
+ level: 'warning',
853
893
  link: riskHoldsPath()
854
894
  }, orgId ? {
855
895
  orgId
@@ -863,7 +903,7 @@ function isAlreadyExists(error) {
863
903
  });
864
904
  const rendered = await renderRiskNoticeEmail(RISK_NOTICE_DIGEST_EMAIL_KEY, {
865
905
  'notice.title': title,
866
- 'notice.summary': renderRiskNoticeText('In the last hour, more items on {{workspace.name}} were held or flagged than we send one by one:', {
906
+ 'notice.summary': renderRiskNoticeText('In the last hour, {{workspace.name}} had more notices than we send one by one. Each one, with its status and anything that is due, is listed on Holds & reviews. By title:', {
867
907
  'workspace.name': context.orgName
868
908
  }),
869
909
  'notice.meaning': summary,
@@ -881,10 +921,12 @@ function isAlreadyExists(error) {
881
921
  }
882
922
  }
883
923
  if (taken.staffCount > 0) {
924
+ var _context_orgName2;
925
+ const about = context.orgName ? `workspace ${context.orgName} (${orgId})` : scope === 'user' ? `account ${id}` : scope === 'host' ? `site ${id}` : ledgerId;
884
926
  await deps.notifyStaff({
885
927
  type: 'system.abuseReportUrgent',
886
- title: `Digest: ${taken.staffCount} more risk alerts on ${ledgerId}`,
887
- body: summarize(taken.staff, taken.staffCount).slice(0, 1000),
928
+ title: `Digest: ${taken.staffCount} more risk alerts on ${(_context_orgName2 = context.orgName) != null ? _context_orgName2 : ledgerId}`,
929
+ body: (`${taken.staffCount} more risk alerts on ${about} arrived in the last hour ` + `and were batched here instead of sent one by one:\n` + summarize(taken.staff, taken.staffCount)).slice(0, 1000),
888
930
  link: '/admin/abuse-reports'
889
931
  });
890
932
  }
@@ -942,7 +984,8 @@ function isAlreadyExists(error) {
942
984
  label: stamp.itemLabel,
943
985
  path: (_stamp_itemPath = stamp.itemPath) != null ? _stamp_itemPath : null
944
986
  } : null,
945
- dedupeKey: `${closing}:${input.reviewId}`
987
+ dedupeKey: `${closing}:${input.reviewId}`,
988
+ openedAs: stamp.kind
946
989
  }, overrides);
947
990
  } catch (error) {
948
991
  console.error('[risk-notice] the closing notice failed', error);
@@ -957,7 +1000,8 @@ function isAlreadyExists(error) {
957
1000
  * here, so no request can release a hold or lift a lock. Staff see the
958
1001
  * request as a note on the row, and are notified with a link to it.
959
1002
  */ export async function requestRiskReview(input, overrides = {}) {
960
- var _input_note, _notice_get;
1003
+ var _input_note, _notice_get, _ref, _ref1, _ref2;
1004
+ var _notice_get1;
961
1005
  const deps = resolveDeps(overrides);
962
1006
  const note = String((_input_note = input.note) != null ? _input_note : '').trim().slice(0, RISK_REVIEW_NOTE_MAX);
963
1007
  if (note.length < 10) {
@@ -1031,23 +1075,41 @@ function isAlreadyExists(error) {
1031
1075
  error: 'We already have a request on this. You can add another in a few minutes.'
1032
1076
  };
1033
1077
  }
1078
+ // What the request is about, by name, for staff and for the person who
1079
+ // asked: the item in the owner's words and the workspace.
1080
+ const itemLabel = String((_ref = (_notice_get1 = notice.get('ownerValues')) == null ? void 0 : _notice_get1['item.label']) != null ? _ref : '').trim() || null;
1081
+ const org = await deps.firestore.collection('orgs').doc(input.orgId).get().catch(()=>null);
1082
+ const orgName = String((_ref1 = org == null ? void 0 : org.get('name')) != null ? _ref1 : '').trim() || null;
1083
+ const orgSlug = String((_ref2 = org == null ? void 0 : org.get('slug')) != null ? _ref2 : '').trim() || null;
1034
1084
  await deps.notifyStaff({
1035
1085
  type: 'system.abuseReportUrgent',
1036
1086
  title: `Review requested${outcome.reference ? ` — ${outcome.reference}` : ''}`,
1037
- body: `A workspace owner or admin asked for a review: "${note.slice(0, 280)}"`,
1087
+ body: `An owner or admin of ${orgName ? `${orgName} (${input.orgId})` : `workspace ${input.orgId}`} ` + `asked for a review of ${itemLabel != null ? itemLabel : 'a held or flagged item'} (${kind}): "${note.slice(0, 280)}"`,
1038
1088
  link: staffAbuseRowPath(reviewId)
1039
1089
  });
1040
1090
  // The person who asked is told it arrived — account mail, platform brand.
1041
1091
  if (input.email) {
1042
1092
  var _outcome_reference, _abuseReportContactEmail;
1093
+ const holdsPath = emailPath(riskHoldsPath(input.noticeId), {
1094
+ orgSlug,
1095
+ hostId: null,
1096
+ hostSubdomain: null
1097
+ });
1098
+ const holdsUrl = holdsPath ? absolute(holdsPath) : '';
1099
+ const about = `${itemLabel != null ? itemLabel : 'the held or flagged item'}${orgName ? ` on ${orgName}` : ''}`;
1043
1100
  const rendered = await renderRiskNoticeEmail(RISK_REVIEW_REQUESTED_EMAIL_KEY, {
1044
1101
  'notice.title': 'We received your review request',
1045
- 'notice.summary': 'Your request reached our review team' + (outcome.reference ? ` under the reference ${outcome.reference}` : '') + '. A person reads every request.',
1046
- 'notice.meaning': 'Nothing changes until the review is done; you will get a notice either way.',
1102
+ 'notice.summary': `We received your request to review ${about}` + (outcome.reference ? ` (reference ${outcome.reference})` : '') + '. A person on our review team reads every request.',
1103
+ 'notice.meaning': 'Nothing changes until the review is done, and you will get a notice either way.',
1047
1104
  'notice.steps': numbered([
1048
1105
  'Nothing more is needed.',
1049
- 'You can follow the item\'s status on Holds & reviews.'
1106
+ 'You can follow its status on Holds & reviews.'
1050
1107
  ]),
1108
+ 'notice.actions': holdsUrl ? `View holds and reviews: ${holdsUrl}` : '',
1109
+ 'notice.primaryActionLabel': holdsUrl ? 'View holds and reviews' : '',
1110
+ 'notice.primaryActionUrl': holdsUrl,
1111
+ 'holds.url': holdsUrl,
1112
+ 'workspace.name': orgName != null ? orgName : '',
1051
1113
  reference: (_outcome_reference = outcome.reference) != null ? _outcome_reference : '',
1052
1114
  'help.url': riskNoticeHelpUrl('requesting-a-review')
1053
1115
  });