@aglyn/tenant-data-admin 1.0.0-beta.206 → 1.0.0-beta.207

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.
@@ -0,0 +1,377 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { consentGroupSiteHold } from "@aglyn/aglyn/app-utils/consent-groups";
18
+ import { holdsDedicatedSendingDomain } from "@aglyn/aglyn/app-utils/dedicated-sending-domain";
19
+ import { checkQuota } from "@aglyn/aglyn/app-utils/plan-entitlements";
20
+ import { hostScopeToken } from "@aglyn/aglyn/app-utils/scope-tokens";
21
+ import { resolveHostEnabledPlugins } from "@aglyn/aglyn/plugin-manager/enabled-plugins";
22
+ import { listPluginOrgCollections } from "@aglyn/aglyn/plugin-manager/plugin-host-collections";
23
+ import { normalizeSendingDomain } from "@aglyn/shared-util-email";
24
+ import { FieldValue } from "firebase-admin/firestore";
25
+ import firebaseAdmin from "./firebase-admin.js";
26
+ import { deleteHostProjectionForAllMembers, syncHostProjectionForMembers } from "./host-memberships.js";
27
+ import { syncOrgAuthProjections } from "./organizations.js";
28
+ import { SENDING_LABELS_COLLECTION } from "./host-sending-domain.js";
29
+ import { SENDING_DOMAINS_COLLECTION } from "./sending-domains.js";
30
+ import { TRACKING_HOSTS_COLLECTION } from "./tracking-hosts.js";
31
+ /*
32
+ * MOVING A SITE TO ANOTHER ORGANIZATION (AGL-3381).
33
+ *
34
+ * A site's CONTENT lives under `hosts/{hostId}` and goes wherever the site
35
+ * goes: pages, layouts, versions, forms and their submissions, its members,
36
+ * collections, orders. What ties it to its organization is a handful of
37
+ * pointers and projections, and those are what a transfer rewrites:
38
+ *
39
+ * hosts/{hostId} orgId, and memberRoles / memberPermissions
40
+ * recomputed from the NEW roster (replaced whole,
41
+ * so the old organization's people drop out)
42
+ * hostIndex/{hostId} orgId — how routing resolves the owner
43
+ * orgs/{from} hosts[hostId], and the site's paid seat pools
44
+ * (registerAllocations, collaboratorAllocations)
45
+ * released back to the old organization
46
+ * orgs/{from}/members/* hostAccess[hostId], hostPermissions[hostId]
47
+ * users/{uid}/hostMemberships/{hostId}
48
+ * the old members' rows deleted, the new ones'
49
+ * written
50
+ * orgs/{to} hosts[hostId]
51
+ *
52
+ * The dedicated sending domain moves with it, because the reputation a
53
+ * domain has earned is the site's: its record and its click-tracking host
54
+ * move from `orgs/{from}` to `orgs/{to}`, and the label claim names the new
55
+ * organization. It moves only to an organization whose plan may hold one.
56
+ *
57
+ * ## What stays with the old organization, and why
58
+ *
59
+ * Media `orgs/{from}/media`, stored under that organization's own
60
+ * object prefix. Published pages reference it by address, so
61
+ * the site keeps showing it; moving it would mean copying
62
+ * objects and rewriting every page version that names them.
63
+ * The plan counts it so staff can say so.
64
+ * Records the site filed for its organization — CRM, datasets, lists —
65
+ * are that organization's customer data.
66
+ * Plugin documents a site OWNS on the organization (a declared
67
+ * `siteField`, e.g. sends made as the site) stay as the old
68
+ * organization's history: they name its lists, its
69
+ * containers and the consent it relied on. A plugin that has
70
+ * work still in flight on them (`holdsTransferWhile`) holds
71
+ * the transfer until it lands or is cancelled.
72
+ *
73
+ * ## What refuses a transfer
74
+ *
75
+ * the site or either organization is missing, or it is already there;
76
+ * the site is in a consent group, or a group change naming it is running
77
+ * (its refusals are read by its siblings — it leaves the group first);
78
+ * the destination is at its site limit, unless staff override it;
79
+ * the site holds a dedicated sending domain the destination's plan
80
+ * cannot hold (release it first);
81
+ * a plugin reports work in flight on documents the site owns.
82
+ */ const firestore = ()=>firebaseAdmin.app().firestore();
83
+ const count = async (query)=>(await query.count().get()).data().count;
84
+ const consentHoldMessage = (hold)=>hold.reason === 'grouped' ? `The site is in the consent group "${hold.name}". Remove it from the group first — its opt-outs are read by the other sites in it.` : 'A consent group change naming this site has not finished. Wait for it, then transfer.';
85
+ /** How many sites an organization holds: its directory or its query, whichever is larger. */ async function sitesHeld(db, orgId, org) {
86
+ const directory = org['hosts'];
87
+ const mapped = directory && typeof directory === 'object' ? Object.values(directory).filter(Boolean).length : 0;
88
+ const queried = await count(db.collection('hosts').where('orgId', '==', orgId));
89
+ return Math.max(mapped, queried);
90
+ }
91
+ /**
92
+ * The transfer, worked out and not performed. Read only: safe to call for a
93
+ * confirmation dialog, and called again by {@link transferHost} so what runs
94
+ * is what was decided a moment ago, not what was true when the dialog opened.
95
+ */ export async function planHostTransfer(input) {
96
+ var _input_hostId, _input_toOrgId, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _hostData_sendingDomain, _hostData_sendingLabel;
97
+ const db = firestore();
98
+ const hostId = String((_input_hostId = input.hostId) != null ? _input_hostId : '').trim();
99
+ const toOrgId = String((_input_toOrgId = input.toOrgId) != null ? _input_toOrgId : '').trim();
100
+ const holds = [];
101
+ const warnings = [];
102
+ const host = hostId ? await db.collection('hosts').doc(hostId).get() : null;
103
+ const hostData = (_ref = (host == null ? void 0 : host.exists) ? host.data() : null) != null ? _ref : null;
104
+ const fromOrgId = typeof (hostData == null ? void 0 : hostData['orgId']) === 'string' ? hostData['orgId'] : null;
105
+ const [fromOrg, toOrg] = await Promise.all([
106
+ fromOrgId ? db.collection('orgs').doc(fromOrgId).get() : Promise.resolve(null),
107
+ toOrgId ? db.collection('orgs').doc(toOrgId).get() : Promise.resolve(null)
108
+ ]);
109
+ const fromData = (_ref1 = (fromOrg == null ? void 0 : fromOrg.exists) ? fromOrg.data() : null) != null ? _ref1 : null;
110
+ const toData = (_ref2 = (toOrg == null ? void 0 : toOrg.exists) ? toOrg.data() : null) != null ? _ref2 : null;
111
+ const plan = {
112
+ hostId,
113
+ siteName: (_ref3 = hostData == null ? void 0 : hostData['displayName']) != null ? _ref3 : null,
114
+ fromOrgId,
115
+ fromOrgName: (_ref4 = fromData == null ? void 0 : fromData['name']) != null ? _ref4 : null,
116
+ toOrgId,
117
+ toOrgName: (_ref5 = toData == null ? void 0 : toData['name']) != null ? _ref5 : null,
118
+ holds,
119
+ warnings,
120
+ facts: {
121
+ mediaStaying: 0,
122
+ ownedDocumentsStaying: [],
123
+ collaboratorsLosingAccess: 0,
124
+ sendingDomain: null,
125
+ pluginsLost: [],
126
+ siteLimit: {
127
+ used: 0,
128
+ limit: 0,
129
+ allowed: true
130
+ }
131
+ }
132
+ };
133
+ if (!hostData) {
134
+ holds.push({
135
+ code: 'no-site',
136
+ message: 'There is no such site.'
137
+ });
138
+ return plan;
139
+ }
140
+ if (!toData) {
141
+ holds.push({
142
+ code: 'no-destination',
143
+ message: 'There is no such destination organization.'
144
+ });
145
+ return plan;
146
+ }
147
+ if (fromOrgId === toOrgId) {
148
+ holds.push({
149
+ code: 'same-organization',
150
+ message: 'The site already belongs to that organization.'
151
+ });
152
+ return plan;
153
+ }
154
+ // Consent: the site's refusals are its siblings' too.
155
+ const consentHold = fromData ? consentGroupSiteHold(fromData, hostId) : null;
156
+ if (consentHold) holds.push({
157
+ code: 'consent-group',
158
+ message: consentHoldMessage(consentHold)
159
+ });
160
+ // The destination's site limit, counted the way site creation counts it.
161
+ const used = await sitesHeld(db, toOrgId, toData);
162
+ const quota = checkQuota(toData, 'hostLimit', used);
163
+ plan.facts.siteLimit = {
164
+ used,
165
+ limit: quota.limit,
166
+ allowed: quota.allowed
167
+ };
168
+ if (!quota.allowed) {
169
+ if (input.overrideSiteLimit) {
170
+ warnings.push(`The destination is at its site limit (${used} of ${quota.limit}); the transfer places this site past it.`);
171
+ } else {
172
+ holds.push({
173
+ code: 'site-limit',
174
+ message: `The destination holds ${used} of its ${quota.limit} sites. Raise its limit, or override it.`
175
+ });
176
+ }
177
+ }
178
+ // The dedicated sending domain moves only to a plan that may hold one.
179
+ const sendingDomain = normalizeSendingDomain(String((_hostData_sendingDomain = hostData['sendingDomain']) != null ? _hostData_sendingDomain : '')) || null;
180
+ const sendingLabel = String((_hostData_sendingLabel = hostData['sendingLabel']) != null ? _hostData_sendingLabel : '').trim();
181
+ if (sendingLabel || sendingDomain) {
182
+ plan.facts.sendingDomain = sendingDomain != null ? sendingDomain : sendingLabel;
183
+ if (!holdsDedicatedSendingDomain(toData)) {
184
+ holds.push({
185
+ code: 'sending-domain',
186
+ message: `The site sends as ${plan.facts.sendingDomain}, and the destination's plan cannot hold a ` + 'dedicated sending domain. Release it from the site first, or raise the destination’s plan.'
187
+ });
188
+ } else {
189
+ warnings.push(`The dedicated sending domain ${plan.facts.sendingDomain} moves with the site.`);
190
+ }
191
+ }
192
+ if (fromOrgId) {
193
+ // Plugin documents the site owns on the old organization, and any still
194
+ // in flight — each plugin's own declaration says which field and which
195
+ // states, so no collection is named here.
196
+ for (const declared of listPluginOrgCollections()){
197
+ if (!declared.siteField) continue;
198
+ const owned = db.collection('orgs').doc(fromOrgId).collection(declared.name).where(declared.siteField, '==', hostId);
199
+ const held = await count(owned).catch(()=>0);
200
+ if (held) plan.facts.ownedDocumentsStaying.push({
201
+ collection: declared.name,
202
+ count: held
203
+ });
204
+ const hold = declared.holdsTransferWhile;
205
+ if (held && hold) {
206
+ const moving = await count(owned.where(hold.field, 'in', hold.values)).catch(()=>0);
207
+ if (moving) {
208
+ var _declared_label;
209
+ holds.push({
210
+ code: 'in-flight',
211
+ message: `${moving} ${(_declared_label = declared.label) != null ? _declared_label : declared.name} of this site ${moving === 1 ? 'is' : 'are'} ` + `still ${hold.values.join(' or ')}. Let ${moving === 1 ? 'it' : 'them'} finish or cancel ` + `${moving === 1 ? 'it' : 'them'} first.`
212
+ });
213
+ }
214
+ }
215
+ }
216
+ if (plan.facts.ownedDocumentsStaying.length) {
217
+ warnings.push('Records the site made for its old organization (' + plan.facts.ownedDocumentsStaying.map((row)=>`${row.count} ${row.collection}`).join(', ') + ') stay there as its history.');
218
+ }
219
+ plan.facts.mediaStaying = await count(db.collection('orgs').doc(fromOrgId).collection('media').where('visibleTo', 'array-contains', hostScopeToken(hostId))).catch(()=>0);
220
+ if (plan.facts.mediaStaying) {
221
+ warnings.push(`${plan.facts.mediaStaying} media file(s) stay in the old organization's library. The site keeps ` + 'showing them, but only the old organization can manage them — deleting them there removes them from this site.');
222
+ }
223
+ // Collaborators: old members who reached this site only through a grant.
224
+ const members = await db.collection('orgs').doc(fromOrgId).collection('members').get();
225
+ plan.facts.collaboratorsLosingAccess = members.docs.filter((member)=>{
226
+ var _member_get;
227
+ return member.get('allHosts') !== true && member.get('role') !== 'owner' && Boolean(((_member_get = member.get('hostAccess')) != null ? _member_get : {})[hostId]);
228
+ }).length;
229
+ if (plan.facts.collaboratorsLosingAccess) {
230
+ warnings.push(`${plan.facts.collaboratorsLosingAccess} collaborator(s) of the old organization lose access to the site.`);
231
+ }
232
+ const before = resolveHostEnabledPlugins(fromData, hostData);
233
+ const after = new Set(resolveHostEnabledPlugins(toData, hostData));
234
+ plan.facts.pluginsLost = before.filter((id)=>!after.has(id));
235
+ if (plan.facts.pluginsLost.length) {
236
+ warnings.push(`The destination does not enable ${plan.facts.pluginsLost.join(', ')}; what they draw on the site stops rendering.`);
237
+ }
238
+ }
239
+ if (toData['suspendedAt']) {
240
+ warnings.push('The destination organization is suspended, so the site goes offline with it.');
241
+ }
242
+ return plan;
243
+ }
244
+ /** A transfer refused by its plan: nothing was written. */ export class HostTransferRefusedError extends Error {
245
+ constructor(plan){
246
+ super(plan.holds.map((hold)=>hold.message).join(' '));
247
+ this.name = 'HostTransferRefusedError';
248
+ this.plan = plan;
249
+ }
250
+ }
251
+ /** Move one document from one path to another, deleting the original. */ async function moveDocument(from, to) {
252
+ var _snapshot_data;
253
+ const snapshot = await from.get();
254
+ if (!snapshot.exists) return false;
255
+ await to.set((_snapshot_data = snapshot.data()) != null ? _snapshot_data : {});
256
+ await from.delete();
257
+ return true;
258
+ }
259
+ /**
260
+ * Move a site to another organization (AGL-3381). Re-plans first and refuses
261
+ * — writing nothing — when the plan holds; see the header for what moves,
262
+ * what stays, and why.
263
+ *
264
+ * The ownership flip is one transaction over the site and both organization
265
+ * documents, re-reading the site's organization inside it, so two transfers
266
+ * of one site cannot both land. The site's access list is rebuilt straight
267
+ * after it, before anything else. Everything after the flip is a projection
268
+ * of it, recomputed from the documents rather than carried from the plan, so
269
+ * a failure part-way is finished by running the projections again
270
+ * ({@link syncOrgAuthProjections} and the membership sync).
271
+ *
272
+ * A site that belongs to no organization — left behind by a failed create or
273
+ * a hand edit — is given one the same way, with nothing to release.
274
+ */ export async function transferHost(input) {
275
+ var _current_get, _current_get1;
276
+ const plan = await planHostTransfer(input);
277
+ if (plan.holds.length) throw new HostTransferRefusedError(plan);
278
+ const db = firestore();
279
+ const { hostId, toOrgId, fromOrgId } = plan;
280
+ const hostRef = db.collection('hosts').doc(hostId);
281
+ const fromRef = fromOrgId ? db.collection('orgs').doc(fromOrgId) : null;
282
+ const toRef = db.collection('orgs').doc(toOrgId);
283
+ await db.runTransaction(async (tx)=>{
284
+ var _current_get;
285
+ const current = await tx.get(hostRef);
286
+ if (((_current_get = current.get('orgId')) != null ? _current_get : null) !== fromOrgId) {
287
+ throw new HostTransferRefusedError(_extends({}, plan, {
288
+ holds: [
289
+ {
290
+ code: 'same-organization',
291
+ message: 'The site changed organization while this ran.'
292
+ }
293
+ ]
294
+ }));
295
+ }
296
+ if (fromRef) {
297
+ tx.set(fromRef, {
298
+ hosts: {
299
+ [hostId]: FieldValue.delete()
300
+ },
301
+ registerAllocations: {
302
+ [hostId]: FieldValue.delete()
303
+ },
304
+ collaboratorAllocations: {
305
+ [hostId]: FieldValue.delete()
306
+ },
307
+ updatedAt: FieldValue.serverTimestamp()
308
+ }, {
309
+ merge: true
310
+ });
311
+ }
312
+ tx.set(toRef, {
313
+ hosts: {
314
+ [hostId]: true
315
+ },
316
+ updatedAt: FieldValue.serverTimestamp()
317
+ }, {
318
+ merge: true
319
+ });
320
+ tx.update(hostRef, {
321
+ orgId: toOrgId,
322
+ updatedAt: FieldValue.serverTimestamp()
323
+ });
324
+ });
325
+ // FIRST after the flip: the site's access list, rebuilt whole from the new
326
+ // roster. Until this lands the old organization's people still hold the
327
+ // keys the rules read, so nothing may run ahead of it.
328
+ await syncOrgAuthProjections(toOrgId, hostId);
329
+ // Routing names the new owner.
330
+ await db.collection('hostIndex').doc(hostId).set({
331
+ orgId: toOrgId
332
+ }, {
333
+ merge: true
334
+ });
335
+ if (fromRef && fromOrgId) {
336
+ // The old roster's per-site grants on this site, then its projections.
337
+ const grants = await fromRef.collection('members').get();
338
+ for(let at = 0; at < grants.docs.length; at += 400){
339
+ const batch = db.batch();
340
+ for (const member of grants.docs.slice(at, at + 400)){
341
+ var _member_get, _member_get1;
342
+ const access = (_member_get = member.get('hostAccess')) != null ? _member_get : {};
343
+ const permissions = (_member_get1 = member.get('hostPermissions')) != null ? _member_get1 : {};
344
+ if (!(hostId in access) && !(hostId in permissions)) continue;
345
+ batch.update(member.ref, {
346
+ [`hostAccess.${hostId}`]: FieldValue.delete(),
347
+ [`hostPermissions.${hostId}`]: FieldValue.delete()
348
+ });
349
+ }
350
+ await batch.commit();
351
+ }
352
+ await deleteHostProjectionForAllMembers(fromOrgId, hostId);
353
+ await syncOrgAuthProjections(fromOrgId);
354
+ }
355
+ await syncHostProjectionForMembers(toOrgId, hostId);
356
+ // The dedicated sending domain and its click-tracking host.
357
+ const current = await hostRef.get();
358
+ const domain = normalizeSendingDomain(String((_current_get = current.get('sendingDomain')) != null ? _current_get : '')) || null;
359
+ const label = String((_current_get1 = current.get('sendingLabel')) != null ? _current_get1 : '').trim();
360
+ if (domain && fromRef) {
361
+ for (const collection of [
362
+ SENDING_DOMAINS_COLLECTION,
363
+ TRACKING_HOSTS_COLLECTION
364
+ ]){
365
+ await moveDocument(fromRef.collection(collection).doc(domain), toRef.collection(collection).doc(domain));
366
+ }
367
+ }
368
+ if (label) {
369
+ const claim = db.collection(SENDING_LABELS_COLLECTION).doc(label);
370
+ if ((await claim.get()).exists) await claim.update({
371
+ orgId: toOrgId
372
+ });
373
+ }
374
+ return plan;
375
+ }
376
+
377
+ //# sourceMappingURL=transfer-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/transfer-host.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ConsentGroupSiteHold,\n consentGroupSiteHold,\n} from '@aglyn/aglyn/app-utils/consent-groups'\nimport { holdsDedicatedSendingDomain } from '@aglyn/aglyn/app-utils/dedicated-sending-domain'\nimport type { HostTransferHold, HostTransferPlan } from '@aglyn/aglyn/app-utils/host-transfer'\nimport { checkQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { hostScopeToken } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { resolveHostEnabledPlugins } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { listPluginOrgCollections } from '@aglyn/aglyn/plugin-manager/plugin-host-collections'\nimport { normalizeSendingDomain } from '@aglyn/shared-util-email'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport firebaseAdmin from './firebase-admin'\nimport {\n deleteHostProjectionForAllMembers,\n syncHostProjectionForMembers,\n} from './host-memberships'\nimport { syncOrgAuthProjections } from './organizations'\nimport { SENDING_LABELS_COLLECTION } from './host-sending-domain'\nimport { SENDING_DOMAINS_COLLECTION } from './sending-domains'\nimport { TRACKING_HOSTS_COLLECTION } from './tracking-hosts'\n\n/*\n * MOVING A SITE TO ANOTHER ORGANIZATION (AGL-3381).\n *\n * A site's CONTENT lives under `hosts/{hostId}` and goes wherever the site\n * goes: pages, layouts, versions, forms and their submissions, its members,\n * collections, orders. What ties it to its organization is a handful of\n * pointers and projections, and those are what a transfer rewrites:\n *\n * hosts/{hostId} orgId, and memberRoles / memberPermissions\n * recomputed from the NEW roster (replaced whole,\n * so the old organization's people drop out)\n * hostIndex/{hostId} orgId — how routing resolves the owner\n * orgs/{from} hosts[hostId], and the site's paid seat pools\n * (registerAllocations, collaboratorAllocations)\n * released back to the old organization\n * orgs/{from}/members/* hostAccess[hostId], hostPermissions[hostId]\n * users/{uid}/hostMemberships/{hostId}\n * the old members' rows deleted, the new ones'\n * written\n * orgs/{to} hosts[hostId]\n *\n * The dedicated sending domain moves with it, because the reputation a\n * domain has earned is the site's: its record and its click-tracking host\n * move from `orgs/{from}` to `orgs/{to}`, and the label claim names the new\n * organization. It moves only to an organization whose plan may hold one.\n *\n * ## What stays with the old organization, and why\n *\n * Media `orgs/{from}/media`, stored under that organization's own\n * object prefix. Published pages reference it by address, so\n * the site keeps showing it; moving it would mean copying\n * objects and rewriting every page version that names them.\n * The plan counts it so staff can say so.\n * Records the site filed for its organization — CRM, datasets, lists —\n * are that organization's customer data.\n * Plugin documents a site OWNS on the organization (a declared\n * `siteField`, e.g. sends made as the site) stay as the old\n * organization's history: they name its lists, its\n * containers and the consent it relied on. A plugin that has\n * work still in flight on them (`holdsTransferWhile`) holds\n * the transfer until it lands or is cancelled.\n *\n * ## What refuses a transfer\n *\n * the site or either organization is missing, or it is already there;\n * the site is in a consent group, or a group change naming it is running\n * (its refusals are read by its siblings — it leaves the group first);\n * the destination is at its site limit, unless staff override it;\n * the site holds a dedicated sending domain the destination's plan\n * cannot hold (release it first);\n * a plugin reports work in flight on documents the site owns.\n */\n\nconst firestore = () => firebaseAdmin.app().firestore()\n\nexport type { HostTransferHold, HostTransferPlan } from '@aglyn/aglyn/app-utils/host-transfer'\n\nexport interface PlanHostTransferInput {\n hostId: string\n toOrgId: string\n /** Staff may place a site past the destination's site limit. */\n overrideSiteLimit?: boolean\n}\n\nconst count = async (query: FirebaseFirestore.Query): Promise<number> =>\n (await query.count().get()).data().count\n\nconst consentHoldMessage = (hold: ConsentGroupSiteHold): string =>\n hold.reason === 'grouped'\n ? `The site is in the consent group \"${hold.name}\". Remove it from the group first — its opt-outs are read by the other sites in it.`\n : 'A consent group change naming this site has not finished. Wait for it, then transfer.'\n\n/** How many sites an organization holds: its directory or its query, whichever is larger. */\nasync function sitesHeld(\n db: FirebaseFirestore.Firestore,\n orgId: string,\n org: Record<string, unknown>,\n): Promise<number> {\n const directory = org['hosts']\n const mapped =\n directory && typeof directory === 'object'\n ? Object.values(directory as Record<string, unknown>).filter(Boolean).length\n : 0\n const queried = await count(db.collection('hosts').where('orgId', '==', orgId))\n return Math.max(mapped, queried)\n}\n\n/**\n * The transfer, worked out and not performed. Read only: safe to call for a\n * confirmation dialog, and called again by {@link transferHost} so what runs\n * is what was decided a moment ago, not what was true when the dialog opened.\n */\nexport async function planHostTransfer(input: PlanHostTransferInput): Promise<HostTransferPlan> {\n const db = firestore()\n const hostId = String(input.hostId ?? '').trim()\n const toOrgId = String(input.toOrgId ?? '').trim()\n const holds: HostTransferHold[] = []\n const warnings: string[] = []\n const host = hostId ? await db.collection('hosts').doc(hostId).get() : null\n const hostData = (host?.exists ? host.data() : null) ?? null\n const fromOrgId = typeof hostData?.['orgId'] === 'string' ? (hostData['orgId'] as string) : null\n const [fromOrg, toOrg] = await Promise.all([\n fromOrgId ? db.collection('orgs').doc(fromOrgId).get() : Promise.resolve(null),\n toOrgId ? db.collection('orgs').doc(toOrgId).get() : Promise.resolve(null),\n ])\n const fromData = (fromOrg?.exists ? fromOrg.data() : null) ?? null\n const toData = (toOrg?.exists ? toOrg.data() : null) ?? null\n\n const plan: HostTransferPlan = {\n hostId,\n siteName: (hostData?.['displayName'] as string | undefined) ?? null,\n fromOrgId,\n fromOrgName: (fromData?.['name'] as string | undefined) ?? null,\n toOrgId,\n toOrgName: (toData?.['name'] as string | undefined) ?? null,\n holds,\n warnings,\n facts: {\n mediaStaying: 0,\n ownedDocumentsStaying: [],\n collaboratorsLosingAccess: 0,\n sendingDomain: null,\n pluginsLost: [],\n siteLimit: { used: 0, limit: 0, allowed: true },\n },\n }\n\n if (!hostData) {\n holds.push({ code: 'no-site', message: 'There is no such site.' })\n return plan\n }\n if (!toData) {\n holds.push({ code: 'no-destination', message: 'There is no such destination organization.' })\n return plan\n }\n if (fromOrgId === toOrgId) {\n holds.push({ code: 'same-organization', message: 'The site already belongs to that organization.' })\n return plan\n }\n\n // Consent: the site's refusals are its siblings' too.\n const consentHold = fromData ? consentGroupSiteHold(fromData, hostId) : null\n if (consentHold) holds.push({ code: 'consent-group', message: consentHoldMessage(consentHold) })\n\n // The destination's site limit, counted the way site creation counts it.\n const used = await sitesHeld(db, toOrgId, toData)\n const quota = checkQuota(toData as never, 'hostLimit', used)\n plan.facts.siteLimit = { used, limit: quota.limit, allowed: quota.allowed }\n if (!quota.allowed) {\n if (input.overrideSiteLimit) {\n warnings.push(\n `The destination is at its site limit (${used} of ${quota.limit}); the transfer places this site past it.`,\n )\n } else {\n holds.push({\n code: 'site-limit',\n message: `The destination holds ${used} of its ${quota.limit} sites. Raise its limit, or override it.`,\n })\n }\n }\n\n // The dedicated sending domain moves only to a plan that may hold one.\n const sendingDomain = normalizeSendingDomain(String(hostData['sendingDomain'] ?? '')) || null\n const sendingLabel = String(hostData['sendingLabel'] ?? '').trim()\n if (sendingLabel || sendingDomain) {\n plan.facts.sendingDomain = sendingDomain ?? sendingLabel\n if (!holdsDedicatedSendingDomain(toData as never)) {\n holds.push({\n code: 'sending-domain',\n message:\n `The site sends as ${plan.facts.sendingDomain}, and the destination's plan cannot hold a ` +\n 'dedicated sending domain. Release it from the site first, or raise the destination’s plan.',\n })\n } else {\n warnings.push(`The dedicated sending domain ${plan.facts.sendingDomain} moves with the site.`)\n }\n }\n\n if (fromOrgId) {\n // Plugin documents the site owns on the old organization, and any still\n // in flight — each plugin's own declaration says which field and which\n // states, so no collection is named here.\n for (const declared of listPluginOrgCollections()) {\n if (!declared.siteField) continue\n const owned = db\n .collection('orgs')\n .doc(fromOrgId)\n .collection(declared.name)\n .where(declared.siteField, '==', hostId)\n const held = await count(owned).catch(() => 0)\n if (held) plan.facts.ownedDocumentsStaying.push({ collection: declared.name, count: held })\n const hold = declared.holdsTransferWhile\n if (held && hold) {\n const moving = await count(owned.where(hold.field, 'in', hold.values)).catch(() => 0)\n if (moving) {\n holds.push({\n code: 'in-flight',\n message:\n `${moving} ${declared.label ?? declared.name} of this site ${moving === 1 ? 'is' : 'are'} ` +\n `still ${hold.values.join(' or ')}. Let ${moving === 1 ? 'it' : 'them'} finish or cancel ` +\n `${moving === 1 ? 'it' : 'them'} first.`,\n })\n }\n }\n }\n if (plan.facts.ownedDocumentsStaying.length) {\n warnings.push(\n 'Records the site made for its old organization (' +\n plan.facts.ownedDocumentsStaying.map((row) => `${row.count} ${row.collection}`).join(', ') +\n ') stay there as its history.',\n )\n }\n\n plan.facts.mediaStaying = await count(\n db\n .collection('orgs')\n .doc(fromOrgId)\n .collection('media')\n .where('visibleTo', 'array-contains', hostScopeToken(hostId)),\n ).catch(() => 0)\n if (plan.facts.mediaStaying) {\n warnings.push(\n `${plan.facts.mediaStaying} media file(s) stay in the old organization's library. The site keeps ` +\n 'showing them, but only the old organization can manage them — deleting them there removes them from this site.',\n )\n }\n\n // Collaborators: old members who reached this site only through a grant.\n const members = await db.collection('orgs').doc(fromOrgId).collection('members').get()\n plan.facts.collaboratorsLosingAccess = members.docs.filter(\n (member) =>\n member.get('allHosts') !== true &&\n member.get('role') !== 'owner' &&\n Boolean((member.get('hostAccess') ?? {})[hostId]),\n ).length\n if (plan.facts.collaboratorsLosingAccess) {\n warnings.push(\n `${plan.facts.collaboratorsLosingAccess} collaborator(s) of the old organization lose access to the site.`,\n )\n }\n\n const before = resolveHostEnabledPlugins(fromData as never, hostData as never)\n const after = new Set(resolveHostEnabledPlugins(toData as never, hostData as never))\n plan.facts.pluginsLost = before.filter((id) => !after.has(id))\n if (plan.facts.pluginsLost.length) {\n warnings.push(\n `The destination does not enable ${plan.facts.pluginsLost.join(', ')}; what they draw on the site stops rendering.`,\n )\n }\n }\n\n if (toData['suspendedAt']) {\n warnings.push('The destination organization is suspended, so the site goes offline with it.')\n }\n return plan\n}\n\n/** A transfer refused by its plan: nothing was written. */\nexport class HostTransferRefusedError extends Error {\n readonly plan: HostTransferPlan\n\n constructor(plan: HostTransferPlan) {\n super(plan.holds.map((hold) => hold.message).join(' '))\n this.name = 'HostTransferRefusedError'\n this.plan = plan\n }\n}\n\n/** Move one document from one path to another, deleting the original. */\nasync function moveDocument(\n from: FirebaseFirestore.DocumentReference,\n to: FirebaseFirestore.DocumentReference,\n): Promise<boolean> {\n const snapshot = await from.get()\n if (!snapshot.exists) return false\n await to.set(snapshot.data() ?? {})\n await from.delete()\n return true\n}\n\n/**\n * Move a site to another organization (AGL-3381). Re-plans first and refuses\n * — writing nothing — when the plan holds; see the header for what moves,\n * what stays, and why.\n *\n * The ownership flip is one transaction over the site and both organization\n * documents, re-reading the site's organization inside it, so two transfers\n * of one site cannot both land. The site's access list is rebuilt straight\n * after it, before anything else. Everything after the flip is a projection\n * of it, recomputed from the documents rather than carried from the plan, so\n * a failure part-way is finished by running the projections again\n * ({@link syncOrgAuthProjections} and the membership sync).\n *\n * A site that belongs to no organization — left behind by a failed create or\n * a hand edit — is given one the same way, with nothing to release.\n */\nexport async function transferHost(input: PlanHostTransferInput): Promise<HostTransferPlan> {\n const plan = await planHostTransfer(input)\n if (plan.holds.length) throw new HostTransferRefusedError(plan)\n const db = firestore()\n const { hostId, toOrgId, fromOrgId } = plan\n const hostRef = db.collection('hosts').doc(hostId)\n const fromRef = fromOrgId ? db.collection('orgs').doc(fromOrgId) : null\n const toRef = db.collection('orgs').doc(toOrgId)\n\n await db.runTransaction(async (tx) => {\n const current = await tx.get(hostRef)\n if ((current.get('orgId') ?? null) !== fromOrgId) {\n throw new HostTransferRefusedError({\n ...plan,\n holds: [{ code: 'same-organization', message: 'The site changed organization while this ran.' }],\n })\n }\n if (fromRef) {\n tx.set(\n fromRef,\n {\n hosts: { [hostId]: FieldValue.delete() },\n registerAllocations: { [hostId]: FieldValue.delete() },\n collaboratorAllocations: { [hostId]: FieldValue.delete() },\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n }\n tx.set(\n toRef,\n { hosts: { [hostId]: true }, updatedAt: FieldValue.serverTimestamp() },\n { merge: true },\n )\n tx.update(hostRef, { orgId: toOrgId, updatedAt: FieldValue.serverTimestamp() })\n })\n\n // FIRST after the flip: the site's access list, rebuilt whole from the new\n // roster. Until this lands the old organization's people still hold the\n // keys the rules read, so nothing may run ahead of it.\n await syncOrgAuthProjections(toOrgId, hostId)\n // Routing names the new owner.\n await db.collection('hostIndex').doc(hostId).set({ orgId: toOrgId }, { merge: true })\n\n if (fromRef && fromOrgId) {\n // The old roster's per-site grants on this site, then its projections.\n const grants = await fromRef.collection('members').get()\n for (let at = 0; at < grants.docs.length; at += 400) {\n const batch = db.batch()\n for (const member of grants.docs.slice(at, at + 400)) {\n const access = member.get('hostAccess') ?? {}\n const permissions = member.get('hostPermissions') ?? {}\n if (!(hostId in access) && !(hostId in permissions)) continue\n batch.update(member.ref, {\n [`hostAccess.${hostId}`]: FieldValue.delete(),\n [`hostPermissions.${hostId}`]: FieldValue.delete(),\n })\n }\n await batch.commit()\n }\n await deleteHostProjectionForAllMembers(fromOrgId, hostId)\n await syncOrgAuthProjections(fromOrgId)\n }\n await syncHostProjectionForMembers(toOrgId, hostId)\n\n // The dedicated sending domain and its click-tracking host.\n const current = await hostRef.get()\n const domain = normalizeSendingDomain(String(current.get('sendingDomain') ?? '')) || null\n const label = String(current.get('sendingLabel') ?? '').trim()\n if (domain && fromRef) {\n for (const collection of [SENDING_DOMAINS_COLLECTION, TRACKING_HOSTS_COLLECTION]) {\n await moveDocument(\n fromRef.collection(collection).doc(domain),\n toRef.collection(collection).doc(domain),\n )\n }\n }\n if (label) {\n const claim = db.collection(SENDING_LABELS_COLLECTION).doc(label)\n if ((await claim.get()).exists) await claim.update({ orgId: toOrgId })\n }\n\n return plan\n}\n"],"names":["consentGroupSiteHold","holdsDedicatedSendingDomain","checkQuota","hostScopeToken","resolveHostEnabledPlugins","listPluginOrgCollections","normalizeSendingDomain","FieldValue","firebaseAdmin","deleteHostProjectionForAllMembers","syncHostProjectionForMembers","syncOrgAuthProjections","SENDING_LABELS_COLLECTION","SENDING_DOMAINS_COLLECTION","TRACKING_HOSTS_COLLECTION","firestore","app","count","query","get","data","consentHoldMessage","hold","reason","name","sitesHeld","db","orgId","org","directory","mapped","Object","values","filter","Boolean","length","queried","collection","where","Math","max","planHostTransfer","input","host","fromOrg","toOrg","hostData","hostId","String","trim","toOrgId","holds","warnings","doc","exists","fromOrgId","Promise","all","resolve","fromData","toData","plan","siteName","fromOrgName","toOrgName","facts","mediaStaying","ownedDocumentsStaying","collaboratorsLosingAccess","sendingDomain","pluginsLost","siteLimit","used","limit","allowed","push","code","message","consentHold","quota","overrideSiteLimit","sendingLabel","declared","siteField","owned","held","catch","holdsTransferWhile","moving","field","label","join","map","row","members","docs","member","before","after","Set","id","has","HostTransferRefusedError","Error","moveDocument","from","to","snapshot","set","delete","transferHost","current","hostRef","fromRef","toRef","runTransaction","tx","hosts","registerAllocations","collaboratorAllocations","updatedAt","serverTimestamp","merge","update","grants","at","batch","slice","access","permissions","ref","commit","domain","claim"],"mappings":"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"}
package/src/tenancy.d.ts CHANGED
@@ -73,6 +73,8 @@ export * from './lib/server/hosted-page-review';
73
73
  export * from './lib/server/risk-notice';
74
74
  export * from './lib/server/host-email-tokens';
75
75
  export * from './lib/server/notifications';
76
+ export * from './lib/server/operator-alerts';
77
+ export * from './lib/server/operator-health';
76
78
  export * from './lib/server/org-billing';
77
79
  export * from './lib/server/organizations';
78
80
  export * from './lib/server/duplicate-activity';
package/src/tenancy.js CHANGED
@@ -93,6 +93,10 @@ export * from "./lib/server/hosted-page-review.js";
93
93
  export * from "./lib/server/risk-notice.js";
94
94
  export * from "./lib/server/host-email-tokens.js";
95
95
  export * from "./lib/server/notifications.js";
96
+ // Every operator alert raises through one entry point, and every health check
97
+ // remembers its state (AGL-3377).
98
+ export * from "./lib/server/operator-alerts.js";
99
+ export * from "./lib/server/operator-health.js";
96
100
  export * from "./lib/server/org-billing.js";
97
101
  export * from "./lib/server/organizations.js";
98
102
  export * from "./lib/server/duplicate-activity.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 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\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\nexport * from './lib/server/connect-account-status'\nexport * from './lib/server/connect-payout-failure'\nexport * from './lib/server/stripe-account-mode'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/email-topic-confirmation'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/campaign-conversion-attribution'\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/contact-merge'\nexport * from './lib/server/upsert-contact'\n// The stage floor a won deal applies to its contact (AGL-2641).\nexport * from './lib/server/contact-lifecycle-floor'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\nexport * from './lib/server/crm-next-activity'\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\nexport * from './lib/server/crm-email-activity'\n// What a confirmed booking files on the CRM record — the meeting and the\n// follow-up (AGL-2660).\nexport * from './lib/server/crm-booking-activity'\n// The org's email capture token and the filing of one received message\n// on the record it was with (AGL-2657).\nexport * from './lib/server/crm-inbound-email'\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["domainProvider"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 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\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\nexport * from './lib/server/connect-account-status'\nexport * from './lib/server/connect-payout-failure'\nexport * from './lib/server/stripe-account-mode'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/email-topic-confirmation'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/campaign-conversion-attribution'\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\n// Every operator alert raises through one entry point, and every health check\n// remembers its state (AGL-3377).\nexport * from './lib/server/operator-alerts'\nexport * from './lib/server/operator-health'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/contact-merge'\nexport * from './lib/server/upsert-contact'\n// The stage floor a won deal applies to its contact (AGL-2641).\nexport * from './lib/server/contact-lifecycle-floor'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\nexport * from './lib/server/crm-next-activity'\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\nexport * from './lib/server/crm-email-activity'\n// What a confirmed booking files on the CRM record — the meeting and the\n// follow-up (AGL-2660).\nexport * from './lib/server/crm-booking-activity'\n// The org's email capture token and the filing of one received message\n// on the record it was with (AGL-2657).\nexport * from './lib/server/crm-inbound-email'\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["domainProvider"],"mappings":"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"}