@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
@@ -27,7 +27,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
27
27
  * the one the editor rendered, and no other change is in flight. It sets
28
28
  * the marker `consentGroupsChange` and creates the job document.
29
29
  * 2. CARRY — every carry of the plan through the three platform stores
30
- * (`consent-group-carry.ts`), then every participant's carry phase.
30
+ * (`consent-group-carry.ts`) and the organization's retained refusals
31
+ * (`retained-refusals.ts`), then every participant's carry phase.
31
32
  * 3. CATCH UP — everything filed since the carry began, again.
32
33
  * 4. DECLARE — one transaction: the marker still names this change in its
33
34
  * carry phase and the stored declaration is still the one the job
@@ -66,8 +67,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
66
67
  */ import { CONSENT_GROUP_CHANGE_STEPS, CONSENT_GROUP_CHANGES_COLLECTION, CONSENT_GROUP_RENAME_STEPS, CONSENT_GROUP_SWEEP_DELAY_MS, CONSENT_GROUPS_CHANGE_FIELD, consentGroupChangePhaseOf, consentGroupDisclosureChanges, consentGroupsFingerprint, describeConsentGroupChangeLine, discardedConsentGroupIds, estimateConsentGroupChange, orgSiteIds, planConsentGroupChange, readConsentGroupsChange, validateConsentGroupDeclaration } from "@aglyn/aglyn/app-utils/consent-group-change";
67
68
  import { CONSENT_GROUPS_FIELD, consentGroupsAwaitConfirmation, readConsentGroups } from "@aglyn/aglyn/app-utils/consent-groups";
68
69
  import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
69
- import { DEFAULT_EMAIL_TOPICS, TOPIC_OPT_OUTS_SUBCOLLECTION } from "@aglyn/aglyn/app-utils/email-topics";
70
- import { resolveMarketingConsentPolicy } from "@aglyn/aglyn/app-utils/marketing-consent";
70
+ import { DECLARED_SUBSCRIPTION_TOPICS, TOPIC_OPT_OUTS_SUBCOLLECTION } from "@aglyn/aglyn/app-utils/subscription-topics";
71
+ import { MARKETING_CONSENT_BY_HOST_FIELD, MARKETING_CONSENT_FIELD, resolveMarketingConsentPolicy } from "@aglyn/aglyn/app-utils/marketing-consent";
71
72
  import { isOrgWideMember } from "@aglyn/aglyn/app-utils/organizations";
72
73
  import { listPluginConsentGroupParticipants, previewPluginConsentGroupChange } from "@aglyn/aglyn/plugin-manager/plugin-consent-group-change";
73
74
  import { FieldValue } from "firebase-admin/firestore";
@@ -75,6 +76,7 @@ import { CONSENT_GROUP_CARRY_STORES, CONSENT_GROUP_CARRY_SUBCOLLECTIONS, carryCo
75
76
  import firebaseAdmin from "./firebase-admin.js";
76
77
  import { getLockdownVerdict } from "./lockdown.js";
77
78
  import { logOrgActivity } from "./organizations.js";
79
+ import { carryRetainedRefusals, retainedRefusalsStore } from "./retained-refusals.js";
78
80
  /** How long a claimed job stays claimed without a renewal. */ export const CONSENT_GROUP_CHANGE_LEASE_MS = 90000;
79
81
  /** Consecutive failed units before a job is shown as stalled. */ export const CONSENT_GROUP_CHANGE_STALL_AFTER = 5;
80
82
  /**
@@ -142,7 +144,10 @@ export function consentGroupChangeStatus(job, nowMs = Date.now()) {
142
144
  failures: (_job_failures = job.failures) != null ? _job_failures : 0,
143
145
  lastError: (_job_lastError = job.lastError) != null ? _job_lastError : null,
144
146
  leaseUntilMs: job.lease && job.lease.untilMs > nowMs ? job.lease.untilMs : null,
145
- counts: _extends({}, job.counts),
147
+ // A job started before the retained store was carried has no count for it.
148
+ counts: _extends({
149
+ retainedRefusals: 0
150
+ }, job.counts),
146
151
  sitesReceiving: (_ref = (_job_sitesReceiving = job.sitesReceiving) == null ? void 0 : _job_sitesReceiving.length) != null ? _ref : 0,
147
152
  plugins: (_job_plugins = job.plugins) != null ? _job_plugins : {}
148
153
  }
@@ -266,12 +271,14 @@ const describeLines = (lines, names)=>lines.map((line)=>describeConsentGroupChan
266
271
  const suppressions = (hostId)=>cached(`s:${hostId}`, ()=>store(hostId, 'siteSuppressions'));
267
272
  const optOuts = (hostId)=>cached(`t:${hostId}`, ()=>store(hostId, 'topicOptOuts'));
268
273
  const paces = (hostId)=>cached(`p:${hostId}`, ()=>store(hostId, 'paces').where('cadence', '!=', null));
274
+ const retained = (hostId)=>cached(`r:${hostId}`, ()=>retainedRefusalsStore(orgDoc(ctx, input.orgId)).where(`${MARKETING_CONSENT_BY_HOST_FIELD}.${hostId}.${MARKETING_CONSENT_FIELD}`, '==', false));
269
275
  const carries = await Promise.all(plan.carries.map(async (carry)=>({
270
276
  toHostId: carry.toHostId,
271
277
  fromHostId: carry.fromHostId,
272
278
  siteSuppressions: await suppressions(carry.fromHostId),
273
279
  topicOptOuts: await optOuts(carry.fromHostId),
274
- paces: await paces(carry.fromHostId)
280
+ paces: await paces(carry.fromHostId),
281
+ retainedRefusals: await retained(carry.fromHostId)
275
282
  })));
276
283
  const membersIn = (declaration, hostId)=>{
277
284
  var _ref;
@@ -319,7 +326,7 @@ const describeLines = (lines, names)=>lines.map((line)=>describeConsentGroupChan
319
326
  for (const [hostId, releasedHostIds] of [
320
327
  ...released.entries()
321
328
  ].sort()){
322
- for (const topic of DEFAULT_EMAIL_TOPICS){
329
+ for (const topic of DECLARED_SUBSCRIPTION_TOPICS){
323
330
  const count = await countOf(db.collection('hosts').doc(hostId).collection(TOPIC_OPT_OUTS_SUBCOLLECTION).where(`topics.${topic.id}.confirmedAt`, '==', null));
324
331
  if (count > 0) pendingHolds.push({
325
332
  hostId,
@@ -358,7 +365,7 @@ const describeLines = (lines, names)=>lines.map((line)=>describeConsentGroupChan
358
365
  });
359
366
  const names = await siteNamesFor(ctx, plannedSites(plan));
360
367
  const texts = describeLines(plan.lines, names);
361
- const documents = carries.reduce((total, carry)=>total + carry.siteSuppressions + carry.topicOptOuts + carry.paces, 0) + participants.reduce((total, entry)=>{
368
+ const documents = carries.reduce((total, carry)=>total + carry.siteSuppressions + carry.topicOptOuts + carry.paces + carry.retainedRefusals, 0) + participants.reduce((total, entry)=>{
362
369
  var _entry_lines;
363
370
  return total + ((_entry_lines = entry.lines) != null ? _entry_lines : []).reduce((sum, line)=>{
364
371
  var _line_count;
@@ -457,7 +464,8 @@ const describeLines = (lines, names)=>lines.map((line)=>describeConsentGroupChan
457
464
  counts: {
458
465
  siteSuppressions: 0,
459
466
  topicOptOuts: 0,
460
- paces: 0
467
+ paces: 0,
468
+ retainedRefusals: 0
461
469
  },
462
470
  sitesReceiving: [],
463
471
  plugins: {},
@@ -510,13 +518,21 @@ const describeLines = (lines, names)=>lines.map((line)=>describeConsentGroupChan
510
518
  };
511
519
  }
512
520
  /** The current step's units, in the order they run. */ function unitsOf(job) {
513
- const carries = (sinceMs)=>job.plan.carries.flatMap((_carry, carryIndex)=>CONSENT_GROUP_CARRY_STORES.map((store)=>({
514
- kind: 'carry',
515
- key: `c${carryIndex}:${store}`,
521
+ const carries = (sinceMs)=>job.plan.carries.flatMap((_carry, carryIndex)=>[
522
+ ...CONSENT_GROUP_CARRY_STORES.map((store)=>({
523
+ kind: 'carry',
524
+ key: `c${carryIndex}:${store}`,
525
+ carryIndex,
526
+ store,
527
+ sinceMs
528
+ })),
529
+ {
530
+ kind: 'retained',
531
+ key: `c${carryIndex}:retainedRefusals`,
516
532
  carryIndex,
517
- store,
518
533
  sinceMs
519
- })));
534
+ }
535
+ ]);
520
536
  const participants = (phase)=>job.participants.map((pluginId)=>({
521
537
  kind: 'participant',
522
538
  key: `p:${pluginId}`,
@@ -588,19 +604,27 @@ async function runUnit(ctx, jobRef, job, owner, unit, deadlineMs) {
588
604
  cursor: null,
589
605
  done: false
590
606
  };
591
- if (unit.kind === 'carry') {
592
- var _job_counts_unit_store;
607
+ if (unit.kind === 'carry' || unit.kind === 'retained') {
608
+ var _job_counts_counted;
593
609
  const carry = job.plan.carries[unit.carryIndex];
594
- const page = await carryConsentGroupRefusals({
610
+ const page = unit.kind === 'carry' ? await carryConsentGroupRefusals({
595
611
  firestore: ctx.firestore,
596
612
  store: unit.store,
597
613
  carry,
598
614
  changeId: job.changeId,
599
615
  cursor: state.cursor,
600
616
  sinceMs: unit.sinceMs
617
+ }) : await carryRetainedRefusals({
618
+ firestore: ctx.firestore,
619
+ orgId: job.orgId,
620
+ carry,
621
+ changeId: job.changeId,
622
+ cursor: state.cursor,
623
+ sinceMs: unit.sinceMs
601
624
  });
625
+ const counted = unit.kind === 'carry' ? unit.store : 'retainedRefusals';
602
626
  const counts = _extends({}, job.counts, {
603
- [unit.store]: ((_job_counts_unit_store = job.counts[unit.store]) != null ? _job_counts_unit_store : 0) + page.written
627
+ [counted]: ((_job_counts_counted = job.counts[counted]) != null ? _job_counts_counted : 0) + page.written
604
628
  });
605
629
  const sitesReceiving = page.written > 0 && !job.sitesReceiving.includes(carry.toHostId) ? [
606
630
  ...job.sitesReceiving,
@@ -735,7 +759,8 @@ function participantFor(pluginId) {
735
759
  return finished;
736
760
  }
737
761
  /** The "Finished" line: the carries' totals, then each participant's clause. */ function finishedLine(job) {
738
- const copied = job.counts.siteSuppressions + job.counts.topicOptOuts + job.counts.paces;
762
+ var _job_counts_retainedRefusals;
763
+ const copied = job.counts.siteSuppressions + job.counts.topicOptOuts + job.counts.paces + ((_job_counts_retainedRefusals = job.counts.retainedRefusals) != null ? _job_counts_retainedRefusals : 0);
739
764
  const sites = job.sitesReceiving.length;
740
765
  const clauses = [
741
766
  `${copied} ${copied === 1 ? 'opt-out' : 'opt-outs'} copied to ${sites} ${sites === 1 ? 'site' : 'sites'}`
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/consent-group-change.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 * RUNNING A CONSENT GROUP CHANGE (AGL-3320) — the executor.\n *\n * `consent-group-change.ts` in `app-utils` decides WHAT a change requires;\n * this carries it out, across as many invocations as it takes, without ever\n * leaving a refusal unhonored in between.\n *\n * ## The order is the safety argument\n *\n * 1. START — one transaction on the org document: the stored declaration is\n * the one the editor rendered, and no other change is in flight. It sets\n * the marker `consentGroupsChange` and creates the job document.\n * 2. CARRY — every carry of the plan through the three platform stores\n * (`consent-group-carry.ts`), then every participant's carry phase.\n * 3. CATCH UP — everything filed since the carry began, again.\n * 4. DECLARE — one transaction: the marker still names this change in its\n * carry phase and the stored declaration is still the one the job\n * started from. Only then is the new declaration written.\n * 5. CATCH UP — once more, for whatever raced the flip.\n * 6. RE-HOME — every participant's holder records.\n * 7. SWEEP — once the sweep delay has passed, the whole carry and every\n * participant's sweep, idempotently.\n * 8. DONE — the marker is cleared.\n *\n * The flip cannot happen before every carry and both catch-up passes are\n * done, because the steps run strictly in order and `declare` is only ever\n * reached from a finished `catch-up-pre`. Until the flip the declaration in\n * force is the old one, so a change that stalls or is canceled before it\n * has changed nothing anybody can observe — except refusals honored on more\n * sites, which is the safe direction. A rename moves nothing, and runs the\n * declare and finish alone.\n *\n * ## One invocation at a time, and any invocation may finish it\n *\n * The console's progress panel calls `continue` while it is open, and the\n * cron backstop calls every fifteen minutes. Each claims a LEASE on the job\n * by transaction and renews it on every unit it saves; a lease that lapses\n * (a process that died) is simply claimed by the next caller. A unit is one\n * page of one store, or one participant call, and every unit is idempotent,\n * so a unit re-run after a crash writes nothing it already wrote.\n *\n * ## A failure is retried, and five in a row are shown\n *\n * A unit that throws releases the lease and counts a failure against the job;\n * the next invocation retries it from its cursor. Five consecutive failures\n * mark the job STALLED — the activity log says so once, and the panel shows\n * the error with a retry — and the cron keeps trying. A participant's throw\n * is one of these: the change never flips past a participant that could not\n * carry its refusals (see `plugin-consent-group-change.ts`).\n */\n\nimport {\n CONSENT_GROUP_CHANGE_STEPS,\n CONSENT_GROUP_CHANGES_COLLECTION,\n CONSENT_GROUP_RENAME_STEPS,\n CONSENT_GROUP_SWEEP_DELAY_MS,\n CONSENT_GROUPS_CHANGE_FIELD,\n type ConsentGroupChangeCounts,\n type ConsentGroupChangeLine,\n type ConsentGroupChangePlan,\n type ConsentGroupChangePreview,\n type ConsentGroupChangeStatus,\n type ConsentGroupChangeStep,\n type ConsentGroupChangeProgress,\n type ConsentGroupDeclaration,\n type ConsentGroupsApiInFlight,\n type ConsentGroupsApiRefusal,\n type ConsentGroupsApiStale,\n type ConsentGroupsChangeMarker,\n type ConsentGroupsChangePhase,\n consentGroupChangePhaseOf,\n consentGroupDisclosureChanges,\n consentGroupsFingerprint,\n describeConsentGroupChangeLine,\n discardedConsentGroupIds,\n estimateConsentGroupChange,\n orgSiteIds,\n planConsentGroupChange,\n readConsentGroupsChange,\n validateConsentGroupDeclaration,\n} from '@aglyn/aglyn/app-utils/consent-group-change'\nimport {\n CONSENT_GROUPS_FIELD,\n consentGroupsAwaitConfirmation,\n readConsentGroups,\n} from '@aglyn/aglyn/app-utils/consent-groups'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport {\n DEFAULT_EMAIL_TOPICS,\n TOPIC_OPT_OUTS_SUBCOLLECTION,\n} from '@aglyn/aglyn/app-utils/email-topics'\nimport { resolveMarketingConsentPolicy } from '@aglyn/aglyn/app-utils/marketing-consent'\nimport { isOrgWideMember } from '@aglyn/aglyn/app-utils/organizations'\nimport {\n type ConsentGroupChangeParticipant,\n type ConsentGroupChangeParticipantPhase,\n listPluginConsentGroupParticipants,\n previewPluginConsentGroupChange,\n} from '@aglyn/aglyn/plugin-manager/plugin-consent-group-change'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n CONSENT_GROUP_CARRY_STORES,\n CONSENT_GROUP_CARRY_SUBCOLLECTIONS,\n type ConsentGroupCarryStore,\n carryConsentGroupRefusals,\n} from './consent-group-carry'\nimport firebaseAdmin from './firebase-admin'\nimport { getLockdownVerdict } from './lockdown'\nimport { logOrgActivity } from './organizations'\n\n/** How long a claimed job stays claimed without a renewal. */\nexport const CONSENT_GROUP_CHANGE_LEASE_MS = 90_000\n\n/** Consecutive failed units before a job is shown as stalled. */\nexport const CONSENT_GROUP_CHANGE_STALL_AFTER = 5\n\n/**\n * How far before the carry began a catch-up reads. A refusal filed in the\n * seconds the carry's first page was being read, with a clock a little\n * behind, is inside it.\n */\nconst CATCH_UP_OVERLAP_MS = 5 * 60_000\n\n/** Left before a deadline for the last save. */\nconst DEADLINE_SAFETY_MS = 5_000\n\n/** The phases the cron looks for a change in. */\nconst RUNNING_PHASES: readonly ConsentGroupsChangePhase[] = ['carry', 'rehome', 'sweep']\n\n/** Who asked, as the activity log records it. */\nexport interface ConsentGroupChangeActor {\n uid: string | null\n email?: string | null\n}\n\n/** Everything the executor reaches outside itself, injectable for the specs. */\nexport interface ConsentGroupChangeContext {\n firestore?: FirebaseFirestore.Firestore\n log?: typeof logOrgActivity\n now?: () => number\n /** Whether a lockdown refuses the cron acting for this org and actor. */\n lockedOut?: (org: Record<string, unknown>, uid: string | null) => Promise<boolean>\n}\n\ninterface Context {\n firestore: FirebaseFirestore.Firestore\n log: typeof logOrgActivity\n now: () => number\n lockedOut: (org: Record<string, unknown>, uid: string | null) => Promise<boolean>\n}\n\nfunction contextOf(input: ConsentGroupChangeContext): Context {\n return {\n firestore: input.firestore ?? firebaseAdmin.app().firestore(),\n log: input.log ?? logOrgActivity,\n now: input.now ?? Date.now,\n lockedOut:\n input.lockedOut ??\n (async (org, uid) =>\n (await getLockdownVerdict({ org: org as never, uid, intent: 'write' })) !== null),\n }\n}\n\n/** Where a unit of the current step stands. */\ninterface UnitState {\n cursor: string | null\n done: boolean\n}\n\n/** The job document, `orgs/{orgId}/consentGroupChanges/{changeId}`. */\nexport interface ConsentGroupChangeJob {\n changeId: string\n orgId: string\n status: ConsentGroupChangeStatus\n step: ConsentGroupChangeStep\n /** The marker's phase while running; `done` or `canceled` after. */\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n /** The steps this change runs, in order. */\n steps: ConsentGroupChangeStep[]\n /** The stored declaration it started from: what the flip re-checks. */\n beforeFingerprint: string\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n plan: Omit<ConsentGroupChangePlan, 'before' | 'after'>\n /** The plan's lines as the activity log writes them. */\n lines: string[]\n siteNames: Record<string, string>\n /** The participants registered when it started — every one of them runs. */\n participants: string[]\n /** The current step's units, by key. */\n units: Record<string, UnitState>\n counts: ConsentGroupChangeCounts\n sitesReceiving: string[]\n plugins: Record<string, Record<string, number>>\n lease: { owner: string; untilMs: number } | null\n failures: number\n stalled: boolean\n lastError: string | null\n actor: { uid: string | null; email: string | null }\n startedAtMs: number\n carryStartedAtMs: number\n declaredAtMs: number | null\n finishedAtMs: number | null\n canceledAtMs: number | null\n}\n\n/** What the route answers for a job. */\nexport interface ConsentGroupChangeStatusBody {\n changeId: string\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n done: boolean\n progress: ConsentGroupChangeProgress\n}\n\n/** A refusal the route turns into its status code and body. */\nexport type ConsentGroupChangeRefusal =\n | { ok: false; status: 400; body: ConsentGroupsApiRefusal }\n | { ok: false; status: 404; body: { error: string } }\n | { ok: false; status: 409; body: ConsentGroupsApiStale | ConsentGroupsApiInFlight }\n\nconst NOT_FOUND = { ok: false as const, status: 404 as const, body: { error: 'No such consent group change' } }\n\n/** A caller whose lease was taken, or whose job was canceled, under it. */\nclass LeaseLost extends Error {}\n\nconst orgDoc = (ctx: Context, orgId: string) => ctx.firestore.collection('orgs').doc(orgId)\nconst jobDoc = (ctx: Context, orgId: string, changeId: string) =>\n orgDoc(ctx, orgId).collection(CONSENT_GROUP_CHANGES_COLLECTION).doc(changeId)\n\n/** The job as the plan the participants and the carries read. */\nfunction planOf(job: ConsentGroupChangeJob): ConsentGroupChangePlan {\n return { ...job.plan, before: job.before, after: job.after }\n}\n\n/** An error as the job records it: bounded, and with no address in it. */\nfunction sanitizeError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error ?? 'Unknown error')\n return message.replace(/[^\\s@]+@[^\\s@]+/g, '[address]').slice(0, 300) || 'Unknown error'\n}\n\nexport function consentGroupChangeStatus(\n job: ConsentGroupChangeJob,\n nowMs: number = Date.now(),\n): ConsentGroupChangeStatusBody {\n const declaredAtMs = job.declaredAtMs ?? null\n const index = job.steps.indexOf(job.step)\n return {\n changeId: job.changeId,\n phase: job.phase,\n done: job.status !== 'running',\n progress: {\n status: job.status,\n step: job.step,\n stepIndex: index >= 0 ? index + 1 : job.steps.length,\n stepCount: job.steps.length,\n declaredAtMs,\n sweepAtMs:\n declaredAtMs !== null && !job.plan.renameOnly\n ? declaredAtMs + CONSENT_GROUP_SWEEP_DELAY_MS\n : null,\n stalled: job.stalled === true,\n failures: job.failures ?? 0,\n lastError: job.lastError ?? null,\n leaseUntilMs: job.lease && job.lease.untilMs > nowMs ? job.lease.untilMs : null,\n counts: { ...job.counts },\n sitesReceiving: job.sitesReceiving?.length ?? 0,\n plugins: job.plugins ?? {},\n },\n }\n}\n\n/*==========================================\n * VALIDATING AND PLANNING — shared by the preview and the start\n *=========================================*/\n\ninterface Prepared {\n org: Record<string, unknown>\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n plan: ConsentGroupChangePlan\n siteIds: string[]\n}\n\nfunction prepare(\n org: Record<string, unknown>,\n expected: unknown,\n groups: unknown,\n): Prepared | ConsentGroupChangeRefusal {\n const marker = readConsentGroupsChange(org)\n if (marker) {\n return {\n ok: false,\n status: 409,\n body: {\n error: 'A consent group change is still finishing',\n changeId: marker.changeId,\n phase: marker.phase,\n },\n }\n }\n const raw = org[CONSENT_GROUPS_FIELD]\n if (consentGroupsFingerprint(raw) !== consentGroupsFingerprint(expected)) {\n return {\n ok: false,\n status: 409,\n body: {\n error: 'Someone else changed consent groups while you were editing',\n current:\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : null,\n },\n }\n }\n const before = readConsentGroups(org)\n const siteIds = orgSiteIds(org)\n const validation = validateConsentGroupDeclaration({\n groups,\n before,\n siteIds,\n reserved:\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? Object.keys(raw as Record<string, unknown>)\n : [],\n })\n if (!validation.ok) {\n return {\n ok: false,\n status: 400,\n body: {\n error: 'Those consent groups cannot be saved',\n errors: (validation as { errors: ConsentGroupsApiRefusal['errors'] }).errors,\n },\n }\n }\n const after = (validation as { after: ConsentGroupDeclaration }).after\n return {\n org,\n before,\n after,\n plan: planConsentGroupChange(before, after, { siteIds }),\n siteIds,\n }\n}\n\nconst isRefusal = (value: unknown): value is ConsentGroupChangeRefusal =>\n Boolean(value && typeof value === 'object' && (value as { ok?: unknown }).ok === false)\n\n/** Every site a plan names, for the names its lines are written with. */\nfunction plannedSites(plan: ConsentGroupChangePlan): string[] {\n return [\n ...new Set([\n ...plan.hostIds,\n ...Object.values(plan.before).flatMap((group) => group.hostIds),\n ...Object.values(plan.after).flatMap((group) => group.hostIds),\n ]),\n ]\n}\n\n/** What the console calls each site: its name, its subdomain, or its id. */\nasync function siteNamesFor(ctx: Context, hostIds: readonly string[]): Promise<Record<string, string>> {\n if (!hostIds.length) return {}\n const hosts = ctx.firestore.collection('hosts')\n const snapshots = await ctx.firestore.getAll(...hostIds.map((id) => hosts.doc(id)))\n const names: Record<string, string> = {}\n snapshots.forEach((snapshot, index) => {\n const id = hostIds[index]\n names[id] = String(snapshot.get('name') || snapshot.get('subdomain') || id)\n })\n return names\n}\n\nconst describeLines = (lines: readonly ConsentGroupChangeLine[], names: Record<string, string>) =>\n lines.map((line) => describeConsentGroupChangeLine(line, (hostId) => names[hostId] ?? hostId))\n\n/*==========================================\n * THE PREVIEW\n *=========================================*/\n\nasync function countOf(query: FirebaseFirestore.Query): Promise<number> {\n const snapshot = await query.count().get()\n return Number(snapshot.data().count) || 0\n}\n\n/**\n * What a change would do, counted and worded, writing nothing.\n *\n * Refused exactly as `apply` would be — a change in flight, a stale\n * `expected`, an invalid declaration — so the review step never shows a\n * change the next click cannot make.\n */\nexport async function previewConsentGroupChange(\n input: {\n orgId: string\n expected: unknown\n groups: unknown\n /** The org document, when the caller already read it. */\n org?: Record<string, unknown> | null\n } & ConsentGroupChangeContext,\n): Promise<{ ok: true; preview: ConsentGroupChangePreview } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const org =\n input.org ?? ((await orgDoc(ctx, input.orgId).get()).data() as Record<string, unknown> | undefined)\n if (!org) return { ok: false, status: 404, body: { error: 'No such workspace' } }\n const prepared = prepare(org, input.expected, input.groups)\n if (isRefusal(prepared)) return prepared\n const { plan, before, after } = prepared as Prepared\n\n const db = ctx.firestore\n const cache = new Map<string, Promise<number>>()\n const cached = (key: string, query: () => FirebaseFirestore.Query) => {\n let pending = cache.get(key)\n if (!pending) {\n pending = countOf(query())\n cache.set(key, pending)\n }\n return pending\n }\n const store = (hostId: string, name: ConsentGroupCarryStore) =>\n db.collection('hosts').doc(hostId).collection(CONSENT_GROUP_CARRY_SUBCOLLECTIONS[name])\n const suppressions = (hostId: string) => cached(`s:${hostId}`, () => store(hostId, 'siteSuppressions'))\n const optOuts = (hostId: string) => cached(`t:${hostId}`, () => store(hostId, 'topicOptOuts'))\n const paces = (hostId: string) =>\n cached(`p:${hostId}`, () => store(hostId, 'paces').where('cadence', '!=', null))\n\n const carries = await Promise.all(\n plan.carries.map(async (carry) => ({\n toHostId: carry.toHostId,\n fromHostId: carry.fromHostId,\n siteSuppressions: await suppressions(carry.fromHostId),\n topicOptOuts: await optOuts(carry.fromHostId),\n paces: await paces(carry.fromHostId),\n })),\n )\n\n const membersIn = (declaration: ConsentGroupDeclaration, hostId: string) =>\n Object.values(declaration).find((group) => group.hostIds.includes(hostId))?.hostIds ?? [hostId]\n const joining: Array<{ hostId: string; siblings: string[] }> = []\n for (const hostId of [...new Set(Object.values(after).flatMap((group) => group.hostIds))].sort()) {\n const had = new Set(membersIn(before, hostId))\n const siblings = membersIn(after, hostId).filter((id) => id !== hostId && !had.has(id))\n if (siblings.length) joining.push({ hostId, siblings })\n }\n const inherited = await Promise.all(\n joining.map(async ({ hostId, siblings }) => {\n let refusals = 0\n for (const sibling of siblings) {\n refusals += (await suppressions(sibling)) + (await optOuts(sibling))\n }\n return { hostId, refusals }\n }),\n )\n\n /*\n * A pending confirmation holds a sibling's mail only with the switch on,\n * so only then does a separation release anything. Counted over the\n * built-in streams: the catalog of an organization's own streams belongs to\n * the Email plugin, and a stream it added is not counted here.\n */\n const pendingHolds: ConsentGroupChangePreview['pendingHolds'] = []\n if (consentGroupsAwaitConfirmation(org)) {\n const released = new Map<string, string[]>()\n for (const carry of plan.carries) {\n released.set(carry.toHostId, [...(released.get(carry.toHostId) ?? []), carry.fromHostId])\n }\n for (const [hostId, releasedHostIds] of [...released.entries()].sort()) {\n for (const topic of DEFAULT_EMAIL_TOPICS) {\n const count = await countOf(\n db\n .collection('hosts')\n .doc(hostId)\n .collection(TOPIC_OPT_OUTS_SUBCOLLECTION)\n .where(`topics.${topic.id}.confirmedAt`, '==', null),\n )\n if (count > 0) pendingHolds.push({ hostId, topicId: topic.id, count, releasedHostIds })\n }\n }\n }\n\n const changedGroups = Object.entries(after)\n .filter(([id, group]) => {\n const was = before[id]\n return !was || was.hostIds.join('\\n') !== group.hostIds.join('\\n')\n })\n .map(([, group]) => group.hostIds)\n let partialAccessMembers = 0\n if (changedGroups.length) {\n const members = await orgDoc(ctx, input.orgId).collection('members').get()\n for (const member of members.docs) {\n const data = member.data() as Record<string, unknown>\n if (isOrgWideMember(data)) continue\n const reach = new Set(Object.keys((data['hostAccess'] ?? {}) as Record<string, unknown>))\n if (\n changedGroups.some(\n (hostIds) =>\n hostIds.some((id) => reach.has(id)) && !hostIds.every((id) => reach.has(id)),\n )\n ) {\n partialAccessMembers += 1\n }\n }\n }\n\n const policy = resolveMarketingConsentPolicy(org['marketingConsentPolicy'])\n const forwardPolicyWarning =\n policy.mode === 'forward' && joining.length\n ? { hostIds: joining.map((entry) => entry.hostId), enforceFromMs: policy.enforceFromMs }\n : null\n\n const participants = await previewPluginConsentGroupChange({ orgId: input.orgId, plan })\n const names = await siteNamesFor(ctx, plannedSites(plan))\n const texts = describeLines(plan.lines, names)\n const documents =\n carries.reduce((total, carry) => total + carry.siteSuppressions + carry.topicOptOuts + carry.paces, 0) +\n participants.reduce(\n (total, entry) =>\n total + (entry.lines ?? []).reduce((sum, line) => sum + (line.count ?? 0), 0),\n 0,\n )\n\n return {\n ok: true,\n preview: {\n before,\n after,\n discarded: discardedConsentGroupIds(org),\n lines: plan.lines.map((line, index) => ({ ...line, text: texts[index] })),\n disclosures: consentGroupDisclosureChanges(before, after),\n carries,\n inherited,\n pendingHolds,\n partialAccessMembers,\n forwardPolicyWarning,\n participants,\n capturesDisclosing: ['form'],\n estimate: estimateConsentGroupChange(plan, documents),\n },\n }\n}\n\n/*==========================================\n * STARTING\n *=========================================*/\n\n/**\n * Starts a change: the org marker and the job, in one transaction that\n * refuses a change in flight, a stale `expected` and an invalid declaration.\n * Writes nothing else — the caller advances it.\n */\nexport async function startConsentGroupChange(\n input: {\n orgId: string\n actor: ConsentGroupChangeActor\n expected: unknown\n groups: unknown\n } & ConsentGroupChangeContext,\n): Promise<{ ok: true; changeId: string; job: ConsentGroupChangeJob } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const orgRef = orgDoc(ctx, input.orgId)\n const snapshot = await orgRef.get()\n if (!snapshot.exists) return { ok: false, status: 404, body: { error: 'No such workspace' } }\n const outside = prepare(snapshot.data() as Record<string, unknown>, input.expected, input.groups)\n if (isRefusal(outside)) return outside\n // Names are words for the log, read once and outside the transaction.\n const names = await siteNamesFor(ctx, plannedSites((outside as Prepared).plan))\n const participants = listPluginConsentGroupParticipants().map((entry) => entry.pluginId)\n const changeId = createResourceUid()\n const jobRef = jobDoc(ctx, input.orgId, changeId)\n const nowMs = ctx.now()\n\n const started = await ctx.firestore.runTransaction(async (transaction) => {\n const current = await transaction.get(orgRef)\n if (!current.exists) {\n return { ok: false as const, status: 404 as const, body: { error: 'No such workspace' } }\n }\n const prepared = prepare(current.data() as Record<string, unknown>, input.expected, input.groups)\n if (isRefusal(prepared)) return prepared\n const { plan, before, after, org } = prepared as Prepared\n const steps = plan.renameOnly ? [...CONSENT_GROUP_RENAME_STEPS] : [...CONSENT_GROUP_CHANGE_STEPS]\n const job: ConsentGroupChangeJob = {\n changeId,\n orgId: input.orgId,\n status: 'running',\n step: steps[0],\n phase: 'carry',\n steps,\n beforeFingerprint: consentGroupsFingerprint(org[CONSENT_GROUPS_FIELD]),\n before,\n after,\n plan: {\n carries: plan.carries,\n flows: plan.flows,\n lines: plan.lines,\n hostIds: plan.hostIds,\n renameOnly: plan.renameOnly,\n },\n lines: describeLines(plan.lines, names),\n siteNames: names,\n participants: plan.renameOnly ? [] : participants,\n units: {},\n counts: { siteSuppressions: 0, topicOptOuts: 0, paces: 0 },\n sitesReceiving: [],\n plugins: {},\n lease: null,\n failures: 0,\n stalled: false,\n lastError: null,\n actor: { uid: input.actor.uid ?? null, email: input.actor.email ?? null },\n startedAtMs: nowMs,\n carryStartedAtMs: nowMs,\n declaredAtMs: null,\n finishedAtMs: null,\n canceledAtMs: null,\n }\n const marker: ConsentGroupsChangeMarker = {\n changeId,\n phase: 'carry',\n hostIds: plan.hostIds,\n startedAtMs: nowMs,\n }\n transaction.update(orgRef, {\n [CONSENT_GROUPS_CHANGE_FIELD]: marker,\n updatedAt: FieldValue.serverTimestamp(),\n })\n transaction.create(jobRef, {\n ...job,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n return { ok: true as const, job }\n })\n if (isRefusal(started)) return started\n const { job } = started as { job: ConsentGroupChangeJob }\n await ctx.log(\n input.orgId,\n { uid: job.actor.uid, email: job.actor.email },\n `Started a consent group change: ${job.lines.join('; ')}`,\n { type: 'org', id: input.orgId },\n )\n return { ok: true, changeId, job }\n}\n\n/*==========================================\n * ADVANCING\n *=========================================*/\n\ntype Unit =\n | { kind: 'carry'; key: string; carryIndex: number; store: ConsentGroupCarryStore; sinceMs: number | null }\n | { kind: 'participant'; key: string; pluginId: string; phase: ConsentGroupChangeParticipantPhase }\n\n/** The current step's units, in the order they run. */\nfunction unitsOf(job: ConsentGroupChangeJob): Unit[] {\n const carries = (sinceMs: number | null): Unit[] =>\n job.plan.carries.flatMap((_carry, carryIndex) =>\n CONSENT_GROUP_CARRY_STORES.map((store) => ({\n kind: 'carry' as const,\n key: `c${carryIndex}:${store}`,\n carryIndex,\n store,\n sinceMs,\n })),\n )\n const participants = (phase: ConsentGroupChangeParticipantPhase): Unit[] =>\n job.participants.map((pluginId) => ({\n kind: 'participant' as const,\n key: `p:${pluginId}`,\n pluginId,\n phase,\n }))\n const since = job.carryStartedAtMs - CATCH_UP_OVERLAP_MS\n switch (job.step) {\n case 'carry':\n return [...carries(null), ...participants('carry')]\n case 'catch-up-pre':\n case 'catch-up-post':\n return [...carries(since), ...participants('carry')]\n case 'rehome':\n return participants('rehome')\n case 'sweep':\n return [...carries(null), ...participants('sweep')]\n default:\n return []\n }\n}\n\nfunction jobFrom(snapshot: FirebaseFirestore.DocumentSnapshot): ConsentGroupChangeJob | null {\n return snapshot.exists ? (snapshot.data() as ConsentGroupChangeJob) : null\n}\n\n/**\n * Saves a unit's result, renewing the lease — only while the job is still\n * running and still this caller's.\n */\nasync function save(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n fields: Partial<ConsentGroupChangeJob>,\n extra?: (transaction: FirebaseFirestore.Transaction, job: ConsentGroupChangeJob) => Promise<void>,\n): Promise<ConsentGroupChangeJob> {\n return ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job || job.status !== 'running' || job.lease?.owner !== owner) throw new LeaseLost()\n if (extra) await extra(transaction, job)\n const next: Partial<ConsentGroupChangeJob> = {\n ...fields,\n lease: { owner, untilMs: ctx.now() + CONSENT_GROUP_CHANGE_LEASE_MS },\n failures: 0,\n stalled: false,\n lastError: null,\n }\n transaction.update(jobRef, { ...next, updatedAt: FieldValue.serverTimestamp() })\n return { ...job, ...next }\n })\n}\n\nfunction addCounts(\n base: Record<string, number> | undefined,\n more: Record<string, number>,\n): Record<string, number> {\n const out: Record<string, number> = { ...(base ?? {}) }\n for (const [key, value] of Object.entries(more)) {\n if (typeof value === 'number' && Number.isFinite(value)) out[key] = (out[key] ?? 0) + value\n }\n return out\n}\n\nasync function runUnit(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n unit: Unit,\n deadlineMs: number,\n): Promise<ConsentGroupChangeJob> {\n const state = job.units[unit.key] ?? { cursor: null, done: false }\n if (unit.kind === 'carry') {\n const carry = job.plan.carries[unit.carryIndex]\n const page = await carryConsentGroupRefusals({\n firestore: ctx.firestore,\n store: unit.store,\n carry,\n changeId: job.changeId,\n cursor: state.cursor,\n sinceMs: unit.sinceMs,\n })\n const counts = { ...job.counts, [unit.store]: (job.counts[unit.store] ?? 0) + page.written }\n const sitesReceiving =\n page.written > 0 && !job.sitesReceiving.includes(carry.toHostId)\n ? [...job.sitesReceiving, carry.toHostId]\n : job.sitesReceiving\n return save(ctx, jobRef, owner, {\n units: { ...job.units, [unit.key]: { cursor: page.cursor, done: page.done } },\n counts,\n sitesReceiving,\n })\n }\n const participant = participantFor(unit.pluginId)\n const result = await participant.run({\n orgId: job.orgId,\n changeId: job.changeId,\n plan: planOf(job),\n phase: unit.phase,\n cursor: state.cursor,\n deadlineMs: deadlineMs - DEADLINE_SAFETY_MS,\n dryRun: false,\n })\n return save(ctx, jobRef, owner, {\n units: { ...job.units, [unit.key]: { cursor: result.cursor ?? null, done: result.done === true } },\n plugins: { ...job.plugins, [unit.pluginId]: addCounts(job.plugins[unit.pluginId], result.counts ?? {}) },\n })\n}\n\nfunction participantFor(pluginId: string): ConsentGroupChangeParticipant {\n const entry = listPluginConsentGroupParticipants().find((candidate) => candidate.pluginId === pluginId)\n if (!entry) {\n // The change started with this participant; running on without it would\n // skip its share. Another process — one whose declarations registered it —\n // picks the job up.\n throw new Error(`The ${pluginId} plugin's part of this change is not available here`)\n }\n return entry.participant\n}\n\n/** Moves the job to the step after its current one. */\nasync function nextStep(\n ctx: Context,\n orgId: string,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n): Promise<ConsentGroupChangeJob> {\n const next = job.steps[job.steps.indexOf(job.step) + 1] ?? 'finish'\n const phase = consentGroupChangePhaseOf(next)\n if (phase === job.phase) return save(ctx, jobRef, owner, { step: next, units: {} })\n // Entering the sweep: the marker says so, for the readers that hold off\n // while holder records may still be moving.\n const orgRef = orgDoc(ctx, orgId)\n return save(ctx, jobRef, owner, { step: next, units: {}, phase }, async (transaction) => {\n const marker = readConsentGroupsChange((await transaction.get(orgRef)).data())\n if (marker?.changeId === job.changeId) {\n transaction.update(orgRef, { [`${CONSENT_GROUPS_CHANGE_FIELD}.phase`]: phase })\n }\n })\n}\n\n/**\n * THE FLIP — the one write of `consentGroups` anywhere in the platform.\n *\n * The marker must still name this change in its carry phase and the stored\n * declaration must be the one the job started from; otherwise nothing is\n * written and the unit fails. `update`, not a merge, so a removed group's id\n * vanishes with it; an empty declaration deletes the field.\n */\nasync function declare(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n): Promise<ConsentGroupChangeJob> {\n const orgRef = orgDoc(ctx, job.orgId)\n const nowMs = ctx.now()\n const renameOnly = job.plan.renameOnly\n const declared = await save(\n ctx,\n jobRef,\n owner,\n renameOnly\n ? {\n step: 'finish',\n status: 'done',\n phase: 'done',\n declaredAtMs: nowMs,\n finishedAtMs: nowMs,\n units: {},\n }\n : { step: 'catch-up-post', phase: 'rehome', declaredAtMs: nowMs, units: {} },\n async (transaction) => {\n const org = (await transaction.get(orgRef)).data() as Record<string, unknown> | undefined\n const marker = readConsentGroupsChange(org)\n if (!org || marker?.changeId !== job.changeId || marker.phase !== 'carry') {\n throw new Error('The change no longer holds the organization, so the new groups were not saved')\n }\n if (consentGroupsFingerprint(org[CONSENT_GROUPS_FIELD]) !== job.beforeFingerprint) {\n throw new Error('The consent groups changed outside this change, so the new groups were not saved')\n }\n transaction.update(orgRef, {\n [CONSENT_GROUPS_FIELD]: Object.keys(job.after).length ? job.after : FieldValue.delete(),\n [CONSENT_GROUPS_CHANGE_FIELD]: renameOnly\n ? FieldValue.delete()\n : { ...marker, phase: 'rehome', declaredAtMs: nowMs },\n updatedAt: FieldValue.serverTimestamp(),\n })\n },\n )\n const finished = renameOnly ? { ...declared, lease: null } : declared\n if (renameOnly) await jobRef.update({ lease: null })\n for (const line of job.lines) {\n await ctx.log(job.orgId, job.actor, line, { type: 'org', id: job.orgId })\n }\n if (renameOnly) {\n await ctx.log(job.orgId, job.actor, 'Finished a consent group change', {\n type: 'org',\n id: job.orgId,\n })\n }\n return finished\n}\n\n/** The \"Finished\" line: the carries' totals, then each participant's clause. */\nfunction finishedLine(job: ConsentGroupChangeJob): string {\n const copied = job.counts.siteSuppressions + job.counts.topicOptOuts + job.counts.paces\n const sites = job.sitesReceiving.length\n const clauses = [\n `${copied} ${copied === 1 ? 'opt-out' : 'opt-outs'} copied to ${sites} ${sites === 1 ? 'site' : 'sites'}`,\n ]\n for (const pluginId of job.participants) {\n const entry = listPluginConsentGroupParticipants().find((candidate) => candidate.pluginId === pluginId)\n const clause = entry?.participant.summarize?.(job.plugins[pluginId] ?? {})\n if (clause) clauses.push(clause)\n }\n return `Finished a consent group change: ${clauses.join('; ')}`\n}\n\nasync function finish(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n): Promise<ConsentGroupChangeJob> {\n const orgRef = orgDoc(ctx, job.orgId)\n const nowMs = ctx.now()\n const done = await save(\n ctx,\n jobRef,\n owner,\n { status: 'done', phase: 'done', step: 'finish', finishedAtMs: nowMs, units: {} },\n async (transaction) => {\n const marker = readConsentGroupsChange((await transaction.get(orgRef)).data())\n if (marker?.changeId === job.changeId) {\n transaction.update(orgRef, {\n [CONSENT_GROUPS_CHANGE_FIELD]: FieldValue.delete(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n },\n )\n await jobRef.update({ lease: null })\n await ctx.log(job.orgId, job.actor, finishedLine(done), { type: 'org', id: job.orgId })\n return { ...done, lease: null }\n}\n\n/**\n * One unit of work, or nothing when the job is waiting for its sweep.\n */\nasync function advanceOnce(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n deadlineMs: number,\n): Promise<{ job: ConsentGroupChangeJob; idle: boolean }> {\n if (job.step === 'declare') return { job: await declare(ctx, jobRef, job, owner), idle: false }\n if (job.step === 'finish') return { job: await finish(ctx, jobRef, job, owner), idle: false }\n if (job.step === 'sweep-wait') {\n if (ctx.now() < (job.declaredAtMs ?? 0) + CONSENT_GROUP_SWEEP_DELAY_MS) return { job, idle: true }\n return { job: await nextStep(ctx, job.orgId, jobRef, job, owner), idle: false }\n }\n const unit = unitsOf(job).find((candidate) => !job.units[candidate.key]?.done)\n if (!unit) return { job: await nextStep(ctx, job.orgId, jobRef, job, owner), idle: false }\n const before = job.units[unit.key]?.cursor ?? null\n const after = await runUnit(ctx, jobRef, job, owner, unit, deadlineMs)\n const state = after.units[unit.key]\n // A participant that stopped where it started — out of time before its\n // first page — has nothing more to give this invocation.\n return { job: after, idle: unit.kind === 'participant' && !state?.done && (state?.cursor ?? null) === before }\n}\n\n/** Claims the lease, or answers why not. `null` for a job that does not exist. */\nasync function claim(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n): Promise<{ job: ConsentGroupChangeJob; mine: boolean } | null> {\n return ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job) return null\n const nowMs = ctx.now()\n if (job.status !== 'running') return { job, mine: false }\n if (job.lease && job.lease.untilMs > nowMs && job.lease.owner !== owner) return { job, mine: false }\n const lease = { owner, untilMs: nowMs + CONSENT_GROUP_CHANGE_LEASE_MS }\n transaction.update(jobRef, { lease })\n return { job: { ...job, lease }, mine: true }\n })\n}\n\nasync function release(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n): Promise<ConsentGroupChangeJob | null> {\n return ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job) return null\n if (job.lease?.owner !== owner) return job\n transaction.update(jobRef, { lease: null })\n return { ...job, lease: null }\n })\n}\n\n/** Counts a failed unit against the job, and says so once when it stalls. */\nasync function recordFailure(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n error: unknown,\n): Promise<ConsentGroupChangeJob | null> {\n const lastError = sanitizeError(error)\n const outcome = await ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job) return null\n if (job.lease?.owner !== owner) return { job, newlyStalled: false }\n const failures = (job.failures ?? 0) + 1\n const stalled = failures >= CONSENT_GROUP_CHANGE_STALL_AFTER\n const fields = { failures, stalled, lastError, lease: null }\n transaction.update(jobRef, { ...fields, updatedAt: FieldValue.serverTimestamp() })\n return { job: { ...job, ...fields }, newlyStalled: stalled && !job.stalled }\n })\n if (!outcome) return null\n if (outcome.newlyStalled) {\n await ctx.log(\n outcome.job.orgId,\n outcome.job.actor,\n 'A consent group change stopped with an error; it will try again',\n { type: 'org', id: outcome.job.orgId },\n )\n }\n console.error(`[consent-group-change] a unit of ${outcome.job.changeId} failed: ${lastError}`)\n return outcome.job\n}\n\n/**\n * Advances a change until it finishes, waits for its sweep, or reaches\n * `deadlineMs`. Answers the job's status either way — including when another\n * caller holds it, which is not a failure.\n */\nexport async function advanceConsentGroupChange(\n input: {\n orgId: string\n changeId: string\n deadlineMs: number\n /** Who holds the lease; one per invocation. */\n owner?: string\n } & ConsentGroupChangeContext,\n): Promise<{ ok: true; status: ConsentGroupChangeStatusBody } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const jobRef = jobDoc(ctx, input.orgId, input.changeId)\n const owner = input.owner ?? `advance:${createResourceUid()}`\n const claimed = await claim(ctx, jobRef, owner)\n if (!claimed) return NOT_FOUND\n let job = claimed.job\n if (!claimed.mine) return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n try {\n while (job.status === 'running' && ctx.now() < input.deadlineMs - DEADLINE_SAFETY_MS) {\n const outcome = await advanceOnce(ctx, jobRef, job, owner, input.deadlineMs)\n job = outcome.job\n if (outcome.idle) break\n }\n } catch (error) {\n if (!(error instanceof LeaseLost)) {\n job = (await recordFailure(ctx, jobRef, owner, error)) ?? job\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n }\n job = jobFrom(await jobRef.get()) ?? job\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n }\n if (job.status === 'running') job = (await release(ctx, jobRef, owner)) ?? job\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n}\n\n/*==========================================\n * CANCELING AND READING\n *=========================================*/\n\n/**\n * Stops a change before it takes effect: clears the marker and leaves every\n * refusal already carried, which only ever honors an opt-out on more sites.\n * Refused once the declaration has flipped — there is nothing left to stop\n * before, and the only way back is another change.\n */\nexport async function cancelConsentGroupChange(\n input: { orgId: string; changeId: string; actor: ConsentGroupChangeActor } & ConsentGroupChangeContext,\n): Promise<{ ok: true; status: ConsentGroupChangeStatusBody } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const orgRef = orgDoc(ctx, input.orgId)\n const jobRef = jobDoc(ctx, input.orgId, input.changeId)\n const outcome = await ctx.firestore.runTransaction(async (transaction) => {\n const [orgSnapshot, jobSnapshot] = await Promise.all([transaction.get(orgRef), transaction.get(jobRef)])\n const job = jobFrom(jobSnapshot)\n if (!job) return NOT_FOUND\n if (job.status !== 'running' || job.declaredAtMs) {\n return {\n ok: false as const,\n status: 409 as const,\n body: {\n error:\n job.status === 'running' || job.status === 'done'\n ? 'This change has already taken effect'\n : 'This change is no longer running',\n changeId: job.changeId,\n phase: job.phase,\n },\n }\n }\n const marker = readConsentGroupsChange(orgSnapshot.data())\n if (marker?.changeId === job.changeId) {\n transaction.update(orgRef, {\n [CONSENT_GROUPS_CHANGE_FIELD]: FieldValue.delete(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n const fields = {\n status: 'canceled' as const,\n phase: 'canceled' as const,\n canceledAtMs: ctx.now(),\n lease: null,\n }\n transaction.update(jobRef, { ...fields, updatedAt: FieldValue.serverTimestamp() })\n return { ok: true as const, job: { ...job, ...fields } }\n })\n if (isRefusal(outcome)) return outcome\n const { job } = outcome as { job: ConsentGroupChangeJob }\n await ctx.log(\n input.orgId,\n { uid: input.actor.uid ?? null, email: input.actor.email ?? null },\n 'Canceled a consent group change before it took effect',\n { type: 'org', id: input.orgId },\n )\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n}\n\n/** A job's status, or `null` for a change the org never had. */\nexport async function readConsentGroupChangeStatus(\n input: { orgId: string; changeId: string } & ConsentGroupChangeContext,\n): Promise<ConsentGroupChangeStatusBody | null> {\n const ctx = contextOf(input)\n const job = jobFrom(await jobDoc(ctx, input.orgId, input.changeId).get())\n return job ? consentGroupChangeStatus(job, ctx.now()) : null\n}\n\n/*==========================================\n * THE CRON BACKSTOP\n *=========================================*/\n\nexport interface DueConsentGroupChangeReport {\n orgId: string\n changeId: string\n phase: ConsentGroupsChangePhase\n /** What happened on this run. */\n outcome: 'advanced' | 'listed' | 'locked' | 'orphaned' | 'skipped'\n status?: ConsentGroupChangeStatusBody\n error?: string\n}\n\n/**\n * Advances every change in flight, oldest org id first, until `deadlineMs`.\n *\n * A change nobody is watching still finishes: the progress panel is a\n * convenience, and this is the guarantee. A lockdown on the org — or on the\n * member who started the change — pauses it, like every other write the\n * workspace makes. A marker whose job document is gone is a change nothing\n * can finish, and is cleared rather than blocking every change after it.\n */\nexport async function advanceDueConsentGroupChanges(\n input: { deadlineMs: number; dryRun?: boolean; limit?: number } & ConsentGroupChangeContext,\n): Promise<DueConsentGroupChangeReport[]> {\n const ctx = contextOf(input)\n const snapshot = await ctx.firestore\n .collection('orgs')\n .where(`${CONSENT_GROUPS_CHANGE_FIELD}.phase`, 'in', [...RUNNING_PHASES])\n .limit(input.limit ?? 50)\n .get()\n const reports: DueConsentGroupChangeReport[] = []\n for (const org of snapshot.docs) {\n const data = org.data() as Record<string, unknown>\n const marker = readConsentGroupsChange(data)\n if (!marker) continue\n const base = { orgId: org.id, changeId: marker.changeId, phase: marker.phase }\n if (input.dryRun) {\n reports.push({ ...base, outcome: 'listed' })\n continue\n }\n if (ctx.now() >= input.deadlineMs - DEADLINE_SAFETY_MS) {\n reports.push({ ...base, outcome: 'skipped' })\n continue\n }\n try {\n const job = jobFrom(await jobDoc(ctx, org.id, marker.changeId).get())\n if (!job) {\n await orgDoc(ctx, org.id).update({ [CONSENT_GROUPS_CHANGE_FIELD]: FieldValue.delete() })\n console.error(`[consent-group-change] cleared a marker with no job in org ${org.id}`)\n reports.push({ ...base, outcome: 'orphaned' })\n continue\n }\n if (await ctx.lockedOut(data, job.actor?.uid ?? null)) {\n reports.push({ ...base, outcome: 'locked' })\n continue\n }\n const advanced = await advanceConsentGroupChange({\n orgId: org.id,\n changeId: marker.changeId,\n deadlineMs: input.deadlineMs,\n owner: `cron:${createResourceUid()}`,\n firestore: ctx.firestore,\n log: ctx.log,\n now: ctx.now,\n })\n reports.push({\n ...base,\n outcome: 'advanced',\n ...(advanced.ok ? { status: (advanced as { status: ConsentGroupChangeStatusBody }).status } : {}),\n })\n } catch (error) {\n reports.push({ ...base, outcome: 'advanced', error: sanitizeError(error) })\n }\n }\n return reports\n}\n"],"names":["CONSENT_GROUP_CHANGE_STEPS","CONSENT_GROUP_CHANGES_COLLECTION","CONSENT_GROUP_RENAME_STEPS","CONSENT_GROUP_SWEEP_DELAY_MS","CONSENT_GROUPS_CHANGE_FIELD","consentGroupChangePhaseOf","consentGroupDisclosureChanges","consentGroupsFingerprint","describeConsentGroupChangeLine","discardedConsentGroupIds","estimateConsentGroupChange","orgSiteIds","planConsentGroupChange","readConsentGroupsChange","validateConsentGroupDeclaration","CONSENT_GROUPS_FIELD","consentGroupsAwaitConfirmation","readConsentGroups","createResourceUid","DEFAULT_EMAIL_TOPICS","TOPIC_OPT_OUTS_SUBCOLLECTION","resolveMarketingConsentPolicy","isOrgWideMember","listPluginConsentGroupParticipants","previewPluginConsentGroupChange","FieldValue","CONSENT_GROUP_CARRY_STORES","CONSENT_GROUP_CARRY_SUBCOLLECTIONS","carryConsentGroupRefusals","firebaseAdmin","getLockdownVerdict","logOrgActivity","CONSENT_GROUP_CHANGE_LEASE_MS","CONSENT_GROUP_CHANGE_STALL_AFTER","CATCH_UP_OVERLAP_MS","DEADLINE_SAFETY_MS","RUNNING_PHASES","contextOf","input","firestore","app","log","now","Date","lockedOut","org","uid","intent","NOT_FOUND","ok","status","body","error","LeaseLost","Error","orgDoc","ctx","orgId","collection","doc","jobDoc","changeId","planOf","job","plan","before","after","sanitizeError","message","String","replace","slice","consentGroupChangeStatus","nowMs","declaredAtMs","index","steps","indexOf","step","phase","done","progress","stepIndex","length","stepCount","sweepAtMs","renameOnly","stalled","failures","lastError","leaseUntilMs","lease","untilMs","counts","sitesReceiving","plugins","prepare","expected","groups","marker","raw","current","Array","isArray","siteIds","validation","reserved","Object","keys","errors","isRefusal","value","Boolean","plannedSites","Set","hostIds","values","flatMap","group","siteNamesFor","hosts","snapshots","getAll","map","id","names","forEach","snapshot","get","describeLines","lines","line","hostId","countOf","query","count","Number","data","previewConsentGroupChange","prepared","db","cache","Map","cached","key","pending","set","store","name","suppressions","optOuts","paces","where","carries","Promise","all","carry","toHostId","fromHostId","siteSuppressions","topicOptOuts","membersIn","declaration","find","includes","joining","sort","had","siblings","filter","has","push","inherited","refusals","sibling","pendingHolds","released","releasedHostIds","entries","topic","topicId","changedGroups","was","join","partialAccessMembers","members","member","docs","reach","some","every","policy","forwardPolicyWarning","mode","entry","enforceFromMs","participants","texts","documents","reduce","total","sum","preview","discarded","text","disclosures","capturesDisclosing","estimate","startConsentGroupChange","orgRef","exists","outside","pluginId","jobRef","started","runTransaction","transaction","beforeFingerprint","flows","siteNames","units","actor","email","startedAtMs","carryStartedAtMs","finishedAtMs","canceledAtMs","update","updatedAt","serverTimestamp","create","createdAt","type","unitsOf","sinceMs","_carry","carryIndex","kind","since","jobFrom","save","owner","fields","extra","next","addCounts","base","more","out","isFinite","runUnit","unit","deadlineMs","result","state","cursor","page","written","participant","participantFor","run","dryRun","candidate","nextStep","declare","declared","delete","finished","finishedLine","copied","sites","clauses","clause","summarize","finish","advanceOnce","idle","claim","mine","release","recordFailure","outcome","newlyStalled","console","advanceConsentGroupChange","claimed","cancelConsentGroupChange","orgSnapshot","jobSnapshot","readConsentGroupChangeStatus","advanceDueConsentGroupChanges","limit","reports","advanced"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/consent-group-change.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 * RUNNING A CONSENT GROUP CHANGE (AGL-3320) — the executor.\n *\n * `consent-group-change.ts` in `app-utils` decides WHAT a change requires;\n * this carries it out, across as many invocations as it takes, without ever\n * leaving a refusal unhonored in between.\n *\n * ## The order is the safety argument\n *\n * 1. START — one transaction on the org document: the stored declaration is\n * the one the editor rendered, and no other change is in flight. It sets\n * the marker `consentGroupsChange` and creates the job document.\n * 2. CARRY — every carry of the plan through the three platform stores\n * (`consent-group-carry.ts`) and the organization's retained refusals\n * (`retained-refusals.ts`), then every participant's carry phase.\n * 3. CATCH UP — everything filed since the carry began, again.\n * 4. DECLARE — one transaction: the marker still names this change in its\n * carry phase and the stored declaration is still the one the job\n * started from. Only then is the new declaration written.\n * 5. CATCH UP — once more, for whatever raced the flip.\n * 6. RE-HOME — every participant's holder records.\n * 7. SWEEP — once the sweep delay has passed, the whole carry and every\n * participant's sweep, idempotently.\n * 8. DONE — the marker is cleared.\n *\n * The flip cannot happen before every carry and both catch-up passes are\n * done, because the steps run strictly in order and `declare` is only ever\n * reached from a finished `catch-up-pre`. Until the flip the declaration in\n * force is the old one, so a change that stalls or is canceled before it\n * has changed nothing anybody can observe — except refusals honored on more\n * sites, which is the safe direction. A rename moves nothing, and runs the\n * declare and finish alone.\n *\n * ## One invocation at a time, and any invocation may finish it\n *\n * The console's progress panel calls `continue` while it is open, and the\n * cron backstop calls every fifteen minutes. Each claims a LEASE on the job\n * by transaction and renews it on every unit it saves; a lease that lapses\n * (a process that died) is simply claimed by the next caller. A unit is one\n * page of one store, or one participant call, and every unit is idempotent,\n * so a unit re-run after a crash writes nothing it already wrote.\n *\n * ## A failure is retried, and five in a row are shown\n *\n * A unit that throws releases the lease and counts a failure against the job;\n * the next invocation retries it from its cursor. Five consecutive failures\n * mark the job STALLED — the activity log says so once, and the panel shows\n * the error with a retry — and the cron keeps trying. A participant's throw\n * is one of these: the change never flips past a participant that could not\n * carry its refusals (see `plugin-consent-group-change.ts`).\n */\n\nimport {\n CONSENT_GROUP_CHANGE_STEPS,\n CONSENT_GROUP_CHANGES_COLLECTION,\n CONSENT_GROUP_RENAME_STEPS,\n CONSENT_GROUP_SWEEP_DELAY_MS,\n CONSENT_GROUPS_CHANGE_FIELD,\n type ConsentGroupChangeCounts,\n type ConsentGroupChangeLine,\n type ConsentGroupChangePlan,\n type ConsentGroupChangePreview,\n type ConsentGroupChangeStatus,\n type ConsentGroupChangeStep,\n type ConsentGroupChangeProgress,\n type ConsentGroupDeclaration,\n type ConsentGroupsApiInFlight,\n type ConsentGroupsApiRefusal,\n type ConsentGroupsApiStale,\n type ConsentGroupsChangeMarker,\n type ConsentGroupsChangePhase,\n consentGroupChangePhaseOf,\n consentGroupDisclosureChanges,\n consentGroupsFingerprint,\n describeConsentGroupChangeLine,\n discardedConsentGroupIds,\n estimateConsentGroupChange,\n orgSiteIds,\n planConsentGroupChange,\n readConsentGroupsChange,\n validateConsentGroupDeclaration,\n} from '@aglyn/aglyn/app-utils/consent-group-change'\nimport {\n CONSENT_GROUPS_FIELD,\n consentGroupsAwaitConfirmation,\n readConsentGroups,\n} from '@aglyn/aglyn/app-utils/consent-groups'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport {\n DECLARED_SUBSCRIPTION_TOPICS,\n TOPIC_OPT_OUTS_SUBCOLLECTION,\n} from '@aglyn/aglyn/app-utils/subscription-topics'\nimport {\n MARKETING_CONSENT_BY_HOST_FIELD,\n MARKETING_CONSENT_FIELD,\n resolveMarketingConsentPolicy,\n} from '@aglyn/aglyn/app-utils/marketing-consent'\nimport { isOrgWideMember } from '@aglyn/aglyn/app-utils/organizations'\nimport {\n type ConsentGroupChangeParticipant,\n type ConsentGroupChangeParticipantPhase,\n listPluginConsentGroupParticipants,\n previewPluginConsentGroupChange,\n} from '@aglyn/aglyn/plugin-manager/plugin-consent-group-change'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n CONSENT_GROUP_CARRY_STORES,\n CONSENT_GROUP_CARRY_SUBCOLLECTIONS,\n type ConsentGroupCarryStore,\n carryConsentGroupRefusals,\n} from './consent-group-carry'\nimport firebaseAdmin from './firebase-admin'\nimport { getLockdownVerdict } from './lockdown'\nimport { logOrgActivity } from './organizations'\nimport { carryRetainedRefusals, retainedRefusalsStore } from './retained-refusals'\n\n/** How long a claimed job stays claimed without a renewal. */\nexport const CONSENT_GROUP_CHANGE_LEASE_MS = 90_000\n\n/** Consecutive failed units before a job is shown as stalled. */\nexport const CONSENT_GROUP_CHANGE_STALL_AFTER = 5\n\n/**\n * How far before the carry began a catch-up reads. A refusal filed in the\n * seconds the carry's first page was being read, with a clock a little\n * behind, is inside it.\n */\nconst CATCH_UP_OVERLAP_MS = 5 * 60_000\n\n/** Left before a deadline for the last save. */\nconst DEADLINE_SAFETY_MS = 5_000\n\n/** The phases the cron looks for a change in. */\nconst RUNNING_PHASES: readonly ConsentGroupsChangePhase[] = ['carry', 'rehome', 'sweep']\n\n/** Who asked, as the activity log records it. */\nexport interface ConsentGroupChangeActor {\n uid: string | null\n email?: string | null\n}\n\n/** Everything the executor reaches outside itself, injectable for the specs. */\nexport interface ConsentGroupChangeContext {\n firestore?: FirebaseFirestore.Firestore\n log?: typeof logOrgActivity\n now?: () => number\n /** Whether a lockdown refuses the cron acting for this org and actor. */\n lockedOut?: (org: Record<string, unknown>, uid: string | null) => Promise<boolean>\n}\n\ninterface Context {\n firestore: FirebaseFirestore.Firestore\n log: typeof logOrgActivity\n now: () => number\n lockedOut: (org: Record<string, unknown>, uid: string | null) => Promise<boolean>\n}\n\nfunction contextOf(input: ConsentGroupChangeContext): Context {\n return {\n firestore: input.firestore ?? firebaseAdmin.app().firestore(),\n log: input.log ?? logOrgActivity,\n now: input.now ?? Date.now,\n lockedOut:\n input.lockedOut ??\n (async (org, uid) =>\n (await getLockdownVerdict({ org: org as never, uid, intent: 'write' })) !== null),\n }\n}\n\n/** Where a unit of the current step stands. */\ninterface UnitState {\n cursor: string | null\n done: boolean\n}\n\n/** The job document, `orgs/{orgId}/consentGroupChanges/{changeId}`. */\nexport interface ConsentGroupChangeJob {\n changeId: string\n orgId: string\n status: ConsentGroupChangeStatus\n step: ConsentGroupChangeStep\n /** The marker's phase while running; `done` or `canceled` after. */\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n /** The steps this change runs, in order. */\n steps: ConsentGroupChangeStep[]\n /** The stored declaration it started from: what the flip re-checks. */\n beforeFingerprint: string\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n plan: Omit<ConsentGroupChangePlan, 'before' | 'after'>\n /** The plan's lines as the activity log writes them. */\n lines: string[]\n siteNames: Record<string, string>\n /** The participants registered when it started — every one of them runs. */\n participants: string[]\n /** The current step's units, by key. */\n units: Record<string, UnitState>\n counts: ConsentGroupChangeCounts\n sitesReceiving: string[]\n plugins: Record<string, Record<string, number>>\n lease: { owner: string; untilMs: number } | null\n failures: number\n stalled: boolean\n lastError: string | null\n actor: { uid: string | null; email: string | null }\n startedAtMs: number\n carryStartedAtMs: number\n declaredAtMs: number | null\n finishedAtMs: number | null\n canceledAtMs: number | null\n}\n\n/** What the route answers for a job. */\nexport interface ConsentGroupChangeStatusBody {\n changeId: string\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n done: boolean\n progress: ConsentGroupChangeProgress\n}\n\n/** A refusal the route turns into its status code and body. */\nexport type ConsentGroupChangeRefusal =\n | { ok: false; status: 400; body: ConsentGroupsApiRefusal }\n | { ok: false; status: 404; body: { error: string } }\n | { ok: false; status: 409; body: ConsentGroupsApiStale | ConsentGroupsApiInFlight }\n\nconst NOT_FOUND = { ok: false as const, status: 404 as const, body: { error: 'No such consent group change' } }\n\n/** A caller whose lease was taken, or whose job was canceled, under it. */\nclass LeaseLost extends Error {}\n\nconst orgDoc = (ctx: Context, orgId: string) => ctx.firestore.collection('orgs').doc(orgId)\nconst jobDoc = (ctx: Context, orgId: string, changeId: string) =>\n orgDoc(ctx, orgId).collection(CONSENT_GROUP_CHANGES_COLLECTION).doc(changeId)\n\n/** The job as the plan the participants and the carries read. */\nfunction planOf(job: ConsentGroupChangeJob): ConsentGroupChangePlan {\n return { ...job.plan, before: job.before, after: job.after }\n}\n\n/** An error as the job records it: bounded, and with no address in it. */\nfunction sanitizeError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error ?? 'Unknown error')\n return message.replace(/[^\\s@]+@[^\\s@]+/g, '[address]').slice(0, 300) || 'Unknown error'\n}\n\nexport function consentGroupChangeStatus(\n job: ConsentGroupChangeJob,\n nowMs: number = Date.now(),\n): ConsentGroupChangeStatusBody {\n const declaredAtMs = job.declaredAtMs ?? null\n const index = job.steps.indexOf(job.step)\n return {\n changeId: job.changeId,\n phase: job.phase,\n done: job.status !== 'running',\n progress: {\n status: job.status,\n step: job.step,\n stepIndex: index >= 0 ? index + 1 : job.steps.length,\n stepCount: job.steps.length,\n declaredAtMs,\n sweepAtMs:\n declaredAtMs !== null && !job.plan.renameOnly\n ? declaredAtMs + CONSENT_GROUP_SWEEP_DELAY_MS\n : null,\n stalled: job.stalled === true,\n failures: job.failures ?? 0,\n lastError: job.lastError ?? null,\n leaseUntilMs: job.lease && job.lease.untilMs > nowMs ? job.lease.untilMs : null,\n // A job started before the retained store was carried has no count for it.\n counts: { retainedRefusals: 0, ...job.counts },\n sitesReceiving: job.sitesReceiving?.length ?? 0,\n plugins: job.plugins ?? {},\n },\n }\n}\n\n/*==========================================\n * VALIDATING AND PLANNING — shared by the preview and the start\n *=========================================*/\n\ninterface Prepared {\n org: Record<string, unknown>\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n plan: ConsentGroupChangePlan\n siteIds: string[]\n}\n\nfunction prepare(\n org: Record<string, unknown>,\n expected: unknown,\n groups: unknown,\n): Prepared | ConsentGroupChangeRefusal {\n const marker = readConsentGroupsChange(org)\n if (marker) {\n return {\n ok: false,\n status: 409,\n body: {\n error: 'A consent group change is still finishing',\n changeId: marker.changeId,\n phase: marker.phase,\n },\n }\n }\n const raw = org[CONSENT_GROUPS_FIELD]\n if (consentGroupsFingerprint(raw) !== consentGroupsFingerprint(expected)) {\n return {\n ok: false,\n status: 409,\n body: {\n error: 'Someone else changed consent groups while you were editing',\n current:\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : null,\n },\n }\n }\n const before = readConsentGroups(org)\n const siteIds = orgSiteIds(org)\n const validation = validateConsentGroupDeclaration({\n groups,\n before,\n siteIds,\n reserved:\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? Object.keys(raw as Record<string, unknown>)\n : [],\n })\n if (!validation.ok) {\n return {\n ok: false,\n status: 400,\n body: {\n error: 'Those consent groups cannot be saved',\n errors: (validation as { errors: ConsentGroupsApiRefusal['errors'] }).errors,\n },\n }\n }\n const after = (validation as { after: ConsentGroupDeclaration }).after\n return {\n org,\n before,\n after,\n plan: planConsentGroupChange(before, after, { siteIds }),\n siteIds,\n }\n}\n\nconst isRefusal = (value: unknown): value is ConsentGroupChangeRefusal =>\n Boolean(value && typeof value === 'object' && (value as { ok?: unknown }).ok === false)\n\n/** Every site a plan names, for the names its lines are written with. */\nfunction plannedSites(plan: ConsentGroupChangePlan): string[] {\n return [\n ...new Set([\n ...plan.hostIds,\n ...Object.values(plan.before).flatMap((group) => group.hostIds),\n ...Object.values(plan.after).flatMap((group) => group.hostIds),\n ]),\n ]\n}\n\n/** What the console calls each site: its name, its subdomain, or its id. */\nasync function siteNamesFor(ctx: Context, hostIds: readonly string[]): Promise<Record<string, string>> {\n if (!hostIds.length) return {}\n const hosts = ctx.firestore.collection('hosts')\n const snapshots = await ctx.firestore.getAll(...hostIds.map((id) => hosts.doc(id)))\n const names: Record<string, string> = {}\n snapshots.forEach((snapshot, index) => {\n const id = hostIds[index]\n names[id] = String(snapshot.get('name') || snapshot.get('subdomain') || id)\n })\n return names\n}\n\nconst describeLines = (lines: readonly ConsentGroupChangeLine[], names: Record<string, string>) =>\n lines.map((line) => describeConsentGroupChangeLine(line, (hostId) => names[hostId] ?? hostId))\n\n/*==========================================\n * THE PREVIEW\n *=========================================*/\n\nasync function countOf(query: FirebaseFirestore.Query): Promise<number> {\n const snapshot = await query.count().get()\n return Number(snapshot.data().count) || 0\n}\n\n/**\n * What a change would do, counted and worded, writing nothing.\n *\n * Refused exactly as `apply` would be — a change in flight, a stale\n * `expected`, an invalid declaration — so the review step never shows a\n * change the next click cannot make.\n */\nexport async function previewConsentGroupChange(\n input: {\n orgId: string\n expected: unknown\n groups: unknown\n /** The org document, when the caller already read it. */\n org?: Record<string, unknown> | null\n } & ConsentGroupChangeContext,\n): Promise<{ ok: true; preview: ConsentGroupChangePreview } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const org =\n input.org ?? ((await orgDoc(ctx, input.orgId).get()).data() as Record<string, unknown> | undefined)\n if (!org) return { ok: false, status: 404, body: { error: 'No such workspace' } }\n const prepared = prepare(org, input.expected, input.groups)\n if (isRefusal(prepared)) return prepared\n const { plan, before, after } = prepared as Prepared\n\n const db = ctx.firestore\n const cache = new Map<string, Promise<number>>()\n const cached = (key: string, query: () => FirebaseFirestore.Query) => {\n let pending = cache.get(key)\n if (!pending) {\n pending = countOf(query())\n cache.set(key, pending)\n }\n return pending\n }\n const store = (hostId: string, name: ConsentGroupCarryStore) =>\n db.collection('hosts').doc(hostId).collection(CONSENT_GROUP_CARRY_SUBCOLLECTIONS[name])\n const suppressions = (hostId: string) => cached(`s:${hostId}`, () => store(hostId, 'siteSuppressions'))\n const optOuts = (hostId: string) => cached(`t:${hostId}`, () => store(hostId, 'topicOptOuts'))\n const paces = (hostId: string) =>\n cached(`p:${hostId}`, () => store(hostId, 'paces').where('cadence', '!=', null))\n const retained = (hostId: string) =>\n cached(`r:${hostId}`, () =>\n retainedRefusalsStore(orgDoc(ctx, input.orgId)).where(\n `${MARKETING_CONSENT_BY_HOST_FIELD}.${hostId}.${MARKETING_CONSENT_FIELD}`,\n '==',\n false,\n ),\n )\n\n const carries = await Promise.all(\n plan.carries.map(async (carry) => ({\n toHostId: carry.toHostId,\n fromHostId: carry.fromHostId,\n siteSuppressions: await suppressions(carry.fromHostId),\n topicOptOuts: await optOuts(carry.fromHostId),\n paces: await paces(carry.fromHostId),\n retainedRefusals: await retained(carry.fromHostId),\n })),\n )\n\n const membersIn = (declaration: ConsentGroupDeclaration, hostId: string) =>\n Object.values(declaration).find((group) => group.hostIds.includes(hostId))?.hostIds ?? [hostId]\n const joining: Array<{ hostId: string; siblings: string[] }> = []\n for (const hostId of [...new Set(Object.values(after).flatMap((group) => group.hostIds))].sort()) {\n const had = new Set(membersIn(before, hostId))\n const siblings = membersIn(after, hostId).filter((id) => id !== hostId && !had.has(id))\n if (siblings.length) joining.push({ hostId, siblings })\n }\n const inherited = await Promise.all(\n joining.map(async ({ hostId, siblings }) => {\n let refusals = 0\n for (const sibling of siblings) {\n refusals += (await suppressions(sibling)) + (await optOuts(sibling))\n }\n return { hostId, refusals }\n }),\n )\n\n /*\n * A pending confirmation holds a sibling's mail only with the switch on,\n * so only then does a separation release anything. Counted over the\n * built-in streams: the catalog of an organization's own streams belongs to\n * the Email plugin, and a stream it added is not counted here.\n */\n const pendingHolds: ConsentGroupChangePreview['pendingHolds'] = []\n if (consentGroupsAwaitConfirmation(org)) {\n const released = new Map<string, string[]>()\n for (const carry of plan.carries) {\n released.set(carry.toHostId, [...(released.get(carry.toHostId) ?? []), carry.fromHostId])\n }\n for (const [hostId, releasedHostIds] of [...released.entries()].sort()) {\n for (const topic of DECLARED_SUBSCRIPTION_TOPICS) {\n const count = await countOf(\n db\n .collection('hosts')\n .doc(hostId)\n .collection(TOPIC_OPT_OUTS_SUBCOLLECTION)\n .where(`topics.${topic.id}.confirmedAt`, '==', null),\n )\n if (count > 0) pendingHolds.push({ hostId, topicId: topic.id, count, releasedHostIds })\n }\n }\n }\n\n const changedGroups = Object.entries(after)\n .filter(([id, group]) => {\n const was = before[id]\n return !was || was.hostIds.join('\\n') !== group.hostIds.join('\\n')\n })\n .map(([, group]) => group.hostIds)\n let partialAccessMembers = 0\n if (changedGroups.length) {\n const members = await orgDoc(ctx, input.orgId).collection('members').get()\n for (const member of members.docs) {\n const data = member.data() as Record<string, unknown>\n if (isOrgWideMember(data)) continue\n const reach = new Set(Object.keys((data['hostAccess'] ?? {}) as Record<string, unknown>))\n if (\n changedGroups.some(\n (hostIds) =>\n hostIds.some((id) => reach.has(id)) && !hostIds.every((id) => reach.has(id)),\n )\n ) {\n partialAccessMembers += 1\n }\n }\n }\n\n const policy = resolveMarketingConsentPolicy(org['marketingConsentPolicy'])\n const forwardPolicyWarning =\n policy.mode === 'forward' && joining.length\n ? { hostIds: joining.map((entry) => entry.hostId), enforceFromMs: policy.enforceFromMs }\n : null\n\n const participants = await previewPluginConsentGroupChange({ orgId: input.orgId, plan })\n const names = await siteNamesFor(ctx, plannedSites(plan))\n const texts = describeLines(plan.lines, names)\n const documents =\n carries.reduce(\n (total, carry) =>\n total + carry.siteSuppressions + carry.topicOptOuts + carry.paces + carry.retainedRefusals,\n 0,\n ) +\n participants.reduce(\n (total, entry) =>\n total + (entry.lines ?? []).reduce((sum, line) => sum + (line.count ?? 0), 0),\n 0,\n )\n\n return {\n ok: true,\n preview: {\n before,\n after,\n discarded: discardedConsentGroupIds(org),\n lines: plan.lines.map((line, index) => ({ ...line, text: texts[index] })),\n disclosures: consentGroupDisclosureChanges(before, after),\n carries,\n inherited,\n pendingHolds,\n partialAccessMembers,\n forwardPolicyWarning,\n participants,\n capturesDisclosing: ['form'],\n estimate: estimateConsentGroupChange(plan, documents),\n },\n }\n}\n\n/*==========================================\n * STARTING\n *=========================================*/\n\n/**\n * Starts a change: the org marker and the job, in one transaction that\n * refuses a change in flight, a stale `expected` and an invalid declaration.\n * Writes nothing else — the caller advances it.\n */\nexport async function startConsentGroupChange(\n input: {\n orgId: string\n actor: ConsentGroupChangeActor\n expected: unknown\n groups: unknown\n } & ConsentGroupChangeContext,\n): Promise<{ ok: true; changeId: string; job: ConsentGroupChangeJob } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const orgRef = orgDoc(ctx, input.orgId)\n const snapshot = await orgRef.get()\n if (!snapshot.exists) return { ok: false, status: 404, body: { error: 'No such workspace' } }\n const outside = prepare(snapshot.data() as Record<string, unknown>, input.expected, input.groups)\n if (isRefusal(outside)) return outside\n // Names are words for the log, read once and outside the transaction.\n const names = await siteNamesFor(ctx, plannedSites((outside as Prepared).plan))\n const participants = listPluginConsentGroupParticipants().map((entry) => entry.pluginId)\n const changeId = createResourceUid()\n const jobRef = jobDoc(ctx, input.orgId, changeId)\n const nowMs = ctx.now()\n\n const started = await ctx.firestore.runTransaction(async (transaction) => {\n const current = await transaction.get(orgRef)\n if (!current.exists) {\n return { ok: false as const, status: 404 as const, body: { error: 'No such workspace' } }\n }\n const prepared = prepare(current.data() as Record<string, unknown>, input.expected, input.groups)\n if (isRefusal(prepared)) return prepared\n const { plan, before, after, org } = prepared as Prepared\n const steps = plan.renameOnly ? [...CONSENT_GROUP_RENAME_STEPS] : [...CONSENT_GROUP_CHANGE_STEPS]\n const job: ConsentGroupChangeJob = {\n changeId,\n orgId: input.orgId,\n status: 'running',\n step: steps[0],\n phase: 'carry',\n steps,\n beforeFingerprint: consentGroupsFingerprint(org[CONSENT_GROUPS_FIELD]),\n before,\n after,\n plan: {\n carries: plan.carries,\n flows: plan.flows,\n lines: plan.lines,\n hostIds: plan.hostIds,\n renameOnly: plan.renameOnly,\n },\n lines: describeLines(plan.lines, names),\n siteNames: names,\n participants: plan.renameOnly ? [] : participants,\n units: {},\n counts: { siteSuppressions: 0, topicOptOuts: 0, paces: 0, retainedRefusals: 0 },\n sitesReceiving: [],\n plugins: {},\n lease: null,\n failures: 0,\n stalled: false,\n lastError: null,\n actor: { uid: input.actor.uid ?? null, email: input.actor.email ?? null },\n startedAtMs: nowMs,\n carryStartedAtMs: nowMs,\n declaredAtMs: null,\n finishedAtMs: null,\n canceledAtMs: null,\n }\n const marker: ConsentGroupsChangeMarker = {\n changeId,\n phase: 'carry',\n hostIds: plan.hostIds,\n startedAtMs: nowMs,\n }\n transaction.update(orgRef, {\n [CONSENT_GROUPS_CHANGE_FIELD]: marker,\n updatedAt: FieldValue.serverTimestamp(),\n })\n transaction.create(jobRef, {\n ...job,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n return { ok: true as const, job }\n })\n if (isRefusal(started)) return started\n const { job } = started as { job: ConsentGroupChangeJob }\n await ctx.log(\n input.orgId,\n { uid: job.actor.uid, email: job.actor.email },\n `Started a consent group change: ${job.lines.join('; ')}`,\n { type: 'org', id: input.orgId },\n )\n return { ok: true, changeId, job }\n}\n\n/*==========================================\n * ADVANCING\n *=========================================*/\n\ntype Unit =\n | { kind: 'carry'; key: string; carryIndex: number; store: ConsentGroupCarryStore; sinceMs: number | null }\n | { kind: 'retained'; key: string; carryIndex: number; sinceMs: number | null }\n | { kind: 'participant'; key: string; pluginId: string; phase: ConsentGroupChangeParticipantPhase }\n\n/** The current step's units, in the order they run. */\nfunction unitsOf(job: ConsentGroupChangeJob): Unit[] {\n const carries = (sinceMs: number | null): Unit[] =>\n job.plan.carries.flatMap((_carry, carryIndex): Unit[] => [\n ...CONSENT_GROUP_CARRY_STORES.map((store) => ({\n kind: 'carry' as const,\n key: `c${carryIndex}:${store}`,\n carryIndex,\n store,\n sinceMs,\n })),\n { kind: 'retained' as const, key: `c${carryIndex}:retainedRefusals`, carryIndex, sinceMs },\n ])\n const participants = (phase: ConsentGroupChangeParticipantPhase): Unit[] =>\n job.participants.map((pluginId) => ({\n kind: 'participant' as const,\n key: `p:${pluginId}`,\n pluginId,\n phase,\n }))\n const since = job.carryStartedAtMs - CATCH_UP_OVERLAP_MS\n switch (job.step) {\n case 'carry':\n return [...carries(null), ...participants('carry')]\n case 'catch-up-pre':\n case 'catch-up-post':\n return [...carries(since), ...participants('carry')]\n case 'rehome':\n return participants('rehome')\n case 'sweep':\n return [...carries(null), ...participants('sweep')]\n default:\n return []\n }\n}\n\nfunction jobFrom(snapshot: FirebaseFirestore.DocumentSnapshot): ConsentGroupChangeJob | null {\n return snapshot.exists ? (snapshot.data() as ConsentGroupChangeJob) : null\n}\n\n/**\n * Saves a unit's result, renewing the lease — only while the job is still\n * running and still this caller's.\n */\nasync function save(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n fields: Partial<ConsentGroupChangeJob>,\n extra?: (transaction: FirebaseFirestore.Transaction, job: ConsentGroupChangeJob) => Promise<void>,\n): Promise<ConsentGroupChangeJob> {\n return ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job || job.status !== 'running' || job.lease?.owner !== owner) throw new LeaseLost()\n if (extra) await extra(transaction, job)\n const next: Partial<ConsentGroupChangeJob> = {\n ...fields,\n lease: { owner, untilMs: ctx.now() + CONSENT_GROUP_CHANGE_LEASE_MS },\n failures: 0,\n stalled: false,\n lastError: null,\n }\n transaction.update(jobRef, { ...next, updatedAt: FieldValue.serverTimestamp() })\n return { ...job, ...next }\n })\n}\n\nfunction addCounts(\n base: Record<string, number> | undefined,\n more: Record<string, number>,\n): Record<string, number> {\n const out: Record<string, number> = { ...(base ?? {}) }\n for (const [key, value] of Object.entries(more)) {\n if (typeof value === 'number' && Number.isFinite(value)) out[key] = (out[key] ?? 0) + value\n }\n return out\n}\n\nasync function runUnit(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n unit: Unit,\n deadlineMs: number,\n): Promise<ConsentGroupChangeJob> {\n const state = job.units[unit.key] ?? { cursor: null, done: false }\n if (unit.kind === 'carry' || unit.kind === 'retained') {\n const carry = job.plan.carries[unit.carryIndex]\n const page =\n unit.kind === 'carry'\n ? await carryConsentGroupRefusals({\n firestore: ctx.firestore,\n store: unit.store,\n carry,\n changeId: job.changeId,\n cursor: state.cursor,\n sinceMs: unit.sinceMs,\n })\n : await carryRetainedRefusals({\n firestore: ctx.firestore,\n orgId: job.orgId,\n carry,\n changeId: job.changeId,\n cursor: state.cursor,\n sinceMs: unit.sinceMs,\n })\n const counted = unit.kind === 'carry' ? unit.store : 'retainedRefusals'\n const counts = { ...job.counts, [counted]: (job.counts[counted] ?? 0) + page.written }\n const sitesReceiving =\n page.written > 0 && !job.sitesReceiving.includes(carry.toHostId)\n ? [...job.sitesReceiving, carry.toHostId]\n : job.sitesReceiving\n return save(ctx, jobRef, owner, {\n units: { ...job.units, [unit.key]: { cursor: page.cursor, done: page.done } },\n counts,\n sitesReceiving,\n })\n }\n const participant = participantFor(unit.pluginId)\n const result = await participant.run({\n orgId: job.orgId,\n changeId: job.changeId,\n plan: planOf(job),\n phase: unit.phase,\n cursor: state.cursor,\n deadlineMs: deadlineMs - DEADLINE_SAFETY_MS,\n dryRun: false,\n })\n return save(ctx, jobRef, owner, {\n units: { ...job.units, [unit.key]: { cursor: result.cursor ?? null, done: result.done === true } },\n plugins: { ...job.plugins, [unit.pluginId]: addCounts(job.plugins[unit.pluginId], result.counts ?? {}) },\n })\n}\n\nfunction participantFor(pluginId: string): ConsentGroupChangeParticipant {\n const entry = listPluginConsentGroupParticipants().find((candidate) => candidate.pluginId === pluginId)\n if (!entry) {\n // The change started with this participant; running on without it would\n // skip its share. Another process — one whose declarations registered it —\n // picks the job up.\n throw new Error(`The ${pluginId} plugin's part of this change is not available here`)\n }\n return entry.participant\n}\n\n/** Moves the job to the step after its current one. */\nasync function nextStep(\n ctx: Context,\n orgId: string,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n): Promise<ConsentGroupChangeJob> {\n const next = job.steps[job.steps.indexOf(job.step) + 1] ?? 'finish'\n const phase = consentGroupChangePhaseOf(next)\n if (phase === job.phase) return save(ctx, jobRef, owner, { step: next, units: {} })\n // Entering the sweep: the marker says so, for the readers that hold off\n // while holder records may still be moving.\n const orgRef = orgDoc(ctx, orgId)\n return save(ctx, jobRef, owner, { step: next, units: {}, phase }, async (transaction) => {\n const marker = readConsentGroupsChange((await transaction.get(orgRef)).data())\n if (marker?.changeId === job.changeId) {\n transaction.update(orgRef, { [`${CONSENT_GROUPS_CHANGE_FIELD}.phase`]: phase })\n }\n })\n}\n\n/**\n * THE FLIP — the one write of `consentGroups` anywhere in the platform.\n *\n * The marker must still name this change in its carry phase and the stored\n * declaration must be the one the job started from; otherwise nothing is\n * written and the unit fails. `update`, not a merge, so a removed group's id\n * vanishes with it; an empty declaration deletes the field.\n */\nasync function declare(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n): Promise<ConsentGroupChangeJob> {\n const orgRef = orgDoc(ctx, job.orgId)\n const nowMs = ctx.now()\n const renameOnly = job.plan.renameOnly\n const declared = await save(\n ctx,\n jobRef,\n owner,\n renameOnly\n ? {\n step: 'finish',\n status: 'done',\n phase: 'done',\n declaredAtMs: nowMs,\n finishedAtMs: nowMs,\n units: {},\n }\n : { step: 'catch-up-post', phase: 'rehome', declaredAtMs: nowMs, units: {} },\n async (transaction) => {\n const org = (await transaction.get(orgRef)).data() as Record<string, unknown> | undefined\n const marker = readConsentGroupsChange(org)\n if (!org || marker?.changeId !== job.changeId || marker.phase !== 'carry') {\n throw new Error('The change no longer holds the organization, so the new groups were not saved')\n }\n if (consentGroupsFingerprint(org[CONSENT_GROUPS_FIELD]) !== job.beforeFingerprint) {\n throw new Error('The consent groups changed outside this change, so the new groups were not saved')\n }\n transaction.update(orgRef, {\n [CONSENT_GROUPS_FIELD]: Object.keys(job.after).length ? job.after : FieldValue.delete(),\n [CONSENT_GROUPS_CHANGE_FIELD]: renameOnly\n ? FieldValue.delete()\n : { ...marker, phase: 'rehome', declaredAtMs: nowMs },\n updatedAt: FieldValue.serverTimestamp(),\n })\n },\n )\n const finished = renameOnly ? { ...declared, lease: null } : declared\n if (renameOnly) await jobRef.update({ lease: null })\n for (const line of job.lines) {\n await ctx.log(job.orgId, job.actor, line, { type: 'org', id: job.orgId })\n }\n if (renameOnly) {\n await ctx.log(job.orgId, job.actor, 'Finished a consent group change', {\n type: 'org',\n id: job.orgId,\n })\n }\n return finished\n}\n\n/** The \"Finished\" line: the carries' totals, then each participant's clause. */\nfunction finishedLine(job: ConsentGroupChangeJob): string {\n const copied =\n job.counts.siteSuppressions +\n job.counts.topicOptOuts +\n job.counts.paces +\n (job.counts.retainedRefusals ?? 0)\n const sites = job.sitesReceiving.length\n const clauses = [\n `${copied} ${copied === 1 ? 'opt-out' : 'opt-outs'} copied to ${sites} ${sites === 1 ? 'site' : 'sites'}`,\n ]\n for (const pluginId of job.participants) {\n const entry = listPluginConsentGroupParticipants().find((candidate) => candidate.pluginId === pluginId)\n const clause = entry?.participant.summarize?.(job.plugins[pluginId] ?? {})\n if (clause) clauses.push(clause)\n }\n return `Finished a consent group change: ${clauses.join('; ')}`\n}\n\nasync function finish(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n): Promise<ConsentGroupChangeJob> {\n const orgRef = orgDoc(ctx, job.orgId)\n const nowMs = ctx.now()\n const done = await save(\n ctx,\n jobRef,\n owner,\n { status: 'done', phase: 'done', step: 'finish', finishedAtMs: nowMs, units: {} },\n async (transaction) => {\n const marker = readConsentGroupsChange((await transaction.get(orgRef)).data())\n if (marker?.changeId === job.changeId) {\n transaction.update(orgRef, {\n [CONSENT_GROUPS_CHANGE_FIELD]: FieldValue.delete(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n },\n )\n await jobRef.update({ lease: null })\n await ctx.log(job.orgId, job.actor, finishedLine(done), { type: 'org', id: job.orgId })\n return { ...done, lease: null }\n}\n\n/**\n * One unit of work, or nothing when the job is waiting for its sweep.\n */\nasync function advanceOnce(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n job: ConsentGroupChangeJob,\n owner: string,\n deadlineMs: number,\n): Promise<{ job: ConsentGroupChangeJob; idle: boolean }> {\n if (job.step === 'declare') return { job: await declare(ctx, jobRef, job, owner), idle: false }\n if (job.step === 'finish') return { job: await finish(ctx, jobRef, job, owner), idle: false }\n if (job.step === 'sweep-wait') {\n if (ctx.now() < (job.declaredAtMs ?? 0) + CONSENT_GROUP_SWEEP_DELAY_MS) return { job, idle: true }\n return { job: await nextStep(ctx, job.orgId, jobRef, job, owner), idle: false }\n }\n const unit = unitsOf(job).find((candidate) => !job.units[candidate.key]?.done)\n if (!unit) return { job: await nextStep(ctx, job.orgId, jobRef, job, owner), idle: false }\n const before = job.units[unit.key]?.cursor ?? null\n const after = await runUnit(ctx, jobRef, job, owner, unit, deadlineMs)\n const state = after.units[unit.key]\n // A participant that stopped where it started — out of time before its\n // first page — has nothing more to give this invocation.\n return { job: after, idle: unit.kind === 'participant' && !state?.done && (state?.cursor ?? null) === before }\n}\n\n/** Claims the lease, or answers why not. `null` for a job that does not exist. */\nasync function claim(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n): Promise<{ job: ConsentGroupChangeJob; mine: boolean } | null> {\n return ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job) return null\n const nowMs = ctx.now()\n if (job.status !== 'running') return { job, mine: false }\n if (job.lease && job.lease.untilMs > nowMs && job.lease.owner !== owner) return { job, mine: false }\n const lease = { owner, untilMs: nowMs + CONSENT_GROUP_CHANGE_LEASE_MS }\n transaction.update(jobRef, { lease })\n return { job: { ...job, lease }, mine: true }\n })\n}\n\nasync function release(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n): Promise<ConsentGroupChangeJob | null> {\n return ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job) return null\n if (job.lease?.owner !== owner) return job\n transaction.update(jobRef, { lease: null })\n return { ...job, lease: null }\n })\n}\n\n/** Counts a failed unit against the job, and says so once when it stalls. */\nasync function recordFailure(\n ctx: Context,\n jobRef: FirebaseFirestore.DocumentReference,\n owner: string,\n error: unknown,\n): Promise<ConsentGroupChangeJob | null> {\n const lastError = sanitizeError(error)\n const outcome = await ctx.firestore.runTransaction(async (transaction) => {\n const job = jobFrom(await transaction.get(jobRef))\n if (!job) return null\n if (job.lease?.owner !== owner) return { job, newlyStalled: false }\n const failures = (job.failures ?? 0) + 1\n const stalled = failures >= CONSENT_GROUP_CHANGE_STALL_AFTER\n const fields = { failures, stalled, lastError, lease: null }\n transaction.update(jobRef, { ...fields, updatedAt: FieldValue.serverTimestamp() })\n return { job: { ...job, ...fields }, newlyStalled: stalled && !job.stalled }\n })\n if (!outcome) return null\n if (outcome.newlyStalled) {\n await ctx.log(\n outcome.job.orgId,\n outcome.job.actor,\n 'A consent group change stopped with an error; it will try again',\n { type: 'org', id: outcome.job.orgId },\n )\n }\n console.error(`[consent-group-change] a unit of ${outcome.job.changeId} failed: ${lastError}`)\n return outcome.job\n}\n\n/**\n * Advances a change until it finishes, waits for its sweep, or reaches\n * `deadlineMs`. Answers the job's status either way — including when another\n * caller holds it, which is not a failure.\n */\nexport async function advanceConsentGroupChange(\n input: {\n orgId: string\n changeId: string\n deadlineMs: number\n /** Who holds the lease; one per invocation. */\n owner?: string\n } & ConsentGroupChangeContext,\n): Promise<{ ok: true; status: ConsentGroupChangeStatusBody } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const jobRef = jobDoc(ctx, input.orgId, input.changeId)\n const owner = input.owner ?? `advance:${createResourceUid()}`\n const claimed = await claim(ctx, jobRef, owner)\n if (!claimed) return NOT_FOUND\n let job = claimed.job\n if (!claimed.mine) return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n try {\n while (job.status === 'running' && ctx.now() < input.deadlineMs - DEADLINE_SAFETY_MS) {\n const outcome = await advanceOnce(ctx, jobRef, job, owner, input.deadlineMs)\n job = outcome.job\n if (outcome.idle) break\n }\n } catch (error) {\n if (!(error instanceof LeaseLost)) {\n job = (await recordFailure(ctx, jobRef, owner, error)) ?? job\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n }\n job = jobFrom(await jobRef.get()) ?? job\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n }\n if (job.status === 'running') job = (await release(ctx, jobRef, owner)) ?? job\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n}\n\n/*==========================================\n * CANCELING AND READING\n *=========================================*/\n\n/**\n * Stops a change before it takes effect: clears the marker and leaves every\n * refusal already carried, which only ever honors an opt-out on more sites.\n * Refused once the declaration has flipped — there is nothing left to stop\n * before, and the only way back is another change.\n */\nexport async function cancelConsentGroupChange(\n input: { orgId: string; changeId: string; actor: ConsentGroupChangeActor } & ConsentGroupChangeContext,\n): Promise<{ ok: true; status: ConsentGroupChangeStatusBody } | ConsentGroupChangeRefusal> {\n const ctx = contextOf(input)\n const orgRef = orgDoc(ctx, input.orgId)\n const jobRef = jobDoc(ctx, input.orgId, input.changeId)\n const outcome = await ctx.firestore.runTransaction(async (transaction) => {\n const [orgSnapshot, jobSnapshot] = await Promise.all([transaction.get(orgRef), transaction.get(jobRef)])\n const job = jobFrom(jobSnapshot)\n if (!job) return NOT_FOUND\n if (job.status !== 'running' || job.declaredAtMs) {\n return {\n ok: false as const,\n status: 409 as const,\n body: {\n error:\n job.status === 'running' || job.status === 'done'\n ? 'This change has already taken effect'\n : 'This change is no longer running',\n changeId: job.changeId,\n phase: job.phase,\n },\n }\n }\n const marker = readConsentGroupsChange(orgSnapshot.data())\n if (marker?.changeId === job.changeId) {\n transaction.update(orgRef, {\n [CONSENT_GROUPS_CHANGE_FIELD]: FieldValue.delete(),\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n const fields = {\n status: 'canceled' as const,\n phase: 'canceled' as const,\n canceledAtMs: ctx.now(),\n lease: null,\n }\n transaction.update(jobRef, { ...fields, updatedAt: FieldValue.serverTimestamp() })\n return { ok: true as const, job: { ...job, ...fields } }\n })\n if (isRefusal(outcome)) return outcome\n const { job } = outcome as { job: ConsentGroupChangeJob }\n await ctx.log(\n input.orgId,\n { uid: input.actor.uid ?? null, email: input.actor.email ?? null },\n 'Canceled a consent group change before it took effect',\n { type: 'org', id: input.orgId },\n )\n return { ok: true, status: consentGroupChangeStatus(job, ctx.now()) }\n}\n\n/** A job's status, or `null` for a change the org never had. */\nexport async function readConsentGroupChangeStatus(\n input: { orgId: string; changeId: string } & ConsentGroupChangeContext,\n): Promise<ConsentGroupChangeStatusBody | null> {\n const ctx = contextOf(input)\n const job = jobFrom(await jobDoc(ctx, input.orgId, input.changeId).get())\n return job ? consentGroupChangeStatus(job, ctx.now()) : null\n}\n\n/*==========================================\n * THE CRON BACKSTOP\n *=========================================*/\n\nexport interface DueConsentGroupChangeReport {\n orgId: string\n changeId: string\n phase: ConsentGroupsChangePhase\n /** What happened on this run. */\n outcome: 'advanced' | 'listed' | 'locked' | 'orphaned' | 'skipped'\n status?: ConsentGroupChangeStatusBody\n error?: string\n}\n\n/**\n * Advances every change in flight, oldest org id first, until `deadlineMs`.\n *\n * A change nobody is watching still finishes: the progress panel is a\n * convenience, and this is the guarantee. A lockdown on the org — or on the\n * member who started the change — pauses it, like every other write the\n * workspace makes. A marker whose job document is gone is a change nothing\n * can finish, and is cleared rather than blocking every change after it.\n */\nexport async function advanceDueConsentGroupChanges(\n input: { deadlineMs: number; dryRun?: boolean; limit?: number } & ConsentGroupChangeContext,\n): Promise<DueConsentGroupChangeReport[]> {\n const ctx = contextOf(input)\n const snapshot = await ctx.firestore\n .collection('orgs')\n .where(`${CONSENT_GROUPS_CHANGE_FIELD}.phase`, 'in', [...RUNNING_PHASES])\n .limit(input.limit ?? 50)\n .get()\n const reports: DueConsentGroupChangeReport[] = []\n for (const org of snapshot.docs) {\n const data = org.data() as Record<string, unknown>\n const marker = readConsentGroupsChange(data)\n if (!marker) continue\n const base = { orgId: org.id, changeId: marker.changeId, phase: marker.phase }\n if (input.dryRun) {\n reports.push({ ...base, outcome: 'listed' })\n continue\n }\n if (ctx.now() >= input.deadlineMs - DEADLINE_SAFETY_MS) {\n reports.push({ ...base, outcome: 'skipped' })\n continue\n }\n try {\n const job = jobFrom(await jobDoc(ctx, org.id, marker.changeId).get())\n if (!job) {\n await orgDoc(ctx, org.id).update({ [CONSENT_GROUPS_CHANGE_FIELD]: FieldValue.delete() })\n console.error(`[consent-group-change] cleared a marker with no job in org ${org.id}`)\n reports.push({ ...base, outcome: 'orphaned' })\n continue\n }\n if (await ctx.lockedOut(data, job.actor?.uid ?? null)) {\n reports.push({ ...base, outcome: 'locked' })\n continue\n }\n const advanced = await advanceConsentGroupChange({\n orgId: org.id,\n changeId: marker.changeId,\n deadlineMs: input.deadlineMs,\n owner: `cron:${createResourceUid()}`,\n firestore: ctx.firestore,\n log: ctx.log,\n now: ctx.now,\n })\n reports.push({\n ...base,\n outcome: 'advanced',\n ...(advanced.ok ? { status: (advanced as { status: ConsentGroupChangeStatusBody }).status } : {}),\n })\n } catch (error) {\n reports.push({ ...base, outcome: 'advanced', error: sanitizeError(error) })\n }\n }\n return reports\n}\n"],"names":["CONSENT_GROUP_CHANGE_STEPS","CONSENT_GROUP_CHANGES_COLLECTION","CONSENT_GROUP_RENAME_STEPS","CONSENT_GROUP_SWEEP_DELAY_MS","CONSENT_GROUPS_CHANGE_FIELD","consentGroupChangePhaseOf","consentGroupDisclosureChanges","consentGroupsFingerprint","describeConsentGroupChangeLine","discardedConsentGroupIds","estimateConsentGroupChange","orgSiteIds","planConsentGroupChange","readConsentGroupsChange","validateConsentGroupDeclaration","CONSENT_GROUPS_FIELD","consentGroupsAwaitConfirmation","readConsentGroups","createResourceUid","DECLARED_SUBSCRIPTION_TOPICS","TOPIC_OPT_OUTS_SUBCOLLECTION","MARKETING_CONSENT_BY_HOST_FIELD","MARKETING_CONSENT_FIELD","resolveMarketingConsentPolicy","isOrgWideMember","listPluginConsentGroupParticipants","previewPluginConsentGroupChange","FieldValue","CONSENT_GROUP_CARRY_STORES","CONSENT_GROUP_CARRY_SUBCOLLECTIONS","carryConsentGroupRefusals","firebaseAdmin","getLockdownVerdict","logOrgActivity","carryRetainedRefusals","retainedRefusalsStore","CONSENT_GROUP_CHANGE_LEASE_MS","CONSENT_GROUP_CHANGE_STALL_AFTER","CATCH_UP_OVERLAP_MS","DEADLINE_SAFETY_MS","RUNNING_PHASES","contextOf","input","firestore","app","log","now","Date","lockedOut","org","uid","intent","NOT_FOUND","ok","status","body","error","LeaseLost","Error","orgDoc","ctx","orgId","collection","doc","jobDoc","changeId","planOf","job","plan","before","after","sanitizeError","message","String","replace","slice","consentGroupChangeStatus","nowMs","declaredAtMs","index","steps","indexOf","step","phase","done","progress","stepIndex","length","stepCount","sweepAtMs","renameOnly","stalled","failures","lastError","leaseUntilMs","lease","untilMs","counts","retainedRefusals","sitesReceiving","plugins","prepare","expected","groups","marker","raw","current","Array","isArray","siteIds","validation","reserved","Object","keys","errors","isRefusal","value","Boolean","plannedSites","Set","hostIds","values","flatMap","group","siteNamesFor","hosts","snapshots","getAll","map","id","names","forEach","snapshot","get","describeLines","lines","line","hostId","countOf","query","count","Number","data","previewConsentGroupChange","prepared","db","cache","Map","cached","key","pending","set","store","name","suppressions","optOuts","paces","where","retained","carries","Promise","all","carry","toHostId","fromHostId","siteSuppressions","topicOptOuts","membersIn","declaration","find","includes","joining","sort","had","siblings","filter","has","push","inherited","refusals","sibling","pendingHolds","released","releasedHostIds","entries","topic","topicId","changedGroups","was","join","partialAccessMembers","members","member","docs","reach","some","every","policy","forwardPolicyWarning","mode","entry","enforceFromMs","participants","texts","documents","reduce","total","sum","preview","discarded","text","disclosures","capturesDisclosing","estimate","startConsentGroupChange","orgRef","exists","outside","pluginId","jobRef","started","runTransaction","transaction","beforeFingerprint","flows","siteNames","units","actor","email","startedAtMs","carryStartedAtMs","finishedAtMs","canceledAtMs","update","updatedAt","serverTimestamp","create","createdAt","type","unitsOf","sinceMs","_carry","carryIndex","kind","since","jobFrom","save","owner","fields","extra","next","addCounts","base","more","out","isFinite","runUnit","unit","deadlineMs","result","state","cursor","page","counted","written","participant","participantFor","run","dryRun","candidate","nextStep","declare","declared","delete","finished","finishedLine","copied","sites","clauses","clause","summarize","finish","advanceOnce","idle","claim","mine","release","recordFailure","outcome","newlyStalled","console","advanceConsentGroupChange","claimed","cancelConsentGroupChange","orgSnapshot","jobSnapshot","readConsentGroupChangeStatus","advanceDueConsentGroupChanges","limit","reports","advanced"],"mappings":"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"}
@@ -54,3 +54,25 @@ export interface FindContactByEmailOptions {
54
54
  * the last one that has to.
55
55
  */
56
56
  export declare function findContactByEmail(contactsRef: FirebaseFirestore.CollectionReference, email: unknown, options?: FindContactByEmailOptions): Promise<FirebaseFirestore.DocumentSnapshot | null>;
57
+ /**
58
+ * {@link findContactByEmail} for a whole file of addresses (AGL-3423).
59
+ *
60
+ * The same answer per address — the index first, the contact it names only
61
+ * if that still exists, the `email` query when the index has nothing — paid
62
+ * in a number of round trips that does not grow with the file: one `getAll`
63
+ * of the index entries, one `getAll` of the contacts they name, and one
64
+ * `in` query per thirty addresses the index missed, every page at once. A
65
+ * row found by the query gets its index entry the way a single lookup
66
+ * writes one, in one batch for the whole file.
67
+ *
68
+ * Unscoped, like the capture door's own lookup: one human who touched two
69
+ * sites is one person, and the door decides visibility, not the lookup.
70
+ *
71
+ * The map is keyed by normalized address. An address ABSENT from it is one
72
+ * this call could not answer — its page of the query failed — and a caller
73
+ * asks {@link findContactByEmail} for it rather than taking it as new;
74
+ * `null` is an address that resolves to nobody. When the query returns two
75
+ * documents for one address, the first in document order is the answer, as
76
+ * `limit(1)` gives the single lookup.
77
+ */
78
+ export declare function findContactsByEmail(contactsRef: FirebaseFirestore.CollectionReference, emails: readonly unknown[]): Promise<Map<string, FirebaseFirestore.DocumentSnapshot | null>>;