@aglyn/aglyn 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.
- package/package.json +12 -12
- package/src/lib/app-utils/actions.d.ts +26 -433
- package/src/lib/app-utils/actions.js +108 -633
- package/src/lib/app-utils/actions.js.map +1 -1
- package/src/lib/app-utils/activity-presenter.d.ts +3 -3
- package/src/lib/app-utils/activity-presenter.js +36 -52
- package/src/lib/app-utils/activity-presenter.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +1 -1
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/analytics-provider.d.ts +182 -0
- package/src/lib/app-utils/analytics-provider.js +133 -0
- package/src/lib/app-utils/analytics-provider.js.map +1 -0
- package/src/lib/app-utils/api-plugins.d.ts +46 -0
- package/src/lib/app-utils/api-plugins.js +19 -0
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
- package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
- package/src/lib/app-utils/artifact-overrides.js.map +1 -0
- package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
- package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
- package/src/lib/app-utils/artifact-provenance.js.map +1 -0
- package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
- package/src/lib/app-utils/binding-token-catalog.js +17 -41
- package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
- package/src/lib/app-utils/brand-support-line.d.ts +36 -0
- package/src/lib/app-utils/brand-support-line.js +38 -0
- package/src/lib/app-utils/brand-support-line.js.map +1 -0
- package/src/lib/app-utils/collection-entries.d.ts +2 -2
- package/src/lib/app-utils/collection-entries.js.map +1 -1
- package/src/lib/app-utils/consent-group-change.d.ts +4 -0
- package/src/lib/app-utils/consent-group-change.js.map +1 -1
- package/src/lib/app-utils/consent-groups.d.ts +1 -1
- package/src/lib/app-utils/consent-groups.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +15 -42
- package/src/lib/app-utils/console-routes.js +10 -39
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +24 -2
- package/src/lib/app-utils/contacts.js +21 -5
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/container-membership.d.ts +93 -0
- package/src/lib/app-utils/container-membership.js +165 -0
- package/src/lib/app-utils/container-membership.js.map +1 -0
- package/src/lib/app-utils/crm-task-reminders.d.ts +23 -6
- package/src/lib/app-utils/crm-task-reminders.js +32 -11
- package/src/lib/app-utils/crm-task-reminders.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +24 -4
- package/src/lib/app-utils/crm.js +23 -0
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
- package/src/lib/app-utils/cross-pool-desync.js +47 -0
- package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
- package/src/lib/app-utils/csv-upload.d.ts +29 -0
- package/src/lib/app-utils/csv-upload.js +29 -0
- package/src/lib/app-utils/csv-upload.js.map +1 -0
- package/src/lib/app-utils/{dataset-csv.d.ts → csv.d.ts} +10 -43
- package/src/lib/app-utils/{dataset-csv.js → csv.js} +10 -81
- package/src/lib/app-utils/csv.js.map +1 -0
- package/src/lib/app-utils/dataset-models.d.ts +5 -5
- package/src/lib/app-utils/dataset-models.js +19 -2
- package/src/lib/app-utils/dataset-models.js.map +1 -1
- package/src/lib/app-utils/datasets.d.ts +65 -30
- package/src/lib/app-utils/datasets.js +74 -24
- package/src/lib/app-utils/datasets.js.map +1 -1
- package/src/lib/app-utils/docs-deflection.d.ts +6 -6
- package/src/lib/app-utils/docs-deflection.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +14 -8
- package/src/lib/app-utils/docs-help.generated.js +23 -6
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
- package/src/lib/app-utils/docs-index.generated.js +570 -346
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
- package/src/lib/app-utils/docs-retrieval.js +14 -10
- package/src/lib/app-utils/docs-retrieval.js.map +1 -1
- package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
- package/src/lib/app-utils/duplicate-resource.js +7 -4
- package/src/lib/app-utils/duplicate-resource.js.map +1 -1
- package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
- package/src/lib/app-utils/dynamic-list-rule.js +3 -3
- package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
- package/src/lib/app-utils/element-ui.d.ts +1 -1
- package/src/lib/app-utils/element-ui.js +1 -1
- package/src/lib/app-utils/element-ui.js.map +1 -1
- package/src/lib/app-utils/email-starter-policy.js +3 -3
- package/src/lib/app-utils/email-starter-policy.js.map +1 -1
- package/src/lib/app-utils/email-text.d.ts +80 -0
- package/src/lib/app-utils/email-text.js +189 -0
- package/src/lib/app-utils/email-text.js.map +1 -0
- package/src/lib/app-utils/{list-assignment-policy.d.ts → enrollment-basis.d.ts} +4 -0
- package/src/lib/app-utils/{list-assignment-policy.js → enrollment-basis.js} +5 -1
- package/src/lib/app-utils/enrollment-basis.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/error-beacon.d.ts +75 -6
- package/src/lib/app-utils/error-beacon.js +160 -21
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
- package/src/lib/app-utils/expand-repeatables.js +14 -14
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.js +2 -2
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/health-report.d.ts +2 -3
- package/src/lib/app-utils/health-report.js +8 -6
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/host-events.d.ts +82 -0
- package/src/lib/app-utils/host-events.js +62 -0
- package/src/lib/app-utils/host-events.js.map +1 -0
- package/src/lib/app-utils/inbound-mail-domain.d.ts +27 -0
- package/src/lib/app-utils/inbound-mail-domain.js +31 -0
- package/src/lib/app-utils/inbound-mail-domain.js.map +1 -0
- package/src/lib/app-utils/lockdown.d.ts +11 -0
- package/src/lib/app-utils/lockdown.js +33 -3
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/margin-utilization.d.ts +4 -69
- package/src/lib/app-utils/margin-utilization.js +82 -69
- package/src/lib/app-utils/margin-utilization.js.map +1 -1
- package/src/lib/app-utils/member-email-aliases.d.ts +25 -0
- package/src/lib/app-utils/member-email-aliases.js +29 -0
- package/src/lib/app-utils/member-email-aliases.js.map +1 -1
- package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
- package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
- package/src/lib/app-utils/node-interactions.d.ts +7 -7
- package/src/lib/app-utils/node-interactions.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +147 -47
- package/src/lib/app-utils/notifications.js +182 -44
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.d.ts +15 -1
- package/src/lib/app-utils/operator-alerts.js +47 -10
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/payments-configured.d.ts +8 -21
- package/src/lib/app-utils/payments-configured.js +8 -23
- package/src/lib/app-utils/payments-configured.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +207 -306
- package/src/lib/app-utils/plan-entitlements.js +345 -528
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/platform-brand.d.ts +10 -0
- package/src/lib/app-utils/platform-brand.js +14 -3
- package/src/lib/app-utils/platform-brand.js.map +1 -1
- package/src/lib/app-utils/platform-marketing-consent.d.ts +56 -2
- package/src/lib/app-utils/platform-marketing-consent.js +6 -5
- package/src/lib/app-utils/platform-marketing-consent.js.map +1 -1
- package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
- package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
- package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
- package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js +711 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js.map +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +14 -5
- package/src/lib/app-utils/release-flags.js +21 -125
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/repeat-query.d.ts +56 -0
- package/src/lib/app-utils/{dataset-query.js → repeat-query.js} +16 -16
- package/src/lib/app-utils/repeat-query.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +9 -0
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/retained-refusals.d.ts +73 -0
- package/src/lib/app-utils/retained-refusals.js +186 -0
- package/src/lib/app-utils/retained-refusals.js.map +1 -0
- package/src/lib/app-utils/scope-tokens.d.ts +27 -0
- package/src/lib/app-utils/scope-tokens.js +61 -0
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +3 -3
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.js +1 -1
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +132 -0
- package/src/lib/app-utils/seo-audit.js +246 -0
- package/src/lib/app-utils/seo-audit.js.map +1 -0
- package/src/lib/app-utils/seo-keywords.d.ts +48 -0
- package/src/lib/app-utils/seo-keywords.js +60 -0
- package/src/lib/app-utils/seo-keywords.js.map +1 -0
- package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
- package/src/lib/app-utils/seo-page-facts.js +155 -0
- package/src/lib/app-utils/seo-page-facts.js.map +1 -0
- package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
- package/src/lib/app-utils/seo-site-scan.js +162 -0
- package/src/lib/app-utils/seo-site-scan.js.map +1 -0
- package/src/lib/app-utils/seo-title-variables.js +1 -1
- package/src/lib/app-utils/seo-title-variables.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +13 -18
- package/src/lib/app-utils/server.js +33 -51
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-interactions.d.ts +501 -0
- package/src/lib/app-utils/site-interactions.js +554 -0
- package/src/lib/app-utils/site-interactions.js.map +1 -0
- package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
- package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +2 -2
- package/src/lib/app-utils/site-theme.js.map +1 -0
- package/src/lib/app-utils/sitemap.d.ts +4 -3
- package/src/lib/app-utils/sitemap.js +9 -7
- package/src/lib/app-utils/sitemap.js.map +1 -1
- package/src/lib/app-utils/stale-build-error.d.ts +51 -0
- package/src/lib/app-utils/stale-build-error.js +100 -1
- package/src/lib/app-utils/stale-build-error.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +137 -0
- package/src/lib/app-utils/starter-template-nodes.js +129 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -0
- package/src/lib/app-utils/starter-templates.d.ts +11 -34
- package/src/lib/app-utils/starter-templates.js +19 -230
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/{email-topics.d.ts → subscription-topics.d.ts} +64 -48
- package/src/lib/app-utils/{email-topics.js → subscription-topics.js} +39 -109
- package/src/lib/app-utils/subscription-topics.js.map +1 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
- package/src/lib/app-utils/theme-library.d.ts +10 -3
- package/src/lib/app-utils/theme-library.js +13 -4
- package/src/lib/app-utils/theme-library.js.map +1 -1
- package/src/lib/app-utils/usage-budget.d.ts +73 -188
- package/src/lib/app-utils/usage-budget.js +58 -194
- package/src/lib/app-utils/usage-budget.js.map +1 -1
- package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
- package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
- package/src/lib/app-utils/utm-attribution.js.map +1 -0
- package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
- package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
- package/src/lib/app-utils/utm-forwarding.js.map +1 -0
- package/src/lib/app-utils/{campaign-touch.d.ts → utm-touch.d.ts} +25 -17
- package/src/lib/app-utils/{campaign-touch.js → utm-touch.js} +38 -30
- package/src/lib/app-utils/utm-touch.js.map +1 -0
- package/src/lib/app-utils/variables.d.ts +9 -9
- package/src/lib/app-utils/variables.js.map +1 -1
- package/src/lib/app-utils/video-object.js +6 -5
- package/src/lib/app-utils/video-object.js.map +1 -1
- package/src/lib/app-utils/visitor-consent.d.ts +41 -164
- package/src/lib/app-utils/visitor-consent.js +66 -277
- package/src/lib/app-utils/visitor-consent.js.map +1 -1
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +32 -0
- package/src/lib/app-utils/visitor-record-ceiling.js +39 -0
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
- package/src/lib/app-utils/web-vitals-rum.js +31 -63
- package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
- package/src/lib/app-utils/where-used.d.ts +11 -6
- package/src/lib/app-utils/where-used.js +3 -8
- package/src/lib/app-utils/where-used.js.map +1 -1
- package/src/lib/foundation/definitions/components.types.d.ts +1 -1
- package/src/lib/foundation/definitions/components.types.js +1 -1
- package/src/lib/foundation/definitions/components.types.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +11 -137
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +1 -3
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +11 -5
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
- package/src/lib/plugin-manager/besigner-documents.js +61 -0
- package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
- package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
- package/src/lib/plugin-manager/computed-variables.js +74 -0
- package/src/lib/plugin-manager/computed-variables.js.map +1 -0
- package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
- package/src/lib/plugin-manager/custom-fields.js +47 -1
- package/src/lib/plugin-manager/custom-fields.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +372 -99
- package/src/lib/plugin-manager/feature-plugins.js +126 -51
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +134 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +840 -10
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/index.d.ts +0 -1
- package/src/lib/plugin-manager/index.js +0 -1
- package/src/lib/plugin-manager/index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +9 -4
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-containers.d.ts +68 -0
- package/src/lib/plugin-manager/plugin-containers.generated.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js +20 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-containers.js +27 -0
- package/src/lib/plugin-manager/plugin-containers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
- package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-dependents.js +75 -0
- package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
- package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-distribution.js +45 -0
- package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
- package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-membership-detach.js +1 -1
- package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -1
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +32 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
- package/src/lib/plugin-manager/plugin-record-index.js +51 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +115 -0
- package/src/lib/plugin-manager/plugin-record-lists.js +75 -0
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +42 -8
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
- package/src/lib/plugin-manager/plugin-record-written.js +42 -0
- package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-beacons.d.ts +71 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js +54 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-site-export.js +40 -0
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.d.ts +11 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js +41 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
- package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +319 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js +186 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-meters.d.ts +108 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js +105 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js.map +1 -0
- package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
- package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
- package/src/lib/plugin-manager/record-captured-contact.js +10 -32
- package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
- package/src/lib/plugin-manager/repeat-rows.js +108 -0
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
- package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
- package/src/lib/plugin-manager/submission-record-target.js +125 -0
- package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
- package/src/lib/plugin-manager/usage-alert-contributors.d.ts +76 -6
- package/src/lib/plugin-manager/usage-alert-contributors.js +23 -10
- package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -1
- package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
- package/src/lib/plugin-manager/video-embed-provider.js +165 -0
- package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
- package/src/lib/types/nodes.d.ts +2 -2
- package/src/lib/types/nodes.js.map +1 -1
- package/src/lib/app-utils/assist-credits.d.ts +0 -482
- package/src/lib/app-utils/assist-credits.js +0 -509
- package/src/lib/app-utils/assist-credits.js.map +0 -1
- package/src/lib/app-utils/campaign-attribution.js.map +0 -1
- package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
- package/src/lib/app-utils/campaign-membership.d.ts +0 -121
- package/src/lib/app-utils/campaign-membership.js +0 -189
- package/src/lib/app-utils/campaign-membership.js.map +0 -1
- package/src/lib/app-utils/campaign-touch.js.map +0 -1
- package/src/lib/app-utils/console-record-links.d.ts +0 -163
- package/src/lib/app-utils/console-record-links.js +0 -180
- package/src/lib/app-utils/console-record-links.js.map +0 -1
- package/src/lib/app-utils/contact-holder.d.ts +0 -68
- package/src/lib/app-utils/contact-holder.js +0 -129
- package/src/lib/app-utils/contact-holder.js.map +0 -1
- package/src/lib/app-utils/contact-merge.d.ts +0 -136
- package/src/lib/app-utils/contact-merge.js +0 -412
- package/src/lib/app-utils/contact-merge.js.map +0 -1
- package/src/lib/app-utils/crm-booking.d.ts +0 -75
- package/src/lib/app-utils/crm-booking.js +0 -133
- package/src/lib/app-utils/crm-booking.js.map +0 -1
- package/src/lib/app-utils/crm-csv.d.ts +0 -239
- package/src/lib/app-utils/crm-csv.js +0 -467
- package/src/lib/app-utils/crm-csv.js.map +0 -1
- package/src/lib/app-utils/crm-digest.d.ts +0 -185
- package/src/lib/app-utils/crm-digest.js +0 -272
- package/src/lib/app-utils/crm-digest.js.map +0 -1
- package/src/lib/app-utils/crm-inbound.d.ts +0 -245
- package/src/lib/app-utils/crm-inbound.js +0 -407
- package/src/lib/app-utils/crm-inbound.js.map +0 -1
- package/src/lib/app-utils/crm-reports.d.ts +0 -479
- package/src/lib/app-utils/crm-reports.js +0 -680
- package/src/lib/app-utils/crm-reports.js.map +0 -1
- package/src/lib/app-utils/dataset-csv.js.map +0 -1
- package/src/lib/app-utils/dataset-query.d.ts +0 -57
- package/src/lib/app-utils/dataset-query.js.map +0 -1
- package/src/lib/app-utils/email-topics.js.map +0 -1
- package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
- package/src/lib/app-utils/form-dataset-binding.js +0 -74
- package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
- package/src/lib/app-utils/list-assignment-policy.js.map +0 -1
- package/src/lib/app-utils/list-import.d.ts +0 -158
- package/src/lib/app-utils/list-import.js +0 -332
- package/src/lib/app-utils/list-import.js.map +0 -1
- package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
- package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
- package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
- package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
- package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
- package/src/lib/app-utils/marketplace-theme.js.map +0 -1
- package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
- package/src/lib/app-utils/marketplace-update-state.js +0 -132
- package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
- package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
- package/src/lib/app-utils/publisher-agreement.js +0 -321
- package/src/lib/app-utils/publisher-agreement.js.map +0 -1
- package/src/lib/app-utils/wistia-embed.d.ts +0 -104
- package/src/lib/app-utils/wistia-embed.js +0 -138
- package/src/lib/app-utils/wistia-embed.js.map +0 -1
- package/src/lib/app-utils/workflows.d.ts +0 -136
- package/src/lib/app-utils/workflows.js +0 -346
- package/src/lib/app-utils/workflows.js.map +0 -1
- package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
- package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
- package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
- package/src/lib/plugin-manager/plugin-metered-lines.d.ts +0 -97
- package/src/lib/plugin-manager/plugin-metered-lines.js +0 -123
- package/src/lib/plugin-manager/plugin-metered-lines.js.map +0 -1
|
@@ -370,6 +370,8 @@ export interface ConsentGroupChangeCounts {
|
|
|
370
370
|
siteSuppressions: number;
|
|
371
371
|
topicOptOuts: number;
|
|
372
372
|
paces: number;
|
|
373
|
+
/** Refusals kept after their record was deleted, in `retainedRefusals` (AGL-3338). */
|
|
374
|
+
retainedRefusals: number;
|
|
373
375
|
}
|
|
374
376
|
/** A job's state, as the route answers it and the progress panel renders it. */
|
|
375
377
|
export interface ConsentGroupChangeProgress {
|
|
@@ -479,6 +481,8 @@ export interface ConsentGroupChangePreview {
|
|
|
479
481
|
siteSuppressions: number;
|
|
480
482
|
topicOptOuts: number;
|
|
481
483
|
paces: number;
|
|
484
|
+
/** The organization's retained refusals for the source site (AGL-3338). */
|
|
485
|
+
retainedRefusals: number;
|
|
482
486
|
}>;
|
|
483
487
|
/** Per joining site, the refusals on its new siblings it starts honoring. */
|
|
484
488
|
inherited: Array<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/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 * CHANGING WHICH SITES ARE ONE SENDER (AGL-3320) — the pure half.\n *\n * `consent-groups.ts` reads a declaration; this is what a change to one\n * means. It validates the next declaration an admin submits, decides what has\n * to move for the sites to stop or start being one sender, and words the\n * result — and it does all of that without a read, so the console's review\n * step, the route's refusal and the executor's plan are one computation.\n *\n * ## Why a change is a plan and not a write\n *\n * A declaration is read at send time, so flipping it is instant — and that is\n * exactly the problem. Two facts are stored per SITE and read across the\n * CURRENT group: a refusal (a suppression row, a topic opt-out, a pace, a\n * declined consent) and a holder's business record (the CRM facet keyed by\n * the group). When sites stop being one sender, each stops reading the\n * other's refusals; when they become one, the records they kept apart must\n * become one. The flip itself moves neither, so every change is:\n *\n * 1. CARRY the refusals onto the sites that are about to stop reading them,\n * in BOTH directions — the person who left one site of a sender was told\n * the opt-out covers all of them, so the site leaving keeps what it\n * honored and the sites staying keep what the leaver honored;\n * 2. FLIP the declaration;\n * 3. RE-HOME the holder records keyed by the old group ids.\n *\n * {@link planConsentGroupChange} says which pairs carry and which holder keys\n * move or split; the executor in `libs/tenant/data/admin` performs it.\n *\n * ## What the planner never touches\n *\n * Grants. A grant is written forward, one entry per disclosed site, and is\n * evidence of what a person was told, so a change never copies, re-keys or\n * revokes one — see `consent-groups.ts`, \"Pooling applies FORWARD only\".\n */\n\nimport {\n consentGroupDisclosure,\n CONSENT_GROUPS_FIELD,\n MAX_CONSENT_GROUP_HOSTS,\n readConsentGroups,\n type StoredConsentGroup,\n} from './consent-groups'\n\n/**\n * The org field that marks a change in flight:\n * {@link ConsentGroupsChangeMarker}. Authoritative — the job document holds\n * the detail, and this says whether there is a job at all.\n *\n * On the org document, beside the declaration it guards, so the transaction\n * that starts a change and the one that flips the declaration read one\n * document. Server-owned: denied to every client by the rules.\n */\nexport const CONSENT_GROUPS_CHANGE_FIELD = 'consentGroupsChange'\n\n/** `orgs/{orgId}/consentGroupChanges/{changeId}` — one job per change. */\nexport const CONSENT_GROUP_CHANGES_COLLECTION = 'consentGroupChanges'\n\n/**\n * The longest group name a declaration may carry.\n *\n * The name is rendered on every signup form of the group, inside a sentence,\n * so it is a brand and not a description.\n */\nexport const CONSENT_GROUP_NAME_MAX = 80\n\n/**\n * How long after the flip the sweep waits before its final pass.\n *\n * A long job — a campaign batch, a dynamic list, an outreach tick — resolves\n * the group once per batch and runs for at most five minutes, so a write it\n * made against the OLD declaration can land after the flip. Six minutes is\n * past the longest of them.\n */\nexport const CONSENT_GROUP_SWEEP_DELAY_MS = 6 * 60_000\n\n/**\n * What every minted group id starts with.\n *\n * A group id is a holder key beside the site ids a solo site is keyed by, so\n * the two must never meet. Firestore's automatic ids — which every site id is\n * — are alphanumeric, and an underscore cannot occur in one.\n */\nexport const CONSENT_GROUP_ID_PREFIX = 'cg_'\n\n/** Every group of a declaration, by id — what `readConsentGroups` answers. */\nexport type ConsentGroupDeclaration = Record<string, StoredConsentGroup>\n\n/** One group of the next declaration as the editor submits it. */\nexport interface ConsentGroupDraft {\n /** The id of a group that exists; absent for a new group. */\n id?: string | null\n name: string\n hostIds: string[]\n}\n\n/**\n * Why a submitted declaration was refused. Every code names something the\n * admin can fix in the editor; `groupIndex` points at the draft it is about,\n * and `hostId` at the site.\n */\nexport type ConsentGroupValidationCode =\n /** Not a list of `{ name, hostIds }` objects. */\n | 'malformed'\n | 'name-empty'\n | 'name-too-long'\n /** Two groups with the same name, ignoring case. */\n | 'name-duplicate'\n | 'too-few-sites'\n | 'too-many-sites'\n /** A site this organization does not own. */\n | 'unknown-site'\n | 'site-in-two-groups'\n /** An id no group of the current declaration has. */\n | 'unknown-group'\n /** One id on two drafts. */\n | 'duplicate-group'\n /**\n * A kept id whose sites are ALL new. Keeping the id would carry the old\n * group's records onto a set of sites none of which kept them; a new group\n * is what that is.\n */\n | 'group-replaced'\n /** The declaration submitted is the one in force. */\n | 'no-change'\n\nexport interface ConsentGroupValidationError {\n code: ConsentGroupValidationCode\n groupIndex?: number\n hostId?: string\n}\n\nexport type ConsentGroupValidation =\n | { ok: true; after: ConsentGroupDeclaration }\n | { ok: false; errors: ConsentGroupValidationError[] }\n\n/**\n * The sites an organization owns, from its `hosts` map, sorted.\n *\n * The map is `{ [hostId]: true }`; an array is read too, so a hand-built\n * fixture and the stored shape agree.\n */\nexport function orgSiteIds(\n org: Record<string, unknown> | null | undefined,\n): string[] {\n const hosts = (org ?? {})['hosts']\n const ids = Array.isArray(hosts)\n ? hosts.map((id) => String(id ?? ''))\n : hosts && typeof hosts === 'object'\n ? Object.entries(hosts as Record<string, unknown>)\n .filter(([, value]) => value !== false && value != null)\n .map(([id]) => id)\n : []\n return [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort()\n}\n\n/** The raw declaration's ids that `readConsentGroups` drops, sorted. */\nexport function discardedConsentGroupIds(\n org: Record<string, unknown> | null | undefined,\n): string[] {\n const raw = (org ?? {})[CONSENT_GROUPS_FIELD]\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return []\n const usable = readConsentGroups(org)\n return Object.keys(raw as Record<string, unknown>)\n .filter((id) => !(id in usable))\n .sort()\n}\n\nconst BASE36 = '0123456789abcdefghijklmnopqrstuvwxyz'\n\n/** Uniform in [0, 1), from the platform's CSPRNG where there is one. */\nfunction defaultRandom(): number {\n const crypto = (globalThis as { crypto?: { getRandomValues?: <T>(array: T) => T } })\n .crypto\n if (crypto?.getRandomValues) {\n const word = crypto.getRandomValues(new Uint32Array(1))[0]\n return word / 2 ** 32\n }\n return Math.random()\n}\n\n/**\n * A fresh group id: {@link CONSENT_GROUP_ID_PREFIX} and sixteen base-36\n * characters, never one in `taken`.\n *\n * `taken` holds every id the id must not equal — the current groups', the\n * raw declaration's, every site's and every id already minted for the same\n * declaration. Eighty bits make a collision a formality, and the loop makes it\n * impossible.\n */\nexport function mintConsentGroupId(\n taken: ReadonlySet<string>,\n random: () => number = defaultRandom,\n): string {\n for (;;) {\n let id = CONSENT_GROUP_ID_PREFIX\n for (let index = 0; index < 16; index += 1) {\n id += BASE36[Math.min(35, Math.floor(random() * 36))]\n }\n if (!taken.has(id)) return id\n }\n}\n\n/** A group's canonical form: its name trimmed, its sites unique and sorted. */\nfunction canonicalGroup(group: unknown): { name: string; hostIds: string[] } {\n const value = (group && typeof group === 'object' ? group : {}) as Record<string, unknown>\n const name = typeof value['name'] === 'string' ? value['name'].trim() : ''\n const hostIds = Array.isArray(value['hostIds'])\n ? [\n ...new Set(\n (value['hostIds'] as unknown[])\n .map((id) => String(id ?? '').trim())\n .filter(Boolean),\n ),\n ].sort()\n : []\n return { name, hostIds }\n}\n\n/**\n * Whether two declarations say the same thing: the same ids, each with the\n * same trimmed name and the same set of sites. Order never matters.\n */\nexport function consentGroupsEqual(\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined,\n): boolean {\n return consentGroupsFingerprint(a) === consentGroupsFingerprint(b)\n}\n\n/**\n * A declaration as one canonical string: what \"the value the editor\n * rendered\" and \"the value stored now\" are compared by.\n *\n * Every entry is kept, usable or not, because the question is whether the\n * stored value CHANGED, and an edit to an entry `readConsentGroups` drops is\n * still somebody else's edit. Absent, `null` and `{}` are one value — an\n * organization with no groups.\n */\nexport function consentGroupsFingerprint(raw: unknown): string {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return 'null'\n const entries = Object.entries(raw as Record<string, unknown>)\n .map(([id, group]) => [id, canonicalGroup(group)] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return entries.length ? JSON.stringify(entries) : 'null'\n}\n\n/**\n * The next declaration, checked against the organization's sites and the\n * declaration in force, with an id minted for every new group.\n *\n * Refuses everything in the list on {@link ConsentGroupValidationCode}, and\n * reports every problem it finds rather than the first, so the editor can\n * mark each draft at once.\n */\nexport function validateConsentGroupDeclaration(input: {\n groups: unknown\n /** The declaration in force: `readConsentGroups(org)`. */\n before: ConsentGroupDeclaration\n /** The organization's sites: {@link orgSiteIds}. */\n siteIds: readonly string[]\n /** Ids a minted id must also avoid — the raw declaration's, for one. */\n reserved?: Iterable<string>\n random?: () => number\n}): ConsentGroupValidation {\n const { before } = input\n if (!Array.isArray(input.groups)) {\n return { ok: false, errors: [{ code: 'malformed' }] }\n }\n const sites = new Set(input.siteIds)\n const errors: ConsentGroupValidationError[] = []\n const names = new Set<string>()\n const ids = new Set<string>()\n const claimed = new Set<string>()\n const drafts: Array<{ id: string | null; name: string; hostIds: string[] }> = []\n\n input.groups.forEach((value, groupIndex) => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n errors.push({ code: 'malformed', groupIndex })\n return\n }\n const entry = value as Record<string, unknown>\n if (typeof entry['name'] !== 'string' || !Array.isArray(entry['hostIds'])) {\n errors.push({ code: 'malformed', groupIndex })\n return\n }\n const { name, hostIds } = canonicalGroup(entry)\n if (!name) errors.push({ code: 'name-empty', groupIndex })\n else if (name.length > CONSENT_GROUP_NAME_MAX) {\n errors.push({ code: 'name-too-long', groupIndex })\n } else if (names.has(name.toLowerCase())) {\n errors.push({ code: 'name-duplicate', groupIndex })\n }\n if (name) names.add(name.toLowerCase())\n\n for (const hostId of hostIds) {\n if (!sites.has(hostId)) {\n errors.push({ code: 'unknown-site', groupIndex, hostId })\n } else if (claimed.has(hostId)) {\n errors.push({ code: 'site-in-two-groups', groupIndex, hostId })\n }\n claimed.add(hostId)\n }\n if (hostIds.length < 2) errors.push({ code: 'too-few-sites', groupIndex })\n else if (hostIds.length > MAX_CONSENT_GROUP_HOSTS) {\n errors.push({ code: 'too-many-sites', groupIndex })\n }\n\n const rawId = entry['id']\n const id = typeof rawId === 'string' && rawId.trim() ? rawId.trim() : null\n if (id !== null) {\n if (ids.has(id)) errors.push({ code: 'duplicate-group', groupIndex })\n else if (!(id in before)) errors.push({ code: 'unknown-group', groupIndex })\n else if (!before[id].hostIds.some((hostId) => hostIds.includes(hostId))) {\n errors.push({ code: 'group-replaced', groupIndex })\n }\n ids.add(id)\n }\n drafts.push({ id, name, hostIds })\n })\n if (errors.length) return { ok: false, errors }\n\n const taken = new Set<string>([\n ...Object.keys(before),\n ...input.siteIds,\n ...(input.reserved ?? []),\n ...ids,\n ])\n const after: ConsentGroupDeclaration = {}\n for (const draft of drafts) {\n const id = draft.id ?? mintConsentGroupId(taken, input.random)\n taken.add(id)\n after[id] = { name: draft.name, hostIds: draft.hostIds }\n }\n if (consentGroupsEqual(before, after)) {\n return { ok: false, errors: [{ code: 'no-change' }] }\n }\n return { ok: true, after }\n}\n\n/*==========================================\n * THE PLAN\n *=========================================*/\n\n/**\n * One direction of a carry: `toHostId` is about to stop reading\n * `fromHostId`'s refusals, so they are copied onto it first.\n */\nexport interface ConsentGroupCarry {\n toHostId: string\n fromHostId: string\n}\n\n/**\n * Every site a holder key covered moves under ONE new key: the key's records\n * are folded into the new key's, and the old key is deleted in the same\n * write.\n */\nexport interface ConsentGroupHolderMove {\n kind: 'move'\n /** The key the records are under now: a group id, or a solo site's id. */\n from: string\n /** The sites `from` covered. */\n fromHostIds: string[]\n /** The key they move under: always a declared group of the next declaration. */\n to: string\n /** The sites of `to` in the next declaration. */\n toHostIds: string[]\n}\n\n/**\n * The sites a holder key covered go separate ways. Each successor may take a\n * copy of the key's records; the key itself survives when some of its sites\n * stay under it, and is deleted when none do.\n */\nexport interface ConsentGroupHolderSplit {\n kind: 'split'\n from: string\n fromHostIds: string[]\n /** Whether `from` is still a key afterwards. */\n survives: boolean\n /** The sites of `from` that stay under it. Empty when it does not survive. */\n stayHostIds: string[]\n /**\n * Where the other sites go: each successor key with the sites of `from`\n * that map to it — never the successor's other sites, whose records were\n * never under `from`.\n */\n successors: Array<{ key: string; hostIds: string[] }>\n}\n\nexport type ConsentGroupHolderFlow = ConsentGroupHolderMove | ConsentGroupHolderSplit\n\n/** What a change does to the declaration, as the activity log words it. */\nexport type ConsentGroupChangeLine =\n | { kind: 'created'; groupId: string; name: string; hostIds: string[] }\n | { kind: 'renamed'; groupId: string; from: string; to: string }\n | { kind: 'added'; groupId: string; name: string; hostId: string }\n | { kind: 'removed'; groupId: string; name: string; hostId: string }\n | {\n kind: 'moved'\n hostId: string\n fromGroupId: string\n fromName: string\n toGroupId: string\n toName: string\n }\n /** Includes a group absorbed into another: its sites each get a `moved`. */\n | { kind: 'dissolved'; groupId: string; name: string }\n\nexport interface ConsentGroupChangePlan {\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n carries: ConsentGroupCarry[]\n flows: ConsentGroupHolderFlow[]\n lines: ConsentGroupChangeLine[]\n /**\n * Every site the change touches — whose group, members or group name\n * changes. What the marker names while the change runs: the sites that\n * may not be deleted, and whose holder records may be in motion.\n */\n hostIds: string[]\n /** Only names change: nothing to carry and nothing to re-home. */\n renameOnly: boolean\n}\n\nfunction groupOf(\n declaration: ConsentGroupDeclaration,\n): Map<string, { id: string; hostIds: string[] }> {\n const map = new Map<string, { id: string; hostIds: string[] }>()\n for (const [id, group] of Object.entries(declaration)) {\n for (const hostId of group.hostIds) map.set(hostId, { id, hostIds: group.hostIds })\n }\n return map\n}\n\nconst byString = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)\n\n/** `declaration` without the sites `keep` does not name. */\nfunction withSitesOnly(\n declaration: ConsentGroupDeclaration,\n keep: ReadonlySet<string>,\n): ConsentGroupDeclaration {\n const out: ConsentGroupDeclaration = {}\n for (const [id, group] of Object.entries(declaration)) {\n const hostIds = group.hostIds.filter((hostId) => keep.has(hostId))\n if (hostIds.length) out[id] = { name: group.name, hostIds }\n }\n return out\n}\n\n/**\n * What moving from `before` to `after` requires.\n *\n * `key(s)` is the holder key of site `s` — its group's id, or `s` itself when\n * it is alone — and `members(s)` the sites of that group, or `[s]`.\n *\n * - **Carries.** For every pair a ≠ b that are one sender now and will not\n * be, `b`'s refusals are copied onto `a`. Pairs that stay together, and\n * pairs that come together, need nothing: a joined site reads its new\n * siblings' refusals the moment the declaration names it.\n * - **Holder flows.** For each key `k` in force, the sites it covers map to\n * a set `T` of keys afterwards. `T = {k}` needs nothing; one other key is\n * a MOVE; anything else is a SPLIT, which `k` survives when it is in `T`.\n *\n * `siteIds`, when given, are the sites the organization still owns: a site\n * named by `before` that is gone neither receives a carry nor takes a copy of\n * a record, because there is nothing left to hold either.\n */\nexport function planConsentGroupChange(\n before: ConsentGroupDeclaration,\n after: ConsentGroupDeclaration,\n options: { siteIds?: readonly string[] } = {},\n): ConsentGroupChangePlan {\n const owned = options.siteIds ? new Set(options.siteIds) : null\n const moving = owned ? withSitesOnly(before, owned) : before\n const beforeOf = groupOf(moving)\n const afterOf = groupOf(after)\n const keyBefore = (hostId: string) => beforeOf.get(hostId)?.id ?? hostId\n const keyAfter = (hostId: string) => afterOf.get(hostId)?.id ?? hostId\n const membersBefore = (hostId: string) => beforeOf.get(hostId)?.hostIds ?? [hostId]\n const membersAfter = (hostId: string) => afterOf.get(hostId)?.hostIds ?? [hostId]\n\n const sites = [\n ...new Set([\n ...Object.values(moving).flatMap((group) => group.hostIds),\n ...Object.values(after).flatMap((group) => group.hostIds),\n ]),\n ].sort(byString)\n\n const carries: ConsentGroupCarry[] = []\n for (const a of sites) {\n const stays = new Set(membersAfter(a))\n for (const b of membersBefore(a)) {\n if (b !== a && !stays.has(b)) carries.push({ toHostId: a, fromHostId: b })\n }\n }\n\n const flows: ConsentGroupHolderFlow[] = []\n const keys = [...new Set(sites.map(keyBefore))].sort(byString)\n for (const key of keys) {\n const covered = sites.filter((hostId) => keyBefore(hostId) === key)\n const targets = [...new Set(covered.map(keyAfter))].sort(byString)\n if (targets.length === 1 && targets[0] === key) continue\n if (targets.length === 1) {\n flows.push({\n kind: 'move',\n from: key,\n fromHostIds: covered,\n to: targets[0],\n toHostIds: [...membersAfter(covered[0])],\n })\n continue\n }\n flows.push({\n kind: 'split',\n from: key,\n fromHostIds: covered,\n survives: targets.includes(key),\n stayHostIds: covered.filter((hostId) => keyAfter(hostId) === key),\n successors: targets\n .filter((target) => target !== key)\n .map((target) => ({\n key: target,\n hostIds: covered.filter((hostId) => keyAfter(hostId) === target),\n })),\n })\n }\n\n const lines = planLines(before, after)\n const touched = new Set<string>()\n const beforeAll = groupOf(before)\n for (const hostId of new Set([...beforeAll.keys(), ...afterOf.keys()])) {\n const was = beforeAll.get(hostId)\n const now = afterOf.get(hostId)\n const sameMembers =\n (was?.hostIds ?? [hostId]).join('\\n') === (now?.hostIds ?? [hostId]).join('\\n')\n const sameName =\n !was || !now || before[was.id]?.name === after[now.id]?.name\n if (was?.id !== now?.id || !sameMembers || !sameName) touched.add(hostId)\n }\n return {\n before,\n after,\n carries,\n flows,\n lines,\n hostIds: [...touched].sort(byString),\n renameOnly:\n !carries.length &&\n !flows.length &&\n lines.length > 0 &&\n lines.every((line) => line.kind === 'renamed'),\n }\n}\n\n/** The declaration-level changes, in the order the activity log lists them. */\nfunction planLines(\n before: ConsentGroupDeclaration,\n after: ConsentGroupDeclaration,\n): ConsentGroupChangeLine[] {\n const beforeOf = groupOf(before)\n const afterOf = groupOf(after)\n const created: ConsentGroupChangeLine[] = []\n const renamed: ConsentGroupChangeLine[] = []\n const added: ConsentGroupChangeLine[] = []\n const removed: ConsentGroupChangeLine[] = []\n const moved: ConsentGroupChangeLine[] = []\n const dissolved: ConsentGroupChangeLine[] = []\n const moveInto = (hostId: string, toGroupId: string) => {\n const from = beforeOf.get(hostId)\n if (!from || from.id === toGroupId) return false\n moved.push({\n kind: 'moved',\n hostId,\n fromGroupId: from.id,\n fromName: before[from.id].name,\n toGroupId,\n toName: after[toGroupId].name,\n })\n return true\n }\n\n for (const id of Object.keys(after).sort(byString)) {\n const next = after[id]\n const previous = before[id]\n if (!previous) {\n created.push({ kind: 'created', groupId: id, name: next.name, hostIds: [...next.hostIds] })\n for (const hostId of next.hostIds) moveInto(hostId, id)\n continue\n }\n if (previous.name !== next.name) {\n renamed.push({ kind: 'renamed', groupId: id, from: previous.name, to: next.name })\n }\n for (const hostId of next.hostIds) {\n if (previous.hostIds.includes(hostId)) continue\n if (!moveInto(hostId, id)) {\n added.push({ kind: 'added', groupId: id, name: next.name, hostId })\n }\n }\n for (const hostId of previous.hostIds) {\n if (next.hostIds.includes(hostId)) continue\n // A site that went to another group is that group's `moved` line.\n if (!afterOf.has(hostId)) {\n removed.push({ kind: 'removed', groupId: id, name: next.name, hostId })\n }\n }\n }\n for (const id of Object.keys(before).sort(byString)) {\n if (!(id in after)) dissolved.push({ kind: 'dissolved', groupId: id, name: before[id].name })\n }\n return [...created, ...renamed, ...added, ...removed, ...moved, ...dissolved]\n}\n\n/**\n * The sites in a sentence: \"A\", \"A and B\", \"A, B and C\".\n */\nexport function listSiteNames(names: readonly string[]): string {\n if (names.length <= 1) return names[0] ?? ''\n return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n}\n\n/**\n * One line of the plan as the organization's activity log says it.\n *\n * `siteName` turns a site id into what the console shows for it; the id\n * itself is the fallback, so a line is never blank.\n */\nexport function describeConsentGroupChangeLine(\n line: ConsentGroupChangeLine,\n siteName: (hostId: string) => string = (hostId) => hostId,\n): string {\n const site = (hostId: string) => siteName(hostId) || hostId\n switch (line.kind) {\n case 'created':\n return `Created consent group \"${line.name}\" with ${listSiteNames(line.hostIds.map(site))}`\n case 'renamed':\n return `Renamed consent group \"${line.from}\" to \"${line.to}\"`\n case 'added':\n return `Added ${site(line.hostId)} to consent group \"${line.name}\"`\n case 'removed':\n return `Removed ${site(line.hostId)} from consent group \"${line.name}\"`\n case 'moved':\n return `Moved ${site(line.hostId)} from consent group \"${line.fromName}\" to \"${line.toName}\"`\n case 'dissolved':\n return `Dissolved consent group \"${line.name}\"`\n }\n}\n\n/*==========================================\n * THE CHANGE IN FLIGHT\n *=========================================*/\n\n/**\n * Where a change stands, on the org marker.\n *\n * - `carry` — copying refusals; the declaration in force is the old one,\n * and the change can still be canceled.\n * - `rehome` — the new declaration is in force; holder records are moving.\n * - `sweep` — waiting out {@link CONSENT_GROUP_SWEEP_DELAY_MS}, then one\n * last idempotent pass.\n */\nexport type ConsentGroupsChangePhase = 'carry' | 'rehome' | 'sweep'\n\n/** `org.consentGroupsChange` — present exactly while a change runs. */\nexport interface ConsentGroupsChangeMarker {\n changeId: string\n phase: ConsentGroupsChangePhase\n /** {@link ConsentGroupChangePlan.hostIds}. */\n hostIds: string[]\n startedAtMs: number\n /** When the new declaration took effect; absent before the flip. */\n declaredAtMs?: number\n}\n\nconst PHASES: readonly ConsentGroupsChangePhase[] = ['carry', 'rehome', 'sweep']\n\n/** The marker off an org document, or `null` when no change is in flight. */\nexport function readConsentGroupsChange(\n org: Record<string, unknown> | null | undefined,\n): ConsentGroupsChangeMarker | null {\n const raw = (org ?? {})[CONSENT_GROUPS_CHANGE_FIELD]\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const value = raw as Record<string, unknown>\n const changeId = typeof value['changeId'] === 'string' ? value['changeId'] : ''\n const phase = PHASES.find((candidate) => candidate === value['phase'])\n if (!changeId || !phase) return null\n const declaredAtMs = Number(value['declaredAtMs'])\n return {\n changeId,\n phase,\n hostIds: Array.isArray(value['hostIds'])\n ? (value['hostIds'] as unknown[]).filter(\n (id): id is string => typeof id === 'string' && id !== '',\n )\n : [],\n startedAtMs: Number(value['startedAtMs']) || 0,\n ...(Number.isFinite(declaredAtMs) && declaredAtMs > 0 ? { declaredAtMs } : {}),\n }\n}\n\n/**\n * Whether the holder records `hostId` reads may be mid-move: a change in\n * flight has flipped the declaration and names this site.\n *\n * A reader that decides something by a record's ABSENCE — a dynamic list\n * that removes whoever no longer matches — must not decide it while this\n * holds, because a record that has not moved yet reads as missing.\n */\nexport function consentGroupHoldersInMotion(\n org: Record<string, unknown> | null | undefined,\n hostId: string,\n): boolean {\n const marker = readConsentGroupsChange(org)\n return Boolean(\n marker && marker.phase !== 'carry' && marker.hostIds.includes(hostId),\n )\n}\n\n/**\n * The executor's steps, in order.\n *\n * - `carry` — every carry, then every participant's carry phase.\n * - `catch-up-pre` — the refusals filed since the carry began.\n * - `declare` — the flip, in one transaction.\n * - `catch-up-post` — the same catch-up, for what raced the flip.\n * - `rehome` — every participant's holder records.\n * - `sweep-wait` — until {@link CONSENT_GROUP_SWEEP_DELAY_MS} has passed.\n * - `sweep` — every carry again in full, and every participant's sweep.\n * - `finish` — the marker cleared.\n *\n * A rename-only change runs `declare` and `finish` alone.\n */\nexport const CONSENT_GROUP_CHANGE_STEPS = [\n 'carry',\n 'catch-up-pre',\n 'declare',\n 'catch-up-post',\n 'rehome',\n 'sweep-wait',\n 'sweep',\n 'finish',\n] as const\n\nexport type ConsentGroupChangeStep = (typeof CONSENT_GROUP_CHANGE_STEPS)[number]\n\n/** The steps a rename-only change runs. */\nexport const CONSENT_GROUP_RENAME_STEPS: readonly ConsentGroupChangeStep[] = [\n 'declare',\n 'finish',\n]\n\n/** The marker phase a step runs under. */\nexport function consentGroupChangePhaseOf(\n step: ConsentGroupChangeStep,\n): ConsentGroupsChangePhase {\n if (step === 'carry' || step === 'catch-up-pre' || step === 'declare') return 'carry'\n if (step === 'catch-up-post' || step === 'rehome') return 'rehome'\n return 'sweep'\n}\n\nexport type ConsentGroupChangeStatus = 'running' | 'done' | 'canceled'\n\n/** What the refusal carries copied so far, across every step. */\nexport interface ConsentGroupChangeCounts {\n siteSuppressions: number\n topicOptOuts: number\n paces: number\n}\n\n/** A job's state, as the route answers it and the progress panel renders it. */\nexport interface ConsentGroupChangeProgress {\n status: ConsentGroupChangeStatus\n step: ConsentGroupChangeStep\n /** 1-based position of `step` among the steps this change runs. */\n stepIndex: number\n stepCount: number\n /** When the new declaration took effect, or `null` before the flip. */\n declaredAtMs: number | null\n /** When the sweep may start, or `null` before the flip. */\n sweepAtMs: number | null\n /** Five units in a row failed. The change keeps retrying. */\n stalled: boolean\n /** Consecutive failed units. */\n failures: number\n lastError: string | null\n /** Until when another caller holds the job; `null` when it is free. */\n leaseUntilMs: number | null\n counts: ConsentGroupChangeCounts\n /** The sites that received at least one carried refusal. */\n sitesReceiving: number\n /** Each participant's counts, by plugin id. */\n plugins: Record<string, Record<string, number>>\n}\n\n/*==========================================\n * THE ROUTE'S CONTRACT (`POST /api/orgs/consent-groups`)\n *=========================================*/\n\nexport type ConsentGroupsApiAction = 'preview' | 'apply' | 'continue' | 'cancel' | 'status'\n\nexport interface ConsentGroupsApiDeclarationRequest {\n orgId: string\n action: 'preview' | 'apply'\n /**\n * `org.consentGroups` exactly as the editor rendered it, or `null` for an\n * organization with none. Compared by {@link consentGroupsFingerprint}.\n */\n expected: Record<string, unknown> | null\n /** The COMPLETE next declaration. A new group has no id. */\n groups: ConsentGroupDraft[]\n}\n\nexport interface ConsentGroupsApiChangeRequest {\n orgId: string\n action: 'continue' | 'cancel' | 'status'\n changeId: string\n}\n\nexport type ConsentGroupsApiRequest =\n | ConsentGroupsApiDeclarationRequest\n | ConsentGroupsApiChangeRequest\n\n/** 200 for `preview`. */\nexport interface ConsentGroupsApiPreviewResponse {\n ok: true\n preview: ConsentGroupChangePreview\n}\n\n/** 200 for `apply`, `continue`, `cancel` and `status`. */\nexport interface ConsentGroupsApiChangeResponse {\n ok: true\n changeId: string\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n done: boolean\n progress: ConsentGroupChangeProgress\n}\n\n/** 400: the declaration was refused. */\nexport interface ConsentGroupsApiRefusal {\n error: string\n errors: ConsentGroupValidationError[]\n}\n\n/** 409: the declaration changed since the editor rendered it. */\nexport interface ConsentGroupsApiStale {\n error: string\n current: Record<string, unknown> | null\n}\n\n/** 409: another change is running, or a cancel came after the flip. */\nexport interface ConsentGroupsApiInFlight {\n error: string\n changeId: string\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n}\n\n/*==========================================\n * THE PREVIEW (`action: 'preview'`)\n *=========================================*/\n\n/**\n * One sentence a participant adds to the review step, with the figure it is\n * about. `id` is namespaced by the participant (`crm.combine`).\n */\nexport interface ConsentGroupChangePreviewLine {\n id: string\n text: string\n count: number | null\n severity: 'info' | 'warning'\n}\n\n/** How long a change should take, from the work the preview counted. */\nexport type ConsentGroupChangeEstimate = 'instant' | 'under-a-minute' | 'minutes' | 'long'\n\n/** The work bands behind {@link ConsentGroupChangeEstimate}. */\nexport function estimateConsentGroupChange(\n plan: Pick<ConsentGroupChangePlan, 'renameOnly'>,\n documents: number,\n): ConsentGroupChangeEstimate {\n if (plan.renameOnly) return 'instant'\n if (documents < 5_000) return 'under-a-minute'\n if (documents < 100_000) return 'minutes'\n return 'long'\n}\n\nexport interface ConsentGroupChangePreview {\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n /** Stored ids `readConsentGroups` drops; the write removes them. */\n discarded: string[]\n /** The plan's lines, each with the sentence the activity log will write. */\n lines: Array<ConsentGroupChangeLine & { text: string }>\n /** Every group whose signup-form sentence changes. */\n disclosures: Array<{\n groupId: string\n hostIds: string[]\n before: string | null\n after: string | null\n }>\n /** Per carry, what the source site holds now (count aggregations). */\n carries: Array<{\n toHostId: string\n fromHostId: string\n siteSuppressions: number\n topicOptOuts: number\n paces: number\n }>\n /** Per joining site, the refusals on its new siblings it starts honoring. */\n inherited: Array<{ hostId: string; refusals: number }>\n /**\n * Pending confirmations that stop holding a separated site's mail. Only\n * when the organization's confirmation switch is on.\n */\n pendingHolds: Array<{\n hostId: string\n topicId: string\n count: number\n releasedHostIds: string[]\n }>\n /** Team members who can open only some of a changed group's sites. */\n partialAccessMembers: number\n /**\n * Set when the organization's consent policy is `forward` and a site joins:\n * the joining sites may email people their new siblings captured before\n * `enforceFromMs` without a recorded opt-in.\n */\n forwardPolicyWarning: { hostIds: string[]; enforceFromMs: number } | null\n /** Each participant's lines; `null` for one whose preview failed. */\n participants: Array<{\n pluginId: string\n lines: ConsentGroupChangePreviewLine[] | null\n }>\n /** The capture surfaces that show a group's disclosure. */\n capturesDisclosing: string[]\n estimate: ConsentGroupChangeEstimate\n}\n\n/** The groups whose disclosure sentence differs between the declarations. */\nexport function consentGroupDisclosureChanges(\n before: ConsentGroupDeclaration,\n after: ConsentGroupDeclaration,\n): ConsentGroupChangePreview['disclosures'] {\n const sentence = (id: string, group: StoredConsentGroup | undefined) =>\n group\n ? consentGroupDisclosure({\n hostId: group.hostIds[0],\n groupId: id,\n name: group.name,\n hostIds: group.hostIds,\n declared: true,\n awaitsConfirmation: false,\n })\n : null\n const out: ConsentGroupChangePreview['disclosures'] = []\n for (const id of [...new Set([...Object.keys(before), ...Object.keys(after)])].sort(byString)) {\n const was = sentence(id, before[id])\n const now = sentence(id, after[id])\n if (was === now) continue\n out.push({\n groupId: id,\n hostIds: [...(after[id] ?? before[id]).hostIds],\n before: was,\n after: now,\n })\n }\n return out\n}\n"],"names":["consentGroupDisclosure","CONSENT_GROUPS_FIELD","MAX_CONSENT_GROUP_HOSTS","readConsentGroups","CONSENT_GROUPS_CHANGE_FIELD","CONSENT_GROUP_CHANGES_COLLECTION","CONSENT_GROUP_NAME_MAX","CONSENT_GROUP_SWEEP_DELAY_MS","CONSENT_GROUP_ID_PREFIX","orgSiteIds","org","hosts","ids","Array","isArray","map","id","String","Object","entries","filter","value","Set","trim","Boolean","sort","discardedConsentGroupIds","raw","usable","keys","BASE36","defaultRandom","crypto","globalThis","getRandomValues","word","Uint32Array","Math","random","mintConsentGroupId","taken","index","min","floor","has","canonicalGroup","group","name","hostIds","consentGroupsEqual","a","b","consentGroupsFingerprint","length","JSON","stringify","validateConsentGroupDeclaration","input","before","groups","ok","errors","code","sites","siteIds","names","claimed","drafts","forEach","groupIndex","push","entry","toLowerCase","add","hostId","rawId","some","includes","reserved","after","draft","groupOf","declaration","Map","set","byString","withSitesOnly","keep","out","planConsentGroupChange","options","owned","moving","beforeOf","afterOf","keyBefore","get","keyAfter","membersBefore","membersAfter","values","flatMap","carries","stays","toHostId","fromHostId","flows","key","covered","targets","kind","from","fromHostIds","to","toHostIds","survives","stayHostIds","successors","target","lines","planLines","touched","beforeAll","was","now","sameMembers","join","sameName","renameOnly","every","line","created","renamed","added","removed","moved","dissolved","moveInto","toGroupId","fromGroupId","fromName","toName","next","previous","groupId","listSiteNames","slice","describeConsentGroupChangeLine","siteName","site","PHASES","readConsentGroupsChange","changeId","phase","find","candidate","declaredAtMs","Number","startedAtMs","isFinite","consentGroupHoldersInMotion","marker","CONSENT_GROUP_CHANGE_STEPS","CONSENT_GROUP_RENAME_STEPS","consentGroupChangePhaseOf","step","estimateConsentGroupChange","plan","documents","consentGroupDisclosureChanges","sentence","declared","awaitsConfirmation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/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 * CHANGING WHICH SITES ARE ONE SENDER (AGL-3320) — the pure half.\n *\n * `consent-groups.ts` reads a declaration; this is what a change to one\n * means. It validates the next declaration an admin submits, decides what has\n * to move for the sites to stop or start being one sender, and words the\n * result — and it does all of that without a read, so the console's review\n * step, the route's refusal and the executor's plan are one computation.\n *\n * ## Why a change is a plan and not a write\n *\n * A declaration is read at send time, so flipping it is instant — and that is\n * exactly the problem. Two facts are stored per SITE and read across the\n * CURRENT group: a refusal (a suppression row, a topic opt-out, a pace, a\n * declined consent) and a holder's business record (the CRM facet keyed by\n * the group). When sites stop being one sender, each stops reading the\n * other's refusals; when they become one, the records they kept apart must\n * become one. The flip itself moves neither, so every change is:\n *\n * 1. CARRY the refusals onto the sites that are about to stop reading them,\n * in BOTH directions — the person who left one site of a sender was told\n * the opt-out covers all of them, so the site leaving keeps what it\n * honored and the sites staying keep what the leaver honored;\n * 2. FLIP the declaration;\n * 3. RE-HOME the holder records keyed by the old group ids.\n *\n * {@link planConsentGroupChange} says which pairs carry and which holder keys\n * move or split; the executor in `libs/tenant/data/admin` performs it.\n *\n * ## What the planner never touches\n *\n * Grants. A grant is written forward, one entry per disclosed site, and is\n * evidence of what a person was told, so a change never copies, re-keys or\n * revokes one — see `consent-groups.ts`, \"Pooling applies FORWARD only\".\n */\n\nimport {\n consentGroupDisclosure,\n CONSENT_GROUPS_FIELD,\n MAX_CONSENT_GROUP_HOSTS,\n readConsentGroups,\n type StoredConsentGroup,\n} from './consent-groups'\n\n/**\n * The org field that marks a change in flight:\n * {@link ConsentGroupsChangeMarker}. Authoritative — the job document holds\n * the detail, and this says whether there is a job at all.\n *\n * On the org document, beside the declaration it guards, so the transaction\n * that starts a change and the one that flips the declaration read one\n * document. Server-owned: denied to every client by the rules.\n */\nexport const CONSENT_GROUPS_CHANGE_FIELD = 'consentGroupsChange'\n\n/** `orgs/{orgId}/consentGroupChanges/{changeId}` — one job per change. */\nexport const CONSENT_GROUP_CHANGES_COLLECTION = 'consentGroupChanges'\n\n/**\n * The longest group name a declaration may carry.\n *\n * The name is rendered on every signup form of the group, inside a sentence,\n * so it is a brand and not a description.\n */\nexport const CONSENT_GROUP_NAME_MAX = 80\n\n/**\n * How long after the flip the sweep waits before its final pass.\n *\n * A long job — a campaign batch, a dynamic list, an outreach tick — resolves\n * the group once per batch and runs for at most five minutes, so a write it\n * made against the OLD declaration can land after the flip. Six minutes is\n * past the longest of them.\n */\nexport const CONSENT_GROUP_SWEEP_DELAY_MS = 6 * 60_000\n\n/**\n * What every minted group id starts with.\n *\n * A group id is a holder key beside the site ids a solo site is keyed by, so\n * the two must never meet. Firestore's automatic ids — which every site id is\n * — are alphanumeric, and an underscore cannot occur in one.\n */\nexport const CONSENT_GROUP_ID_PREFIX = 'cg_'\n\n/** Every group of a declaration, by id — what `readConsentGroups` answers. */\nexport type ConsentGroupDeclaration = Record<string, StoredConsentGroup>\n\n/** One group of the next declaration as the editor submits it. */\nexport interface ConsentGroupDraft {\n /** The id of a group that exists; absent for a new group. */\n id?: string | null\n name: string\n hostIds: string[]\n}\n\n/**\n * Why a submitted declaration was refused. Every code names something the\n * admin can fix in the editor; `groupIndex` points at the draft it is about,\n * and `hostId` at the site.\n */\nexport type ConsentGroupValidationCode =\n /** Not a list of `{ name, hostIds }` objects. */\n | 'malformed'\n | 'name-empty'\n | 'name-too-long'\n /** Two groups with the same name, ignoring case. */\n | 'name-duplicate'\n | 'too-few-sites'\n | 'too-many-sites'\n /** A site this organization does not own. */\n | 'unknown-site'\n | 'site-in-two-groups'\n /** An id no group of the current declaration has. */\n | 'unknown-group'\n /** One id on two drafts. */\n | 'duplicate-group'\n /**\n * A kept id whose sites are ALL new. Keeping the id would carry the old\n * group's records onto a set of sites none of which kept them; a new group\n * is what that is.\n */\n | 'group-replaced'\n /** The declaration submitted is the one in force. */\n | 'no-change'\n\nexport interface ConsentGroupValidationError {\n code: ConsentGroupValidationCode\n groupIndex?: number\n hostId?: string\n}\n\nexport type ConsentGroupValidation =\n | { ok: true; after: ConsentGroupDeclaration }\n | { ok: false; errors: ConsentGroupValidationError[] }\n\n/**\n * The sites an organization owns, from its `hosts` map, sorted.\n *\n * The map is `{ [hostId]: true }`; an array is read too, so a hand-built\n * fixture and the stored shape agree.\n */\nexport function orgSiteIds(\n org: Record<string, unknown> | null | undefined,\n): string[] {\n const hosts = (org ?? {})['hosts']\n const ids = Array.isArray(hosts)\n ? hosts.map((id) => String(id ?? ''))\n : hosts && typeof hosts === 'object'\n ? Object.entries(hosts as Record<string, unknown>)\n .filter(([, value]) => value !== false && value != null)\n .map(([id]) => id)\n : []\n return [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort()\n}\n\n/** The raw declaration's ids that `readConsentGroups` drops, sorted. */\nexport function discardedConsentGroupIds(\n org: Record<string, unknown> | null | undefined,\n): string[] {\n const raw = (org ?? {})[CONSENT_GROUPS_FIELD]\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return []\n const usable = readConsentGroups(org)\n return Object.keys(raw as Record<string, unknown>)\n .filter((id) => !(id in usable))\n .sort()\n}\n\nconst BASE36 = '0123456789abcdefghijklmnopqrstuvwxyz'\n\n/** Uniform in [0, 1), from the platform's CSPRNG where there is one. */\nfunction defaultRandom(): number {\n const crypto = (globalThis as { crypto?: { getRandomValues?: <T>(array: T) => T } })\n .crypto\n if (crypto?.getRandomValues) {\n const word = crypto.getRandomValues(new Uint32Array(1))[0]\n return word / 2 ** 32\n }\n return Math.random()\n}\n\n/**\n * A fresh group id: {@link CONSENT_GROUP_ID_PREFIX} and sixteen base-36\n * characters, never one in `taken`.\n *\n * `taken` holds every id the id must not equal — the current groups', the\n * raw declaration's, every site's and every id already minted for the same\n * declaration. Eighty bits make a collision a formality, and the loop makes it\n * impossible.\n */\nexport function mintConsentGroupId(\n taken: ReadonlySet<string>,\n random: () => number = defaultRandom,\n): string {\n for (;;) {\n let id = CONSENT_GROUP_ID_PREFIX\n for (let index = 0; index < 16; index += 1) {\n id += BASE36[Math.min(35, Math.floor(random() * 36))]\n }\n if (!taken.has(id)) return id\n }\n}\n\n/** A group's canonical form: its name trimmed, its sites unique and sorted. */\nfunction canonicalGroup(group: unknown): { name: string; hostIds: string[] } {\n const value = (group && typeof group === 'object' ? group : {}) as Record<string, unknown>\n const name = typeof value['name'] === 'string' ? value['name'].trim() : ''\n const hostIds = Array.isArray(value['hostIds'])\n ? [\n ...new Set(\n (value['hostIds'] as unknown[])\n .map((id) => String(id ?? '').trim())\n .filter(Boolean),\n ),\n ].sort()\n : []\n return { name, hostIds }\n}\n\n/**\n * Whether two declarations say the same thing: the same ids, each with the\n * same trimmed name and the same set of sites. Order never matters.\n */\nexport function consentGroupsEqual(\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined,\n): boolean {\n return consentGroupsFingerprint(a) === consentGroupsFingerprint(b)\n}\n\n/**\n * A declaration as one canonical string: what \"the value the editor\n * rendered\" and \"the value stored now\" are compared by.\n *\n * Every entry is kept, usable or not, because the question is whether the\n * stored value CHANGED, and an edit to an entry `readConsentGroups` drops is\n * still somebody else's edit. Absent, `null` and `{}` are one value — an\n * organization with no groups.\n */\nexport function consentGroupsFingerprint(raw: unknown): string {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return 'null'\n const entries = Object.entries(raw as Record<string, unknown>)\n .map(([id, group]) => [id, canonicalGroup(group)] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return entries.length ? JSON.stringify(entries) : 'null'\n}\n\n/**\n * The next declaration, checked against the organization's sites and the\n * declaration in force, with an id minted for every new group.\n *\n * Refuses everything in the list on {@link ConsentGroupValidationCode}, and\n * reports every problem it finds rather than the first, so the editor can\n * mark each draft at once.\n */\nexport function validateConsentGroupDeclaration(input: {\n groups: unknown\n /** The declaration in force: `readConsentGroups(org)`. */\n before: ConsentGroupDeclaration\n /** The organization's sites: {@link orgSiteIds}. */\n siteIds: readonly string[]\n /** Ids a minted id must also avoid — the raw declaration's, for one. */\n reserved?: Iterable<string>\n random?: () => number\n}): ConsentGroupValidation {\n const { before } = input\n if (!Array.isArray(input.groups)) {\n return { ok: false, errors: [{ code: 'malformed' }] }\n }\n const sites = new Set(input.siteIds)\n const errors: ConsentGroupValidationError[] = []\n const names = new Set<string>()\n const ids = new Set<string>()\n const claimed = new Set<string>()\n const drafts: Array<{ id: string | null; name: string; hostIds: string[] }> = []\n\n input.groups.forEach((value, groupIndex) => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n errors.push({ code: 'malformed', groupIndex })\n return\n }\n const entry = value as Record<string, unknown>\n if (typeof entry['name'] !== 'string' || !Array.isArray(entry['hostIds'])) {\n errors.push({ code: 'malformed', groupIndex })\n return\n }\n const { name, hostIds } = canonicalGroup(entry)\n if (!name) errors.push({ code: 'name-empty', groupIndex })\n else if (name.length > CONSENT_GROUP_NAME_MAX) {\n errors.push({ code: 'name-too-long', groupIndex })\n } else if (names.has(name.toLowerCase())) {\n errors.push({ code: 'name-duplicate', groupIndex })\n }\n if (name) names.add(name.toLowerCase())\n\n for (const hostId of hostIds) {\n if (!sites.has(hostId)) {\n errors.push({ code: 'unknown-site', groupIndex, hostId })\n } else if (claimed.has(hostId)) {\n errors.push({ code: 'site-in-two-groups', groupIndex, hostId })\n }\n claimed.add(hostId)\n }\n if (hostIds.length < 2) errors.push({ code: 'too-few-sites', groupIndex })\n else if (hostIds.length > MAX_CONSENT_GROUP_HOSTS) {\n errors.push({ code: 'too-many-sites', groupIndex })\n }\n\n const rawId = entry['id']\n const id = typeof rawId === 'string' && rawId.trim() ? rawId.trim() : null\n if (id !== null) {\n if (ids.has(id)) errors.push({ code: 'duplicate-group', groupIndex })\n else if (!(id in before)) errors.push({ code: 'unknown-group', groupIndex })\n else if (!before[id].hostIds.some((hostId) => hostIds.includes(hostId))) {\n errors.push({ code: 'group-replaced', groupIndex })\n }\n ids.add(id)\n }\n drafts.push({ id, name, hostIds })\n })\n if (errors.length) return { ok: false, errors }\n\n const taken = new Set<string>([\n ...Object.keys(before),\n ...input.siteIds,\n ...(input.reserved ?? []),\n ...ids,\n ])\n const after: ConsentGroupDeclaration = {}\n for (const draft of drafts) {\n const id = draft.id ?? mintConsentGroupId(taken, input.random)\n taken.add(id)\n after[id] = { name: draft.name, hostIds: draft.hostIds }\n }\n if (consentGroupsEqual(before, after)) {\n return { ok: false, errors: [{ code: 'no-change' }] }\n }\n return { ok: true, after }\n}\n\n/*==========================================\n * THE PLAN\n *=========================================*/\n\n/**\n * One direction of a carry: `toHostId` is about to stop reading\n * `fromHostId`'s refusals, so they are copied onto it first.\n */\nexport interface ConsentGroupCarry {\n toHostId: string\n fromHostId: string\n}\n\n/**\n * Every site a holder key covered moves under ONE new key: the key's records\n * are folded into the new key's, and the old key is deleted in the same\n * write.\n */\nexport interface ConsentGroupHolderMove {\n kind: 'move'\n /** The key the records are under now: a group id, or a solo site's id. */\n from: string\n /** The sites `from` covered. */\n fromHostIds: string[]\n /** The key they move under: always a declared group of the next declaration. */\n to: string\n /** The sites of `to` in the next declaration. */\n toHostIds: string[]\n}\n\n/**\n * The sites a holder key covered go separate ways. Each successor may take a\n * copy of the key's records; the key itself survives when some of its sites\n * stay under it, and is deleted when none do.\n */\nexport interface ConsentGroupHolderSplit {\n kind: 'split'\n from: string\n fromHostIds: string[]\n /** Whether `from` is still a key afterwards. */\n survives: boolean\n /** The sites of `from` that stay under it. Empty when it does not survive. */\n stayHostIds: string[]\n /**\n * Where the other sites go: each successor key with the sites of `from`\n * that map to it — never the successor's other sites, whose records were\n * never under `from`.\n */\n successors: Array<{ key: string; hostIds: string[] }>\n}\n\nexport type ConsentGroupHolderFlow = ConsentGroupHolderMove | ConsentGroupHolderSplit\n\n/** What a change does to the declaration, as the activity log words it. */\nexport type ConsentGroupChangeLine =\n | { kind: 'created'; groupId: string; name: string; hostIds: string[] }\n | { kind: 'renamed'; groupId: string; from: string; to: string }\n | { kind: 'added'; groupId: string; name: string; hostId: string }\n | { kind: 'removed'; groupId: string; name: string; hostId: string }\n | {\n kind: 'moved'\n hostId: string\n fromGroupId: string\n fromName: string\n toGroupId: string\n toName: string\n }\n /** Includes a group absorbed into another: its sites each get a `moved`. */\n | { kind: 'dissolved'; groupId: string; name: string }\n\nexport interface ConsentGroupChangePlan {\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n carries: ConsentGroupCarry[]\n flows: ConsentGroupHolderFlow[]\n lines: ConsentGroupChangeLine[]\n /**\n * Every site the change touches — whose group, members or group name\n * changes. What the marker names while the change runs: the sites that\n * may not be deleted, and whose holder records may be in motion.\n */\n hostIds: string[]\n /** Only names change: nothing to carry and nothing to re-home. */\n renameOnly: boolean\n}\n\nfunction groupOf(\n declaration: ConsentGroupDeclaration,\n): Map<string, { id: string; hostIds: string[] }> {\n const map = new Map<string, { id: string; hostIds: string[] }>()\n for (const [id, group] of Object.entries(declaration)) {\n for (const hostId of group.hostIds) map.set(hostId, { id, hostIds: group.hostIds })\n }\n return map\n}\n\nconst byString = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)\n\n/** `declaration` without the sites `keep` does not name. */\nfunction withSitesOnly(\n declaration: ConsentGroupDeclaration,\n keep: ReadonlySet<string>,\n): ConsentGroupDeclaration {\n const out: ConsentGroupDeclaration = {}\n for (const [id, group] of Object.entries(declaration)) {\n const hostIds = group.hostIds.filter((hostId) => keep.has(hostId))\n if (hostIds.length) out[id] = { name: group.name, hostIds }\n }\n return out\n}\n\n/**\n * What moving from `before` to `after` requires.\n *\n * `key(s)` is the holder key of site `s` — its group's id, or `s` itself when\n * it is alone — and `members(s)` the sites of that group, or `[s]`.\n *\n * - **Carries.** For every pair a ≠ b that are one sender now and will not\n * be, `b`'s refusals are copied onto `a`. Pairs that stay together, and\n * pairs that come together, need nothing: a joined site reads its new\n * siblings' refusals the moment the declaration names it.\n * - **Holder flows.** For each key `k` in force, the sites it covers map to\n * a set `T` of keys afterwards. `T = {k}` needs nothing; one other key is\n * a MOVE; anything else is a SPLIT, which `k` survives when it is in `T`.\n *\n * `siteIds`, when given, are the sites the organization still owns: a site\n * named by `before` that is gone neither receives a carry nor takes a copy of\n * a record, because there is nothing left to hold either.\n */\nexport function planConsentGroupChange(\n before: ConsentGroupDeclaration,\n after: ConsentGroupDeclaration,\n options: { siteIds?: readonly string[] } = {},\n): ConsentGroupChangePlan {\n const owned = options.siteIds ? new Set(options.siteIds) : null\n const moving = owned ? withSitesOnly(before, owned) : before\n const beforeOf = groupOf(moving)\n const afterOf = groupOf(after)\n const keyBefore = (hostId: string) => beforeOf.get(hostId)?.id ?? hostId\n const keyAfter = (hostId: string) => afterOf.get(hostId)?.id ?? hostId\n const membersBefore = (hostId: string) => beforeOf.get(hostId)?.hostIds ?? [hostId]\n const membersAfter = (hostId: string) => afterOf.get(hostId)?.hostIds ?? [hostId]\n\n const sites = [\n ...new Set([\n ...Object.values(moving).flatMap((group) => group.hostIds),\n ...Object.values(after).flatMap((group) => group.hostIds),\n ]),\n ].sort(byString)\n\n const carries: ConsentGroupCarry[] = []\n for (const a of sites) {\n const stays = new Set(membersAfter(a))\n for (const b of membersBefore(a)) {\n if (b !== a && !stays.has(b)) carries.push({ toHostId: a, fromHostId: b })\n }\n }\n\n const flows: ConsentGroupHolderFlow[] = []\n const keys = [...new Set(sites.map(keyBefore))].sort(byString)\n for (const key of keys) {\n const covered = sites.filter((hostId) => keyBefore(hostId) === key)\n const targets = [...new Set(covered.map(keyAfter))].sort(byString)\n if (targets.length === 1 && targets[0] === key) continue\n if (targets.length === 1) {\n flows.push({\n kind: 'move',\n from: key,\n fromHostIds: covered,\n to: targets[0],\n toHostIds: [...membersAfter(covered[0])],\n })\n continue\n }\n flows.push({\n kind: 'split',\n from: key,\n fromHostIds: covered,\n survives: targets.includes(key),\n stayHostIds: covered.filter((hostId) => keyAfter(hostId) === key),\n successors: targets\n .filter((target) => target !== key)\n .map((target) => ({\n key: target,\n hostIds: covered.filter((hostId) => keyAfter(hostId) === target),\n })),\n })\n }\n\n const lines = planLines(before, after)\n const touched = new Set<string>()\n const beforeAll = groupOf(before)\n for (const hostId of new Set([...beforeAll.keys(), ...afterOf.keys()])) {\n const was = beforeAll.get(hostId)\n const now = afterOf.get(hostId)\n const sameMembers =\n (was?.hostIds ?? [hostId]).join('\\n') === (now?.hostIds ?? [hostId]).join('\\n')\n const sameName =\n !was || !now || before[was.id]?.name === after[now.id]?.name\n if (was?.id !== now?.id || !sameMembers || !sameName) touched.add(hostId)\n }\n return {\n before,\n after,\n carries,\n flows,\n lines,\n hostIds: [...touched].sort(byString),\n renameOnly:\n !carries.length &&\n !flows.length &&\n lines.length > 0 &&\n lines.every((line) => line.kind === 'renamed'),\n }\n}\n\n/** The declaration-level changes, in the order the activity log lists them. */\nfunction planLines(\n before: ConsentGroupDeclaration,\n after: ConsentGroupDeclaration,\n): ConsentGroupChangeLine[] {\n const beforeOf = groupOf(before)\n const afterOf = groupOf(after)\n const created: ConsentGroupChangeLine[] = []\n const renamed: ConsentGroupChangeLine[] = []\n const added: ConsentGroupChangeLine[] = []\n const removed: ConsentGroupChangeLine[] = []\n const moved: ConsentGroupChangeLine[] = []\n const dissolved: ConsentGroupChangeLine[] = []\n const moveInto = (hostId: string, toGroupId: string) => {\n const from = beforeOf.get(hostId)\n if (!from || from.id === toGroupId) return false\n moved.push({\n kind: 'moved',\n hostId,\n fromGroupId: from.id,\n fromName: before[from.id].name,\n toGroupId,\n toName: after[toGroupId].name,\n })\n return true\n }\n\n for (const id of Object.keys(after).sort(byString)) {\n const next = after[id]\n const previous = before[id]\n if (!previous) {\n created.push({ kind: 'created', groupId: id, name: next.name, hostIds: [...next.hostIds] })\n for (const hostId of next.hostIds) moveInto(hostId, id)\n continue\n }\n if (previous.name !== next.name) {\n renamed.push({ kind: 'renamed', groupId: id, from: previous.name, to: next.name })\n }\n for (const hostId of next.hostIds) {\n if (previous.hostIds.includes(hostId)) continue\n if (!moveInto(hostId, id)) {\n added.push({ kind: 'added', groupId: id, name: next.name, hostId })\n }\n }\n for (const hostId of previous.hostIds) {\n if (next.hostIds.includes(hostId)) continue\n // A site that went to another group is that group's `moved` line.\n if (!afterOf.has(hostId)) {\n removed.push({ kind: 'removed', groupId: id, name: next.name, hostId })\n }\n }\n }\n for (const id of Object.keys(before).sort(byString)) {\n if (!(id in after)) dissolved.push({ kind: 'dissolved', groupId: id, name: before[id].name })\n }\n return [...created, ...renamed, ...added, ...removed, ...moved, ...dissolved]\n}\n\n/**\n * The sites in a sentence: \"A\", \"A and B\", \"A, B and C\".\n */\nexport function listSiteNames(names: readonly string[]): string {\n if (names.length <= 1) return names[0] ?? ''\n return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n}\n\n/**\n * One line of the plan as the organization's activity log says it.\n *\n * `siteName` turns a site id into what the console shows for it; the id\n * itself is the fallback, so a line is never blank.\n */\nexport function describeConsentGroupChangeLine(\n line: ConsentGroupChangeLine,\n siteName: (hostId: string) => string = (hostId) => hostId,\n): string {\n const site = (hostId: string) => siteName(hostId) || hostId\n switch (line.kind) {\n case 'created':\n return `Created consent group \"${line.name}\" with ${listSiteNames(line.hostIds.map(site))}`\n case 'renamed':\n return `Renamed consent group \"${line.from}\" to \"${line.to}\"`\n case 'added':\n return `Added ${site(line.hostId)} to consent group \"${line.name}\"`\n case 'removed':\n return `Removed ${site(line.hostId)} from consent group \"${line.name}\"`\n case 'moved':\n return `Moved ${site(line.hostId)} from consent group \"${line.fromName}\" to \"${line.toName}\"`\n case 'dissolved':\n return `Dissolved consent group \"${line.name}\"`\n }\n}\n\n/*==========================================\n * THE CHANGE IN FLIGHT\n *=========================================*/\n\n/**\n * Where a change stands, on the org marker.\n *\n * - `carry` — copying refusals; the declaration in force is the old one,\n * and the change can still be canceled.\n * - `rehome` — the new declaration is in force; holder records are moving.\n * - `sweep` — waiting out {@link CONSENT_GROUP_SWEEP_DELAY_MS}, then one\n * last idempotent pass.\n */\nexport type ConsentGroupsChangePhase = 'carry' | 'rehome' | 'sweep'\n\n/** `org.consentGroupsChange` — present exactly while a change runs. */\nexport interface ConsentGroupsChangeMarker {\n changeId: string\n phase: ConsentGroupsChangePhase\n /** {@link ConsentGroupChangePlan.hostIds}. */\n hostIds: string[]\n startedAtMs: number\n /** When the new declaration took effect; absent before the flip. */\n declaredAtMs?: number\n}\n\nconst PHASES: readonly ConsentGroupsChangePhase[] = ['carry', 'rehome', 'sweep']\n\n/** The marker off an org document, or `null` when no change is in flight. */\nexport function readConsentGroupsChange(\n org: Record<string, unknown> | null | undefined,\n): ConsentGroupsChangeMarker | null {\n const raw = (org ?? {})[CONSENT_GROUPS_CHANGE_FIELD]\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null\n const value = raw as Record<string, unknown>\n const changeId = typeof value['changeId'] === 'string' ? value['changeId'] : ''\n const phase = PHASES.find((candidate) => candidate === value['phase'])\n if (!changeId || !phase) return null\n const declaredAtMs = Number(value['declaredAtMs'])\n return {\n changeId,\n phase,\n hostIds: Array.isArray(value['hostIds'])\n ? (value['hostIds'] as unknown[]).filter(\n (id): id is string => typeof id === 'string' && id !== '',\n )\n : [],\n startedAtMs: Number(value['startedAtMs']) || 0,\n ...(Number.isFinite(declaredAtMs) && declaredAtMs > 0 ? { declaredAtMs } : {}),\n }\n}\n\n/**\n * Whether the holder records `hostId` reads may be mid-move: a change in\n * flight has flipped the declaration and names this site.\n *\n * A reader that decides something by a record's ABSENCE — a dynamic list\n * that removes whoever no longer matches — must not decide it while this\n * holds, because a record that has not moved yet reads as missing.\n */\nexport function consentGroupHoldersInMotion(\n org: Record<string, unknown> | null | undefined,\n hostId: string,\n): boolean {\n const marker = readConsentGroupsChange(org)\n return Boolean(\n marker && marker.phase !== 'carry' && marker.hostIds.includes(hostId),\n )\n}\n\n/**\n * The executor's steps, in order.\n *\n * - `carry` — every carry, then every participant's carry phase.\n * - `catch-up-pre` — the refusals filed since the carry began.\n * - `declare` — the flip, in one transaction.\n * - `catch-up-post` — the same catch-up, for what raced the flip.\n * - `rehome` — every participant's holder records.\n * - `sweep-wait` — until {@link CONSENT_GROUP_SWEEP_DELAY_MS} has passed.\n * - `sweep` — every carry again in full, and every participant's sweep.\n * - `finish` — the marker cleared.\n *\n * A rename-only change runs `declare` and `finish` alone.\n */\nexport const CONSENT_GROUP_CHANGE_STEPS = [\n 'carry',\n 'catch-up-pre',\n 'declare',\n 'catch-up-post',\n 'rehome',\n 'sweep-wait',\n 'sweep',\n 'finish',\n] as const\n\nexport type ConsentGroupChangeStep = (typeof CONSENT_GROUP_CHANGE_STEPS)[number]\n\n/** The steps a rename-only change runs. */\nexport const CONSENT_GROUP_RENAME_STEPS: readonly ConsentGroupChangeStep[] = [\n 'declare',\n 'finish',\n]\n\n/** The marker phase a step runs under. */\nexport function consentGroupChangePhaseOf(\n step: ConsentGroupChangeStep,\n): ConsentGroupsChangePhase {\n if (step === 'carry' || step === 'catch-up-pre' || step === 'declare') return 'carry'\n if (step === 'catch-up-post' || step === 'rehome') return 'rehome'\n return 'sweep'\n}\n\nexport type ConsentGroupChangeStatus = 'running' | 'done' | 'canceled'\n\n/** What the refusal carries copied so far, across every step. */\nexport interface ConsentGroupChangeCounts {\n siteSuppressions: number\n topicOptOuts: number\n paces: number\n /** Refusals kept after their record was deleted, in `retainedRefusals` (AGL-3338). */\n retainedRefusals: number\n}\n\n/** A job's state, as the route answers it and the progress panel renders it. */\nexport interface ConsentGroupChangeProgress {\n status: ConsentGroupChangeStatus\n step: ConsentGroupChangeStep\n /** 1-based position of `step` among the steps this change runs. */\n stepIndex: number\n stepCount: number\n /** When the new declaration took effect, or `null` before the flip. */\n declaredAtMs: number | null\n /** When the sweep may start, or `null` before the flip. */\n sweepAtMs: number | null\n /** Five units in a row failed. The change keeps retrying. */\n stalled: boolean\n /** Consecutive failed units. */\n failures: number\n lastError: string | null\n /** Until when another caller holds the job; `null` when it is free. */\n leaseUntilMs: number | null\n counts: ConsentGroupChangeCounts\n /** The sites that received at least one carried refusal. */\n sitesReceiving: number\n /** Each participant's counts, by plugin id. */\n plugins: Record<string, Record<string, number>>\n}\n\n/*==========================================\n * THE ROUTE'S CONTRACT (`POST /api/orgs/consent-groups`)\n *=========================================*/\n\nexport type ConsentGroupsApiAction = 'preview' | 'apply' | 'continue' | 'cancel' | 'status'\n\nexport interface ConsentGroupsApiDeclarationRequest {\n orgId: string\n action: 'preview' | 'apply'\n /**\n * `org.consentGroups` exactly as the editor rendered it, or `null` for an\n * organization with none. Compared by {@link consentGroupsFingerprint}.\n */\n expected: Record<string, unknown> | null\n /** The COMPLETE next declaration. A new group has no id. */\n groups: ConsentGroupDraft[]\n}\n\nexport interface ConsentGroupsApiChangeRequest {\n orgId: string\n action: 'continue' | 'cancel' | 'status'\n changeId: string\n}\n\nexport type ConsentGroupsApiRequest =\n | ConsentGroupsApiDeclarationRequest\n | ConsentGroupsApiChangeRequest\n\n/** 200 for `preview`. */\nexport interface ConsentGroupsApiPreviewResponse {\n ok: true\n preview: ConsentGroupChangePreview\n}\n\n/** 200 for `apply`, `continue`, `cancel` and `status`. */\nexport interface ConsentGroupsApiChangeResponse {\n ok: true\n changeId: string\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n done: boolean\n progress: ConsentGroupChangeProgress\n}\n\n/** 400: the declaration was refused. */\nexport interface ConsentGroupsApiRefusal {\n error: string\n errors: ConsentGroupValidationError[]\n}\n\n/** 409: the declaration changed since the editor rendered it. */\nexport interface ConsentGroupsApiStale {\n error: string\n current: Record<string, unknown> | null\n}\n\n/** 409: another change is running, or a cancel came after the flip. */\nexport interface ConsentGroupsApiInFlight {\n error: string\n changeId: string\n phase: ConsentGroupsChangePhase | 'done' | 'canceled'\n}\n\n/*==========================================\n * THE PREVIEW (`action: 'preview'`)\n *=========================================*/\n\n/**\n * One sentence a participant adds to the review step, with the figure it is\n * about. `id` is namespaced by the participant (`crm.combine`).\n */\nexport interface ConsentGroupChangePreviewLine {\n id: string\n text: string\n count: number | null\n severity: 'info' | 'warning'\n}\n\n/** How long a change should take, from the work the preview counted. */\nexport type ConsentGroupChangeEstimate = 'instant' | 'under-a-minute' | 'minutes' | 'long'\n\n/** The work bands behind {@link ConsentGroupChangeEstimate}. */\nexport function estimateConsentGroupChange(\n plan: Pick<ConsentGroupChangePlan, 'renameOnly'>,\n documents: number,\n): ConsentGroupChangeEstimate {\n if (plan.renameOnly) return 'instant'\n if (documents < 5_000) return 'under-a-minute'\n if (documents < 100_000) return 'minutes'\n return 'long'\n}\n\nexport interface ConsentGroupChangePreview {\n before: ConsentGroupDeclaration\n after: ConsentGroupDeclaration\n /** Stored ids `readConsentGroups` drops; the write removes them. */\n discarded: string[]\n /** The plan's lines, each with the sentence the activity log will write. */\n lines: Array<ConsentGroupChangeLine & { text: string }>\n /** Every group whose signup-form sentence changes. */\n disclosures: Array<{\n groupId: string\n hostIds: string[]\n before: string | null\n after: string | null\n }>\n /** Per carry, what the source site holds now (count aggregations). */\n carries: Array<{\n toHostId: string\n fromHostId: string\n siteSuppressions: number\n topicOptOuts: number\n paces: number\n /** The organization's retained refusals for the source site (AGL-3338). */\n retainedRefusals: number\n }>\n /** Per joining site, the refusals on its new siblings it starts honoring. */\n inherited: Array<{ hostId: string; refusals: number }>\n /**\n * Pending confirmations that stop holding a separated site's mail. Only\n * when the organization's confirmation switch is on.\n */\n pendingHolds: Array<{\n hostId: string\n topicId: string\n count: number\n releasedHostIds: string[]\n }>\n /** Team members who can open only some of a changed group's sites. */\n partialAccessMembers: number\n /**\n * Set when the organization's consent policy is `forward` and a site joins:\n * the joining sites may email people their new siblings captured before\n * `enforceFromMs` without a recorded opt-in.\n */\n forwardPolicyWarning: { hostIds: string[]; enforceFromMs: number } | null\n /** Each participant's lines; `null` for one whose preview failed. */\n participants: Array<{\n pluginId: string\n lines: ConsentGroupChangePreviewLine[] | null\n }>\n /** The capture surfaces that show a group's disclosure. */\n capturesDisclosing: string[]\n estimate: ConsentGroupChangeEstimate\n}\n\n/** The groups whose disclosure sentence differs between the declarations. */\nexport function consentGroupDisclosureChanges(\n before: ConsentGroupDeclaration,\n after: ConsentGroupDeclaration,\n): ConsentGroupChangePreview['disclosures'] {\n const sentence = (id: string, group: StoredConsentGroup | undefined) =>\n group\n ? consentGroupDisclosure({\n hostId: group.hostIds[0],\n groupId: id,\n name: group.name,\n hostIds: group.hostIds,\n declared: true,\n awaitsConfirmation: false,\n })\n : null\n const out: ConsentGroupChangePreview['disclosures'] = []\n for (const id of [...new Set([...Object.keys(before), ...Object.keys(after)])].sort(byString)) {\n const was = sentence(id, before[id])\n const now = sentence(id, after[id])\n if (was === now) continue\n out.push({\n groupId: id,\n hostIds: [...(after[id] ?? before[id]).hostIds],\n before: was,\n after: now,\n })\n }\n return out\n}\n"],"names":["consentGroupDisclosure","CONSENT_GROUPS_FIELD","MAX_CONSENT_GROUP_HOSTS","readConsentGroups","CONSENT_GROUPS_CHANGE_FIELD","CONSENT_GROUP_CHANGES_COLLECTION","CONSENT_GROUP_NAME_MAX","CONSENT_GROUP_SWEEP_DELAY_MS","CONSENT_GROUP_ID_PREFIX","orgSiteIds","org","hosts","ids","Array","isArray","map","id","String","Object","entries","filter","value","Set","trim","Boolean","sort","discardedConsentGroupIds","raw","usable","keys","BASE36","defaultRandom","crypto","globalThis","getRandomValues","word","Uint32Array","Math","random","mintConsentGroupId","taken","index","min","floor","has","canonicalGroup","group","name","hostIds","consentGroupsEqual","a","b","consentGroupsFingerprint","length","JSON","stringify","validateConsentGroupDeclaration","input","before","groups","ok","errors","code","sites","siteIds","names","claimed","drafts","forEach","groupIndex","push","entry","toLowerCase","add","hostId","rawId","some","includes","reserved","after","draft","groupOf","declaration","Map","set","byString","withSitesOnly","keep","out","planConsentGroupChange","options","owned","moving","beforeOf","afterOf","keyBefore","get","keyAfter","membersBefore","membersAfter","values","flatMap","carries","stays","toHostId","fromHostId","flows","key","covered","targets","kind","from","fromHostIds","to","toHostIds","survives","stayHostIds","successors","target","lines","planLines","touched","beforeAll","was","now","sameMembers","join","sameName","renameOnly","every","line","created","renamed","added","removed","moved","dissolved","moveInto","toGroupId","fromGroupId","fromName","toName","next","previous","groupId","listSiteNames","slice","describeConsentGroupChangeLine","siteName","site","PHASES","readConsentGroupsChange","changeId","phase","find","candidate","declaredAtMs","Number","startedAtMs","isFinite","consentGroupHoldersInMotion","marker","CONSENT_GROUP_CHANGE_STEPS","CONSENT_GROUP_RENAME_STEPS","consentGroupChangePhaseOf","step","estimateConsentGroupChange","plan","documents","consentGroupDisclosureChanges","sentence","declared","awaitsConfirmation"],"mappings":"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"}
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
* are resolved by different functions on purpose, and neither reads the
|
|
129
129
|
* other.
|
|
130
130
|
*/
|
|
131
|
-
import type { TopicSubscriptionState } from './
|
|
131
|
+
import type { TopicSubscriptionState } from './subscription-topics';
|
|
132
132
|
import { type ScopeToken } from './scope-tokens';
|
|
133
133
|
/**
|
|
134
134
|
* The org field holding the declarations:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/consent-groups.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 * CONSENT GROUPS — the declared unit a marketing basis is given to.\n *\n * ## The two organizations this exists to serve at once\n *\n * An AGENCY runs twelve unrelated client brands out of one account. A person\n * who ticked a box on one client's form has agreed to hear from that client\n * and from nobody else, and mailing them on behalf of the other eleven is a\n * consent violation whatever the account structure says.\n *\n * A SINGLE BUSINESS runs three sites — a shop, a booking page, a blog — under\n * one name. It collects addresses on all three and mails them from any of\n * them, because there is one controller and one brand and the visitor knows\n * it. Refusing that is not caution, it is a product that cannot serve a\n * perfectly ordinary customer.\n *\n * The org boundary cannot tell them apart, and neither can anything else the\n * database already holds. So the difference is DECLARED: a group names the\n * sites that are one sender. Undeclared, every site is its own group of one,\n * which is the agency's answer and costs the agency nothing to get right.\n *\n * ## ⛔ NOTHING HERE IS INFERRED\n *\n * Not from shared ownership, not from a shared org, not from a shared sending\n * domain, not from a shared theme. Sharing an account is a billing fact.\n * There is exactly one input — {@link CONSENT_GROUPS_FIELD} on the org — and\n * a site that is not named in it is alone.\n *\n * ## A group must be DISCLOSABLE, so it must have a NAME\n *\n * Pooling is only legitimate if the person was told about it: the checkbox\n * has to say who they will hear from. A group with no display name cannot be\n * rendered on a capture surface, so it cannot be disclosed, so it does not\n * pool — {@link consentGroupForHost} returns the group of one for every site\n * in a nameless group rather than pooling silently. That refusal is the whole\n * mechanism by which \"declared\" is stronger than \"configured\".\n *\n * ## …and a grant pools only where the name was actually SHOWN\n *\n * Disclosable is not disclosed. A form rendered before the group existed, a\n * checkout that has never displayed the sentence, and a page cached across a\n * rename all capture under a declaration their visitor never read. So a\n * capture surface that renders {@link consentGroupDisclosure} sends back the\n * {@link consentGroupDisclosureKey} of what it rendered, and\n * {@link consentGroupForGrant} pools the grant only when that key matches the\n * group as it stands. Anything else — no key, a stale key, a surface that\n * never renders the sentence — records the capturing site alone. Visibility\n * is not narrowed with it: that axis follows the group, see below.\n *\n * ## Pooling applies FORWARD only\n *\n * A grant records the group AS DISCLOSED at the moment it was given, in the\n * entry itself, and every covered site gets its own entry. Adding a site to a\n * group therefore reaches captures made after the change and none made\n * before. An org-level switch that widened existing grants would be the leak\n * wearing a different hat: the people already on the list were told a\n * different thing. The same holds for a grant carried from one record to\n * another — a list enrollment, a lead becoming a contact: it is copied as it\n * was recorded, never re-derived from the group as it stands now.\n *\n * OPT-OUT runs the other way — read against the CURRENT group, so a site\n * joining a group inherits every refusal already standing against it. The\n * asymmetry is the same one the rest of this area keeps: a permissive fact is\n * written narrowly and read exactly, a restrictive fact is read as broadly as\n * it could possibly apply. Every opt-out the send paths consult is read this\n * way — the site suppression lists, the topic opt-outs and the recipient's\n * cadence (`email-suppression.ts`), and the consent refusal\n * (`marketing-consent.ts`) — and the preference pages lift one across the\n * group, since a person rejoining the sender has rejoined all of it.\n *\n * ## A pending CONFIRMATION is the asking site's, unless the org says the group waits\n *\n * A site that asks for a confirmation click holds its own mail on that stream\n * until the click comes. A pending question is not a refusal, so by default\n * the rule above does not carry it: the sibling sites go on mailing. An org\n * may turn {@link CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD} on, and then every\n * site of a declared group waits for the click the way the asking site does —\n * a pending entry on any of them holds the stream from all of them, read\n * across the CURRENT group exactly as a refusal is.\n *\n * The switch is resolved INTO the group ({@link ConsentGroup.awaitsConfirmation}),\n * so a send path already holding the group holds the answer and reads nothing\n * more for it. A group of one never waits on anybody: there is nobody else.\n * Off is the default and the absence, and an org that never set it reads\n * exactly the documents it read before the field existed.\n *\n * ⚠️ The one change that runs AGAINST reading is sites SEPARATING — a site\n * leaving a group, moving to another, or a group dissolving. A refusal is\n * stored on the site the person acted on, so once two sites stop being one\n * sender, each stops seeing the refusals filed against the other, and every\n * one of them was a refusal of the sender both sites were at the time. So a\n * change to this declaration carries them BOTH WAYS before it takes effect:\n * the sites that stay keep every opt-out filed on the site that leaves, and\n * the site that leaves keeps every opt-out filed on the sites it leaves —\n * the preference page promised the person that an opt-out from the group\n * covers all of it, and separating the sites must not quietly break that.\n * The consent group change (the pure plan in `consent-group-change.ts`, run\n * by the executor in `@aglyn/tenant-data-admin`, started from the Emails\n * hub's Consent groups section through `POST /api/orgs/consent-groups`) is\n * the one writer of this field, and it carries the site suppression rows,\n * the topic opt-outs, the pace and a contact's per-site refusal before it\n * flips the declaration.\n * A site that is in a group, or in a change still running, cannot be\n * deleted until it is out: deletion would destroy refusals a sibling still\n * reads.\n *\n * ## VISIBILITY IS A SEPARATE AXIS\n *\n * Seeing a contact is not permission to mail them. A group decides consent\n * and opt-out; `visibleTo` decides who can read the row, and an org may\n * widen that for reporting while consent stays where it was given. The two\n * are resolved by different functions on purpose, and neither reads the\n * other.\n */\n\nimport type { TopicSubscriptionState } from './email-topics'\nimport { hostScopeToken, MAX_SCOPE_HOSTS, type ScopeToken } from './scope-tokens'\n\n/**\n * The org field holding the declarations:\n * `{ [groupId]: { name, hostIds } }`.\n *\n * On the org rather than on each host, because a group is a statement ABOUT\n * a set and a set stored as N per-host pointers can disagree with itself —\n * site A naming a group that site B has left is a half-declared controller,\n * and there is no reading of that which is safe to pool on.\n */\nexport const CONSENT_GROUPS_FIELD = 'consentGroups'\n\n/**\n * The org field that makes a declared group's sites wait for each other's\n * confirmation click (AGL-3316): `true` is on; absent is off.\n *\n * One switch for the org beside {@link CONSENT_GROUPS_FIELD} rather than a\n * flag on each declaration, because a declaration decides WHICH sites are one\n * sender and this decides how the org treats a question one of them has put.\n * Written only by `/api/orgs/settings`; the rules deny both fields to every\n * client.\n */\nexport const CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD =\n 'consentGroupsAwaitConfirmation'\n\n/**\n * The most sites one group may name.\n *\n * The same ceiling `visibleTo` carries, and for a related reason: the send\n * path reads a group's opt-out lists in one `getAll`, so the group multiplies\n * that round trip's size. It is also a sanity bound on a disclosure — a\n * checkbox that has to name forty brands has not disclosed anything.\n */\nexport const MAX_CONSENT_GROUP_HOSTS = MAX_SCOPE_HOSTS\n\n/** One declared group, as stored. */\nexport interface StoredConsentGroup {\n /** Shown on the capture surface. Without it the group cannot pool. */\n name: string\n /** The sites that are one sender. */\n hostIds: string[]\n}\n\n/**\n * The group a host belongs to, resolved FOR that host.\n *\n * It carries the asking site as well as the set, which is what lets one value\n * be passed where both are needed. Every consent read needs both — the grant\n * is looked up under the asking site, while a refusal is honored across the\n * whole group — and two arguments that must agree is two arguments that can\n * disagree.\n */\nexport interface ConsentGroup {\n /** The site this was resolved for. Always a member of {@link hostIds}. */\n hostId: string\n /**\n * The group's id, or the host's own id for an undeclared site.\n *\n * Recorded on every grant so a stored basis says which controller it was\n * given to even after the declaration changes.\n */\n groupId: string\n /**\n * What the capture surface must display. `null` for a group of one, where\n * the site's own name is what a form already shows and inventing a second\n * one would put a name in front of a person that nothing else uses.\n */\n name: string | null\n /** Every site covered, sorted, always including the host asked about. */\n hostIds: string[]\n /** False for the implicit group of one. */\n declared: boolean\n /**\n * Whether a confirmation one site of the group is waiting on holds the\n * other sites' mail too — the org's\n * {@link CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD}, for a declared group.\n * Always `false` for a group of one, which has no other site to hold.\n */\n awaitsConfirmation: boolean\n}\n\n/**\n * The group of one — a site that has declared no pooling.\n *\n * Exported because it is a value callers state deliberately: a writer that\n * means \"this site only\" says so, rather than passing a host id and letting\n * a helper decide what it covers.\n */\nexport function soloConsentGroup(hostId: string): ConsentGroup {\n if (!hostId) {\n throw new Error('[consent-groups] a consent group must name a site')\n }\n return {\n hostId,\n groupId: hostId,\n name: null,\n hostIds: [hostId],\n declared: false,\n awaitsConfirmation: false,\n }\n}\n\n/**\n * Whether the org turned {@link CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD} on.\n *\n * Only a stored `true` is on. Absent, `false`, or anything a stray write left\n * is off — the state every org was in before the field existed.\n */\nexport function consentGroupsAwaitConfirmation(\n org: Record<string, unknown> | null | undefined,\n): boolean {\n return (org ?? {})[CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD] === true\n}\n\n/**\n * Reads the declared groups off an org document, dropping every entry that\n * cannot be honored.\n *\n * A malformed declaration reads as ABSENT, never as a wider group: the whole\n * value of this field is that pooling is deliberate, and a corrupt value must\n * not be a way to reach an audience nobody declared.\n *\n * The five refusals, each of which would otherwise pool without a disclosure\n * or file one sender's records under another's key:\n *\n * - **no usable name** — cannot be shown on a form, so cannot be disclosed;\n * - **fewer than two sites** — not a pooling declaration, and a group of one\n * is what an undeclared site already gets;\n * - **over {@link MAX_CONSENT_GROUP_HOSTS}** — see that constant;\n * - **an id that is a site's id** — a site that declared nothing uses its\n * own id as its group id, so a group spelled the same way would share\n * that site's key: its grant stamps, its opt-out lookups and the records\n * the CRM files under the group id would read as the lone site's, and the\n * lone site's as the group's. Checked against every site the org holds\n * and every site any entry names, usable or not;\n * - **a site claimed by two groups** — two controllers claiming one site is\n * a contradiction, not a wider group, so BOTH claims are dropped and the\n * site falls back to being alone.\n */\nexport function readConsentGroups(\n org: Record<string, unknown> | null | undefined,\n): Record<string, StoredConsentGroup> {\n const raw = (org ?? {})[CONSENT_GROUPS_FIELD]\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {}\n const siteIds = consentGroupSiteIds(org, raw as Record<string, unknown>)\n const usable: Record<string, StoredConsentGroup> = {}\n for (const [groupId, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!groupId || !value || typeof value !== 'object') continue\n if (siteIds.has(groupId)) continue\n const group = value as Record<string, unknown>\n const name = typeof group['name'] === 'string' ? group['name'].trim() : ''\n if (!name) continue\n const hostIds = Array.isArray(group['hostIds'])\n ? [\n ...new Set(\n (group['hostIds'] as unknown[])\n .map((id) => String(id ?? '').trim())\n .filter(Boolean),\n ),\n ].sort()\n : []\n if (hostIds.length < 2 || hostIds.length > MAX_CONSENT_GROUP_HOSTS) continue\n usable[groupId] = { name, hostIds }\n }\n /*\n * The overlap pass, second because it needs every survivor of the first.\n * A site named by two groups is dropped from BOTH — the alternative is\n * picking one, and a coin flip is not a declaration.\n */\n const claims = new Map<string, number>()\n for (const group of Object.values(usable)) {\n for (const hostId of group.hostIds) {\n claims.set(hostId, (claims.get(hostId) ?? 0) + 1)\n }\n }\n const contested = new Set(\n [...claims.entries()].filter(([, count]) => count > 1).map(([id]) => id),\n )\n if (!contested.size) return usable\n const settled: Record<string, StoredConsentGroup> = {}\n for (const [groupId, group] of Object.entries(usable)) {\n if (group.hostIds.some((hostId) => contested.has(hostId))) continue\n settled[groupId] = group\n }\n return settled\n}\n\n/**\n * Every id that names a site as far as the declaration can tell: the org's\n * own `hosts` and every site any raw entry names, including the entries the\n * other refusals will drop.\n *\n * Wider than the usable set on purpose. A group id that collides with a\n * site the org has not linked yet, or with a site named only by a broken\n * entry, is the same collision one edit later, and refusing it now costs a\n * declaration nothing a valid one would have.\n */\nfunction consentGroupSiteIds(\n org: Record<string, unknown> | null | undefined,\n raw: Record<string, unknown>,\n): Set<string> {\n const ids = new Set<string>()\n const hosts = (org ?? {})['hosts']\n if (Array.isArray(hosts)) {\n for (const id of hosts) if (typeof id === 'string' && id) ids.add(id)\n } else if (hosts && typeof hosts === 'object') {\n for (const id of Object.keys(hosts)) if (id) ids.add(id)\n }\n for (const value of Object.values(raw)) {\n const hostIds = (value as { hostIds?: unknown } | null)?.hostIds\n if (!Array.isArray(hostIds)) continue\n for (const id of hostIds) {\n const trimmed = String(id ?? '').trim()\n if (trimmed) ids.add(trimmed)\n }\n }\n return ids\n}\n\n/**\n * The group `hostId` belongs to — the declared one, or the group of one.\n *\n * The ONLY way any caller learns what a site's consent covers. A caller that\n * built its own set from `org.hosts`, or from a sending domain, or from a\n * theme, would be inferring a controller, which is the thing this module\n * exists to make impossible.\n */\nexport function consentGroupForHost(\n org: Record<string, unknown> | null | undefined,\n hostId: string,\n): ConsentGroup {\n if (!hostId) {\n throw new Error('[consent-groups] a consent group must name a site')\n }\n for (const [groupId, group] of Object.entries(readConsentGroups(org))) {\n if (!group.hostIds.includes(hostId)) continue\n return {\n hostId,\n groupId,\n name: group.name,\n hostIds: [...group.hostIds],\n declared: true,\n awaitsConfirmation: consentGroupsAwaitConfirmation(org),\n }\n }\n return soloConsentGroup(hostId)\n}\n\n/**\n * The org field a running consent group change marks itself with:\n * `{ changeId, phase, hostIds, … }`, present from the moment the change\n * starts until it has finished. Only its `hostIds` is read here.\n */\nconst CONSENT_GROUP_CHANGE_MARKER_FIELD = 'consentGroupsChange'\n\n/** Why a site may not be deleted yet — see {@link consentGroupSiteHold}. */\nexport type ConsentGroupSiteHold =\n /** The site is one sender with other sites, under this group. */\n | { reason: 'grouped'; groupId: string; name: string }\n /** A consent group change naming the site has not finished. */\n | { reason: 'changing' }\n\n/**\n * Whether deleting `hostId` would take refusals its consent group still\n * needs, and why — or `null` when the site may go (AGL-3320).\n *\n * A site's opt-outs are stored on the site and read across its group, so\n * erasing a grouped site erases refusals its siblings honor today, and\n * leaves the declaration naming a site that no longer exists. A site named\n * by a change still in progress is the same hazard in motion: the change\n * may be copying its opt-outs out, or relying on them being there. So the\n * site leaves its group first — which carries its refusals to the sites\n * that stay — and is deleted after.\n *\n * Deleting the whole organization is the one exception, and is the caller's\n * to make: nothing is left to read the refusals.\n */\nexport function consentGroupSiteHold(\n org: Record<string, unknown> | null | undefined,\n hostId: string,\n): ConsentGroupSiteHold | null {\n if (!hostId) return null\n const group = consentGroupForHost(org, hostId)\n if (group.declared) {\n return { reason: 'grouped', groupId: group.groupId, name: group.name ?? '' }\n }\n const marker = (org ?? {})[CONSENT_GROUP_CHANGE_MARKER_FIELD]\n const changing = (marker as { hostIds?: unknown } | null | undefined)?.hostIds\n if (Array.isArray(changing) && changing.includes(hostId)) {\n return { reason: 'changing' }\n }\n return null\n}\n\n/**\n * The sites whose OPT-OUTS answer for mail sent by `group.hostId`, the\n * sending site first.\n *\n * Every site the group names, because a refusal filed against any of them is\n * a refusal of the sender — see \"OPT-OUT runs the other way\" above. The\n * sending site leads so a reader that treats its own record differently from\n * a sibling's (a sibling's pending confirmation holds the send only when the\n * group {@link ConsentGroup.awaitsConfirmation}) can tell them apart by\n * position. A group of one is the site alone.\n */\nexport function consentGroupOptOutHosts(\n group: Pick<ConsentGroup, 'hostId' | 'hostIds'>,\n): string[] {\n const siblings = (group.hostIds ?? []).filter(\n (id) => typeof id === 'string' && id && id !== group.hostId,\n )\n return [group.hostId, ...new Set(siblings)]\n}\n\n/**\n * The sending site's standing on one stream, read across its group.\n *\n * `states` are the stream's states on {@link consentGroupOptOutHosts}' sites,\n * in that order, so the sending site's comes first. A refusal on any of them\n * is a refusal of the sender. The sending site's own pending confirmation\n * holds its mail; a sibling's holds it only when the group\n * {@link ConsentGroup.awaitsConfirmation}. The precedence is the one\n * `readTopicSubscriptionState` keeps within one entry: a refusal outranks a\n * pending question, and an expired question is still pending.\n */\nexport function consentGroupTopicState(\n group: Pick<ConsentGroup, 'awaitsConfirmation'>,\n states: readonly TopicSubscriptionState[],\n): TopicSubscriptionState {\n const [own = 'subscribed', ...siblings] = states\n if (own === 'opted-out' || siblings.includes('opted-out')) return 'opted-out'\n if (own === 'pending') return 'pending'\n return group.awaitsConfirmation === true && siblings.includes('pending')\n ? 'pending'\n : 'subscribed'\n}\n\n/**\n * The `visibleTo` a resource captured by this group starts with.\n *\n * A separate function from anything above, called by the capture doors and\n * by nothing that decides mailability, because visibility and consent are\n * different questions with different answers. A group of one produces\n * `['host:{id}']` — the isolation an agency needs, arrived at without the\n * agency configuring anything.\n */\nexport function consentGroupScope(group: ConsentGroup): ScopeToken[] {\n return group.hostIds.map(hostScopeToken)\n}\n\n/**\n * The sentence a capture surface must show beside the checkbox, or `null`\n * when the site's own name already says it.\n *\n * Returned as text rather than a boolean so that the disclosure and the\n * grant come from ONE resolved group: a form that rendered its own wording\n * from a separate lookup could show one set of brands and record another,\n * and the recorded set is the one that decides who mails this person.\n */\nexport function consentGroupDisclosure(group: ConsentGroup): string | null {\n if (!group.declared || !group.name) return null\n return `You'll receive marketing email from ${group.name}, which covers ${group.hostIds.length} sites.`\n}\n\n/**\n * A short fingerprint of the disclosure a capture surface rendered, or\n * `null` when {@link consentGroupDisclosure} has nothing to render.\n *\n * The surface sends it back with the submission, and\n * {@link consentGroupForGrant} pools only when it still matches: a form\n * rendered before a rename, before a site joined, or before the group\n * existed at all carries a key that no longer does, and its grant narrows to\n * the site the person was on rather than reaching sites they were never told\n * about.\n *\n * Over the three things the sentence and the grant stand for — the group's\n * id, its name and its sites, sorted — serialized as JSON so no name can\n * run into an id. FNV-1a over the UTF-16 code units, which keeps it\n * synchronous and identical in the browser and on the server with nothing\n * to import. It is not a secret and proves no identity: it answers \"was it\n * THIS disclosure\", and a caller willing to lie could as easily lie about\n * the checkbox.\n */\nexport function consentGroupDisclosureKey(\n group: ConsentGroup,\n): string | null {\n if (!consentGroupDisclosure(group)) return null\n const text = JSON.stringify([\n group.groupId,\n group.name,\n [...group.hostIds].sort(),\n ])\n let hash = 0x811c9dc5\n for (let index = 0; index < text.length; index += 1) {\n hash ^= text.charCodeAt(index)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\n/**\n * The group a GRANT is recorded for: `group` itself only when it is declared\n * and `disclosureKey` is exactly its current {@link consentGroupDisclosureKey};\n * otherwise the capturing site alone.\n *\n * Called by every door that records a grant, with whatever key the capture\n * surface sent back — none, for every surface that does not render the\n * disclosure. Narrow is the failure direction on purpose: a grant that should\n * have pooled and did not withholds mail from a sibling site, where one that\n * pooled on a sentence nobody saw sends it.\n *\n * Only the grant narrows. The same capture still stamps `visibleTo` from the\n * whole group, and still reads refusals across it: who may SEE a person and\n * who may MAIL them are separate questions — see the module note.\n */\nexport function consentGroupForGrant(\n group: ConsentGroup,\n disclosureKey: string | null | undefined,\n): ConsentGroup {\n const expected = consentGroupDisclosureKey(group)\n if (expected !== null && disclosureKey === expected) return group\n return soloConsentGroup(group.hostId)\n}\n"],"names":["hostScopeToken","MAX_SCOPE_HOSTS","CONSENT_GROUPS_FIELD","CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD","MAX_CONSENT_GROUP_HOSTS","soloConsentGroup","hostId","Error","groupId","name","hostIds","declared","awaitsConfirmation","consentGroupsAwaitConfirmation","org","readConsentGroups","raw","Array","isArray","siteIds","consentGroupSiteIds","usable","value","Object","entries","has","group","trim","Set","map","id","String","filter","Boolean","sort","length","claims","Map","values","set","get","contested","count","size","settled","some","ids","hosts","add","keys","trimmed","consentGroupForHost","includes","CONSENT_GROUP_CHANGE_MARKER_FIELD","consentGroupSiteHold","reason","marker","changing","consentGroupOptOutHosts","siblings","consentGroupTopicState","states","own","consentGroupScope","consentGroupDisclosure","consentGroupDisclosureKey","text","JSON","stringify","hash","index","charCodeAt","Math","imul","toString","padStart","consentGroupForGrant","disclosureKey","expected"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/consent-groups.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 * CONSENT GROUPS — the declared unit a marketing basis is given to.\n *\n * ## The two organizations this exists to serve at once\n *\n * An AGENCY runs twelve unrelated client brands out of one account. A person\n * who ticked a box on one client's form has agreed to hear from that client\n * and from nobody else, and mailing them on behalf of the other eleven is a\n * consent violation whatever the account structure says.\n *\n * A SINGLE BUSINESS runs three sites — a shop, a booking page, a blog — under\n * one name. It collects addresses on all three and mails them from any of\n * them, because there is one controller and one brand and the visitor knows\n * it. Refusing that is not caution, it is a product that cannot serve a\n * perfectly ordinary customer.\n *\n * The org boundary cannot tell them apart, and neither can anything else the\n * database already holds. So the difference is DECLARED: a group names the\n * sites that are one sender. Undeclared, every site is its own group of one,\n * which is the agency's answer and costs the agency nothing to get right.\n *\n * ## ⛔ NOTHING HERE IS INFERRED\n *\n * Not from shared ownership, not from a shared org, not from a shared sending\n * domain, not from a shared theme. Sharing an account is a billing fact.\n * There is exactly one input — {@link CONSENT_GROUPS_FIELD} on the org — and\n * a site that is not named in it is alone.\n *\n * ## A group must be DISCLOSABLE, so it must have a NAME\n *\n * Pooling is only legitimate if the person was told about it: the checkbox\n * has to say who they will hear from. A group with no display name cannot be\n * rendered on a capture surface, so it cannot be disclosed, so it does not\n * pool — {@link consentGroupForHost} returns the group of one for every site\n * in a nameless group rather than pooling silently. That refusal is the whole\n * mechanism by which \"declared\" is stronger than \"configured\".\n *\n * ## …and a grant pools only where the name was actually SHOWN\n *\n * Disclosable is not disclosed. A form rendered before the group existed, a\n * checkout that has never displayed the sentence, and a page cached across a\n * rename all capture under a declaration their visitor never read. So a\n * capture surface that renders {@link consentGroupDisclosure} sends back the\n * {@link consentGroupDisclosureKey} of what it rendered, and\n * {@link consentGroupForGrant} pools the grant only when that key matches the\n * group as it stands. Anything else — no key, a stale key, a surface that\n * never renders the sentence — records the capturing site alone. Visibility\n * is not narrowed with it: that axis follows the group, see below.\n *\n * ## Pooling applies FORWARD only\n *\n * A grant records the group AS DISCLOSED at the moment it was given, in the\n * entry itself, and every covered site gets its own entry. Adding a site to a\n * group therefore reaches captures made after the change and none made\n * before. An org-level switch that widened existing grants would be the leak\n * wearing a different hat: the people already on the list were told a\n * different thing. The same holds for a grant carried from one record to\n * another — a list enrollment, a lead becoming a contact: it is copied as it\n * was recorded, never re-derived from the group as it stands now.\n *\n * OPT-OUT runs the other way — read against the CURRENT group, so a site\n * joining a group inherits every refusal already standing against it. The\n * asymmetry is the same one the rest of this area keeps: a permissive fact is\n * written narrowly and read exactly, a restrictive fact is read as broadly as\n * it could possibly apply. Every opt-out the send paths consult is read this\n * way — the site suppression lists, the topic opt-outs and the recipient's\n * cadence (`email-suppression.ts`), and the consent refusal\n * (`marketing-consent.ts`) — and the preference pages lift one across the\n * group, since a person rejoining the sender has rejoined all of it.\n *\n * ## A pending CONFIRMATION is the asking site's, unless the org says the group waits\n *\n * A site that asks for a confirmation click holds its own mail on that stream\n * until the click comes. A pending question is not a refusal, so by default\n * the rule above does not carry it: the sibling sites go on mailing. An org\n * may turn {@link CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD} on, and then every\n * site of a declared group waits for the click the way the asking site does —\n * a pending entry on any of them holds the stream from all of them, read\n * across the CURRENT group exactly as a refusal is.\n *\n * The switch is resolved INTO the group ({@link ConsentGroup.awaitsConfirmation}),\n * so a send path already holding the group holds the answer and reads nothing\n * more for it. A group of one never waits on anybody: there is nobody else.\n * Off is the default and the absence, and an org that never set it reads\n * exactly the documents it read before the field existed.\n *\n * ⚠️ The one change that runs AGAINST reading is sites SEPARATING — a site\n * leaving a group, moving to another, or a group dissolving. A refusal is\n * stored on the site the person acted on, so once two sites stop being one\n * sender, each stops seeing the refusals filed against the other, and every\n * one of them was a refusal of the sender both sites were at the time. So a\n * change to this declaration carries them BOTH WAYS before it takes effect:\n * the sites that stay keep every opt-out filed on the site that leaves, and\n * the site that leaves keeps every opt-out filed on the sites it leaves —\n * the preference page promised the person that an opt-out from the group\n * covers all of it, and separating the sites must not quietly break that.\n * The consent group change (the pure plan in `consent-group-change.ts`, run\n * by the executor in `@aglyn/tenant-data-admin`, started from the Emails\n * hub's Consent groups section through `POST /api/orgs/consent-groups`) is\n * the one writer of this field, and it carries the site suppression rows,\n * the topic opt-outs, the pace and a contact's per-site refusal before it\n * flips the declaration.\n * A site that is in a group, or in a change still running, cannot be\n * deleted until it is out: deletion would destroy refusals a sibling still\n * reads.\n *\n * ## VISIBILITY IS A SEPARATE AXIS\n *\n * Seeing a contact is not permission to mail them. A group decides consent\n * and opt-out; `visibleTo` decides who can read the row, and an org may\n * widen that for reporting while consent stays where it was given. The two\n * are resolved by different functions on purpose, and neither reads the\n * other.\n */\n\nimport type { TopicSubscriptionState } from './subscription-topics'\nimport { hostScopeToken, MAX_SCOPE_HOSTS, type ScopeToken } from './scope-tokens'\n\n/**\n * The org field holding the declarations:\n * `{ [groupId]: { name, hostIds } }`.\n *\n * On the org rather than on each host, because a group is a statement ABOUT\n * a set and a set stored as N per-host pointers can disagree with itself —\n * site A naming a group that site B has left is a half-declared controller,\n * and there is no reading of that which is safe to pool on.\n */\nexport const CONSENT_GROUPS_FIELD = 'consentGroups'\n\n/**\n * The org field that makes a declared group's sites wait for each other's\n * confirmation click (AGL-3316): `true` is on; absent is off.\n *\n * One switch for the org beside {@link CONSENT_GROUPS_FIELD} rather than a\n * flag on each declaration, because a declaration decides WHICH sites are one\n * sender and this decides how the org treats a question one of them has put.\n * Written only by `/api/orgs/settings`; the rules deny both fields to every\n * client.\n */\nexport const CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD =\n 'consentGroupsAwaitConfirmation'\n\n/**\n * The most sites one group may name.\n *\n * The same ceiling `visibleTo` carries, and for a related reason: the send\n * path reads a group's opt-out lists in one `getAll`, so the group multiplies\n * that round trip's size. It is also a sanity bound on a disclosure — a\n * checkbox that has to name forty brands has not disclosed anything.\n */\nexport const MAX_CONSENT_GROUP_HOSTS = MAX_SCOPE_HOSTS\n\n/** One declared group, as stored. */\nexport interface StoredConsentGroup {\n /** Shown on the capture surface. Without it the group cannot pool. */\n name: string\n /** The sites that are one sender. */\n hostIds: string[]\n}\n\n/**\n * The group a host belongs to, resolved FOR that host.\n *\n * It carries the asking site as well as the set, which is what lets one value\n * be passed where both are needed. Every consent read needs both — the grant\n * is looked up under the asking site, while a refusal is honored across the\n * whole group — and two arguments that must agree is two arguments that can\n * disagree.\n */\nexport interface ConsentGroup {\n /** The site this was resolved for. Always a member of {@link hostIds}. */\n hostId: string\n /**\n * The group's id, or the host's own id for an undeclared site.\n *\n * Recorded on every grant so a stored basis says which controller it was\n * given to even after the declaration changes.\n */\n groupId: string\n /**\n * What the capture surface must display. `null` for a group of one, where\n * the site's own name is what a form already shows and inventing a second\n * one would put a name in front of a person that nothing else uses.\n */\n name: string | null\n /** Every site covered, sorted, always including the host asked about. */\n hostIds: string[]\n /** False for the implicit group of one. */\n declared: boolean\n /**\n * Whether a confirmation one site of the group is waiting on holds the\n * other sites' mail too — the org's\n * {@link CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD}, for a declared group.\n * Always `false` for a group of one, which has no other site to hold.\n */\n awaitsConfirmation: boolean\n}\n\n/**\n * The group of one — a site that has declared no pooling.\n *\n * Exported because it is a value callers state deliberately: a writer that\n * means \"this site only\" says so, rather than passing a host id and letting\n * a helper decide what it covers.\n */\nexport function soloConsentGroup(hostId: string): ConsentGroup {\n if (!hostId) {\n throw new Error('[consent-groups] a consent group must name a site')\n }\n return {\n hostId,\n groupId: hostId,\n name: null,\n hostIds: [hostId],\n declared: false,\n awaitsConfirmation: false,\n }\n}\n\n/**\n * Whether the org turned {@link CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD} on.\n *\n * Only a stored `true` is on. Absent, `false`, or anything a stray write left\n * is off — the state every org was in before the field existed.\n */\nexport function consentGroupsAwaitConfirmation(\n org: Record<string, unknown> | null | undefined,\n): boolean {\n return (org ?? {})[CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD] === true\n}\n\n/**\n * Reads the declared groups off an org document, dropping every entry that\n * cannot be honored.\n *\n * A malformed declaration reads as ABSENT, never as a wider group: the whole\n * value of this field is that pooling is deliberate, and a corrupt value must\n * not be a way to reach an audience nobody declared.\n *\n * The five refusals, each of which would otherwise pool without a disclosure\n * or file one sender's records under another's key:\n *\n * - **no usable name** — cannot be shown on a form, so cannot be disclosed;\n * - **fewer than two sites** — not a pooling declaration, and a group of one\n * is what an undeclared site already gets;\n * - **over {@link MAX_CONSENT_GROUP_HOSTS}** — see that constant;\n * - **an id that is a site's id** — a site that declared nothing uses its\n * own id as its group id, so a group spelled the same way would share\n * that site's key: its grant stamps, its opt-out lookups and the records\n * the CRM files under the group id would read as the lone site's, and the\n * lone site's as the group's. Checked against every site the org holds\n * and every site any entry names, usable or not;\n * - **a site claimed by two groups** — two controllers claiming one site is\n * a contradiction, not a wider group, so BOTH claims are dropped and the\n * site falls back to being alone.\n */\nexport function readConsentGroups(\n org: Record<string, unknown> | null | undefined,\n): Record<string, StoredConsentGroup> {\n const raw = (org ?? {})[CONSENT_GROUPS_FIELD]\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {}\n const siteIds = consentGroupSiteIds(org, raw as Record<string, unknown>)\n const usable: Record<string, StoredConsentGroup> = {}\n for (const [groupId, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!groupId || !value || typeof value !== 'object') continue\n if (siteIds.has(groupId)) continue\n const group = value as Record<string, unknown>\n const name = typeof group['name'] === 'string' ? group['name'].trim() : ''\n if (!name) continue\n const hostIds = Array.isArray(group['hostIds'])\n ? [\n ...new Set(\n (group['hostIds'] as unknown[])\n .map((id) => String(id ?? '').trim())\n .filter(Boolean),\n ),\n ].sort()\n : []\n if (hostIds.length < 2 || hostIds.length > MAX_CONSENT_GROUP_HOSTS) continue\n usable[groupId] = { name, hostIds }\n }\n /*\n * The overlap pass, second because it needs every survivor of the first.\n * A site named by two groups is dropped from BOTH — the alternative is\n * picking one, and a coin flip is not a declaration.\n */\n const claims = new Map<string, number>()\n for (const group of Object.values(usable)) {\n for (const hostId of group.hostIds) {\n claims.set(hostId, (claims.get(hostId) ?? 0) + 1)\n }\n }\n const contested = new Set(\n [...claims.entries()].filter(([, count]) => count > 1).map(([id]) => id),\n )\n if (!contested.size) return usable\n const settled: Record<string, StoredConsentGroup> = {}\n for (const [groupId, group] of Object.entries(usable)) {\n if (group.hostIds.some((hostId) => contested.has(hostId))) continue\n settled[groupId] = group\n }\n return settled\n}\n\n/**\n * Every id that names a site as far as the declaration can tell: the org's\n * own `hosts` and every site any raw entry names, including the entries the\n * other refusals will drop.\n *\n * Wider than the usable set on purpose. A group id that collides with a\n * site the org has not linked yet, or with a site named only by a broken\n * entry, is the same collision one edit later, and refusing it now costs a\n * declaration nothing a valid one would have.\n */\nfunction consentGroupSiteIds(\n org: Record<string, unknown> | null | undefined,\n raw: Record<string, unknown>,\n): Set<string> {\n const ids = new Set<string>()\n const hosts = (org ?? {})['hosts']\n if (Array.isArray(hosts)) {\n for (const id of hosts) if (typeof id === 'string' && id) ids.add(id)\n } else if (hosts && typeof hosts === 'object') {\n for (const id of Object.keys(hosts)) if (id) ids.add(id)\n }\n for (const value of Object.values(raw)) {\n const hostIds = (value as { hostIds?: unknown } | null)?.hostIds\n if (!Array.isArray(hostIds)) continue\n for (const id of hostIds) {\n const trimmed = String(id ?? '').trim()\n if (trimmed) ids.add(trimmed)\n }\n }\n return ids\n}\n\n/**\n * The group `hostId` belongs to — the declared one, or the group of one.\n *\n * The ONLY way any caller learns what a site's consent covers. A caller that\n * built its own set from `org.hosts`, or from a sending domain, or from a\n * theme, would be inferring a controller, which is the thing this module\n * exists to make impossible.\n */\nexport function consentGroupForHost(\n org: Record<string, unknown> | null | undefined,\n hostId: string,\n): ConsentGroup {\n if (!hostId) {\n throw new Error('[consent-groups] a consent group must name a site')\n }\n for (const [groupId, group] of Object.entries(readConsentGroups(org))) {\n if (!group.hostIds.includes(hostId)) continue\n return {\n hostId,\n groupId,\n name: group.name,\n hostIds: [...group.hostIds],\n declared: true,\n awaitsConfirmation: consentGroupsAwaitConfirmation(org),\n }\n }\n return soloConsentGroup(hostId)\n}\n\n/**\n * The org field a running consent group change marks itself with:\n * `{ changeId, phase, hostIds, … }`, present from the moment the change\n * starts until it has finished. Only its `hostIds` is read here.\n */\nconst CONSENT_GROUP_CHANGE_MARKER_FIELD = 'consentGroupsChange'\n\n/** Why a site may not be deleted yet — see {@link consentGroupSiteHold}. */\nexport type ConsentGroupSiteHold =\n /** The site is one sender with other sites, under this group. */\n | { reason: 'grouped'; groupId: string; name: string }\n /** A consent group change naming the site has not finished. */\n | { reason: 'changing' }\n\n/**\n * Whether deleting `hostId` would take refusals its consent group still\n * needs, and why — or `null` when the site may go (AGL-3320).\n *\n * A site's opt-outs are stored on the site and read across its group, so\n * erasing a grouped site erases refusals its siblings honor today, and\n * leaves the declaration naming a site that no longer exists. A site named\n * by a change still in progress is the same hazard in motion: the change\n * may be copying its opt-outs out, or relying on them being there. So the\n * site leaves its group first — which carries its refusals to the sites\n * that stay — and is deleted after.\n *\n * Deleting the whole organization is the one exception, and is the caller's\n * to make: nothing is left to read the refusals.\n */\nexport function consentGroupSiteHold(\n org: Record<string, unknown> | null | undefined,\n hostId: string,\n): ConsentGroupSiteHold | null {\n if (!hostId) return null\n const group = consentGroupForHost(org, hostId)\n if (group.declared) {\n return { reason: 'grouped', groupId: group.groupId, name: group.name ?? '' }\n }\n const marker = (org ?? {})[CONSENT_GROUP_CHANGE_MARKER_FIELD]\n const changing = (marker as { hostIds?: unknown } | null | undefined)?.hostIds\n if (Array.isArray(changing) && changing.includes(hostId)) {\n return { reason: 'changing' }\n }\n return null\n}\n\n/**\n * The sites whose OPT-OUTS answer for mail sent by `group.hostId`, the\n * sending site first.\n *\n * Every site the group names, because a refusal filed against any of them is\n * a refusal of the sender — see \"OPT-OUT runs the other way\" above. The\n * sending site leads so a reader that treats its own record differently from\n * a sibling's (a sibling's pending confirmation holds the send only when the\n * group {@link ConsentGroup.awaitsConfirmation}) can tell them apart by\n * position. A group of one is the site alone.\n */\nexport function consentGroupOptOutHosts(\n group: Pick<ConsentGroup, 'hostId' | 'hostIds'>,\n): string[] {\n const siblings = (group.hostIds ?? []).filter(\n (id) => typeof id === 'string' && id && id !== group.hostId,\n )\n return [group.hostId, ...new Set(siblings)]\n}\n\n/**\n * The sending site's standing on one stream, read across its group.\n *\n * `states` are the stream's states on {@link consentGroupOptOutHosts}' sites,\n * in that order, so the sending site's comes first. A refusal on any of them\n * is a refusal of the sender. The sending site's own pending confirmation\n * holds its mail; a sibling's holds it only when the group\n * {@link ConsentGroup.awaitsConfirmation}. The precedence is the one\n * `readTopicSubscriptionState` keeps within one entry: a refusal outranks a\n * pending question, and an expired question is still pending.\n */\nexport function consentGroupTopicState(\n group: Pick<ConsentGroup, 'awaitsConfirmation'>,\n states: readonly TopicSubscriptionState[],\n): TopicSubscriptionState {\n const [own = 'subscribed', ...siblings] = states\n if (own === 'opted-out' || siblings.includes('opted-out')) return 'opted-out'\n if (own === 'pending') return 'pending'\n return group.awaitsConfirmation === true && siblings.includes('pending')\n ? 'pending'\n : 'subscribed'\n}\n\n/**\n * The `visibleTo` a resource captured by this group starts with.\n *\n * A separate function from anything above, called by the capture doors and\n * by nothing that decides mailability, because visibility and consent are\n * different questions with different answers. A group of one produces\n * `['host:{id}']` — the isolation an agency needs, arrived at without the\n * agency configuring anything.\n */\nexport function consentGroupScope(group: ConsentGroup): ScopeToken[] {\n return group.hostIds.map(hostScopeToken)\n}\n\n/**\n * The sentence a capture surface must show beside the checkbox, or `null`\n * when the site's own name already says it.\n *\n * Returned as text rather than a boolean so that the disclosure and the\n * grant come from ONE resolved group: a form that rendered its own wording\n * from a separate lookup could show one set of brands and record another,\n * and the recorded set is the one that decides who mails this person.\n */\nexport function consentGroupDisclosure(group: ConsentGroup): string | null {\n if (!group.declared || !group.name) return null\n return `You'll receive marketing email from ${group.name}, which covers ${group.hostIds.length} sites.`\n}\n\n/**\n * A short fingerprint of the disclosure a capture surface rendered, or\n * `null` when {@link consentGroupDisclosure} has nothing to render.\n *\n * The surface sends it back with the submission, and\n * {@link consentGroupForGrant} pools only when it still matches: a form\n * rendered before a rename, before a site joined, or before the group\n * existed at all carries a key that no longer does, and its grant narrows to\n * the site the person was on rather than reaching sites they were never told\n * about.\n *\n * Over the three things the sentence and the grant stand for — the group's\n * id, its name and its sites, sorted — serialized as JSON so no name can\n * run into an id. FNV-1a over the UTF-16 code units, which keeps it\n * synchronous and identical in the browser and on the server with nothing\n * to import. It is not a secret and proves no identity: it answers \"was it\n * THIS disclosure\", and a caller willing to lie could as easily lie about\n * the checkbox.\n */\nexport function consentGroupDisclosureKey(\n group: ConsentGroup,\n): string | null {\n if (!consentGroupDisclosure(group)) return null\n const text = JSON.stringify([\n group.groupId,\n group.name,\n [...group.hostIds].sort(),\n ])\n let hash = 0x811c9dc5\n for (let index = 0; index < text.length; index += 1) {\n hash ^= text.charCodeAt(index)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\n/**\n * The group a GRANT is recorded for: `group` itself only when it is declared\n * and `disclosureKey` is exactly its current {@link consentGroupDisclosureKey};\n * otherwise the capturing site alone.\n *\n * Called by every door that records a grant, with whatever key the capture\n * surface sent back — none, for every surface that does not render the\n * disclosure. Narrow is the failure direction on purpose: a grant that should\n * have pooled and did not withholds mail from a sibling site, where one that\n * pooled on a sentence nobody saw sends it.\n *\n * Only the grant narrows. The same capture still stamps `visibleTo` from the\n * whole group, and still reads refusals across it: who may SEE a person and\n * who may MAIL them are separate questions — see the module note.\n */\nexport function consentGroupForGrant(\n group: ConsentGroup,\n disclosureKey: string | null | undefined,\n): ConsentGroup {\n const expected = consentGroupDisclosureKey(group)\n if (expected !== null && disclosureKey === expected) return group\n return soloConsentGroup(group.hostId)\n}\n"],"names":["hostScopeToken","MAX_SCOPE_HOSTS","CONSENT_GROUPS_FIELD","CONSENT_GROUPS_AWAIT_CONFIRMATION_FIELD","MAX_CONSENT_GROUP_HOSTS","soloConsentGroup","hostId","Error","groupId","name","hostIds","declared","awaitsConfirmation","consentGroupsAwaitConfirmation","org","readConsentGroups","raw","Array","isArray","siteIds","consentGroupSiteIds","usable","value","Object","entries","has","group","trim","Set","map","id","String","filter","Boolean","sort","length","claims","Map","values","set","get","contested","count","size","settled","some","ids","hosts","add","keys","trimmed","consentGroupForHost","includes","CONSENT_GROUP_CHANGE_MARKER_FIELD","consentGroupSiteHold","reason","marker","changing","consentGroupOptOutHosts","siblings","consentGroupTopicState","states","own","consentGroupScope","consentGroupDisclosure","consentGroupDisclosureKey","text","JSON","stringify","hash","index","charCodeAt","Math","imul","toString","padStart","consentGroupForGrant","disclosureKey","expected"],"mappings":"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"}
|
|
@@ -45,7 +45,6 @@ export declare enum Route {
|
|
|
45
45
|
ORG_HOME = "/[orgSlug]",
|
|
46
46
|
ORG_MEDIA = "/[orgSlug]/media",
|
|
47
47
|
ORG_DATA = "/[orgSlug]/data",
|
|
48
|
-
ORG_CONTACTS = "/[orgSlug]/contacts",
|
|
49
48
|
ORG_CRM = "/[orgSlug]/crm",
|
|
50
49
|
ORG_PLUGIN = "/[orgSlug]/[pluginSlug]",
|
|
51
50
|
ORG_PLUGINS = "/[orgSlug]/plugins",
|
|
@@ -114,7 +113,6 @@ export declare enum Route {
|
|
|
114
113
|
HOST_DASHBOARD = "/[orgSlug]/hosts/[host]",
|
|
115
114
|
HOST_PLUGIN = "/[orgSlug]/hosts/[host]/[pluginSlug]",
|
|
116
115
|
HOST_INBOX = "/[orgSlug]/hosts/[host]/inbox",
|
|
117
|
-
HOST_CONTACTS = "/[orgSlug]/hosts/[host]/contacts",
|
|
118
116
|
HOST_MEDIA = "/[orgSlug]/hosts/[host]/media",
|
|
119
117
|
HOST_SETUP = "/[orgSlug]/hosts/[host]/setup",
|
|
120
118
|
HOST_SETUP_DETAILS = "/[orgSlug]/hosts/[host]/setup/details",
|
|
@@ -134,22 +132,8 @@ export declare enum Route {
|
|
|
134
132
|
HOST_ADMIN_DANGER = "/[orgSlug]/hosts/[host]/admin/danger",
|
|
135
133
|
HOST_THEME = "/[orgSlug]/hosts/[host]/theme",
|
|
136
134
|
HOST_AUTOMATION = "/[orgSlug]/hosts/[host]/automation",
|
|
137
|
-
/**
|
|
138
|
-
* The address the automation section answered to before it had a name of
|
|
139
|
-
* its own. Nothing links here — `HOST_AUTOMATION` is what the nav, the
|
|
140
|
-
* search results and the activity feed build — but the console redirects it
|
|
141
|
-
* permanently, so a held bookmark still lands on the section (and keeps its
|
|
142
|
-
* `?tab=`, which never changed).
|
|
143
|
-
*
|
|
144
|
-
* Kept on the table rather than as a bare literal in `next.config.js`
|
|
145
|
-
* because the redirect and the address it serves are one fact:
|
|
146
|
-
* `automation-section.spec.ts` reads this entry and fails if the rule for
|
|
147
|
-
* it goes missing.
|
|
148
|
-
*/
|
|
149
|
-
HOST_WORKFLOWS = "/[orgSlug]/hosts/[host]/workflows",
|
|
150
135
|
HOST_DATA = "/[orgSlug]/hosts/[host]/data",
|
|
151
136
|
HOST_LOGIC = "/[orgSlug]/hosts/[host]/logic",
|
|
152
|
-
HOST_PRODUCTS = "/[orgSlug]/hosts/[host]/products",
|
|
153
137
|
HOST_COMPONENTS = "/[orgSlug]/hosts/[host]/components",
|
|
154
138
|
COMPONENT_DETAILS = "/[orgSlug]/hosts/[host]/components/[componentId]",
|
|
155
139
|
HOST_FORMS = "/[orgSlug]/hosts/[host]/forms",
|
|
@@ -157,16 +141,16 @@ export declare enum Route {
|
|
|
157
141
|
HOST_TEMPLATES = "/[orgSlug]/hosts/[host]/templates",
|
|
158
142
|
TEMPLATE_DETAILS = "/[orgSlug]/hosts/[host]/templates/[templateId]",
|
|
159
143
|
HOST_MARKETING = "/[orgSlug]/hosts/[host]/marketing",
|
|
160
|
-
HOST_BOOKINGS = "/[orgSlug]/hosts/[host]/bookings",
|
|
161
144
|
HOST_REDIRECTS = "/[orgSlug]/hosts/[host]/redirects",
|
|
162
145
|
HOST_USERS = "/[orgSlug]/hosts/[host]/users",
|
|
163
146
|
HOST_ANALYTICS = "/[orgSlug]/hosts/[host]/analytics",
|
|
164
147
|
COMPONENT_BESIGNER = "/[orgSlug]/hosts/[host]/components/[componentId]/versions/[versionId]/besigner",
|
|
165
|
-
|
|
148
|
+
PLUGIN_DOCUMENT_DETAILS = "/[orgSlug]/hosts/[host]/[documentSegment]/[docId]",
|
|
149
|
+
PLUGIN_DOCUMENT_BESIGNER = "/[orgSlug]/hosts/[host]/[documentSegment]/[docId]/versions/[versionId]/besigner",
|
|
166
150
|
TEMPLATE_BESIGNER = "/[orgSlug]/hosts/[host]/templates/[templateId]/besigner",
|
|
167
151
|
LAYOUT_BESIGNER = "/[orgSlug]/hosts/[host]/layouts/[layoutId]/versions/[versionId]/besigner",
|
|
168
152
|
COMPONENT_PREVIEW = "/[orgSlug]/hosts/[host]/components/[componentId]/versions/[versionId]/preview",
|
|
169
|
-
|
|
153
|
+
PLUGIN_DOCUMENT_PREVIEW = "/[orgSlug]/hosts/[host]/[documentSegment]/[docId]/versions/[versionId]/preview",
|
|
170
154
|
TEMPLATE_PREVIEW = "/[orgSlug]/hosts/[host]/templates/[templateId]/preview",
|
|
171
155
|
LAYOUT_PREVIEW = "/[orgSlug]/hosts/[host]/layouts/[layoutId]/versions/[versionId]/preview",
|
|
172
156
|
HOST_LAYOUTS = "/[orgSlug]/hosts/[host]/layouts",
|
|
@@ -263,9 +247,6 @@ export interface RoutePayload {
|
|
|
263
247
|
[Route.ORG_DATA]: {
|
|
264
248
|
orgSlug: string;
|
|
265
249
|
};
|
|
266
|
-
[Route.ORG_CONTACTS]: {
|
|
267
|
-
orgSlug: string;
|
|
268
|
-
};
|
|
269
250
|
[Route.ORG_CRM]: {
|
|
270
251
|
orgSlug: string;
|
|
271
252
|
};
|
|
@@ -409,10 +390,6 @@ export interface RoutePayload {
|
|
|
409
390
|
orgSlug: string;
|
|
410
391
|
host: string;
|
|
411
392
|
};
|
|
412
|
-
[Route.HOST_CONTACTS]: {
|
|
413
|
-
orgSlug: string;
|
|
414
|
-
host: string;
|
|
415
|
-
};
|
|
416
393
|
[Route.HOST_SETUP]: {
|
|
417
394
|
orgSlug: string;
|
|
418
395
|
host: string;
|
|
@@ -495,10 +472,6 @@ export interface RoutePayload {
|
|
|
495
472
|
orgSlug: string;
|
|
496
473
|
host: string;
|
|
497
474
|
};
|
|
498
|
-
[Route.HOST_WORKFLOWS]: {
|
|
499
|
-
orgSlug: string;
|
|
500
|
-
host: string;
|
|
501
|
-
};
|
|
502
475
|
[Route.HOST_DATA]: {
|
|
503
476
|
orgSlug: string;
|
|
504
477
|
host: string;
|
|
@@ -507,10 +480,6 @@ export interface RoutePayload {
|
|
|
507
480
|
orgSlug: string;
|
|
508
481
|
host: string;
|
|
509
482
|
};
|
|
510
|
-
[Route.HOST_PRODUCTS]: {
|
|
511
|
-
orgSlug: string;
|
|
512
|
-
host: string;
|
|
513
|
-
};
|
|
514
483
|
[Route.HOST_COMPONENTS]: {
|
|
515
484
|
orgSlug: string;
|
|
516
485
|
host: string;
|
|
@@ -542,10 +511,6 @@ export interface RoutePayload {
|
|
|
542
511
|
orgSlug: string;
|
|
543
512
|
host: string;
|
|
544
513
|
};
|
|
545
|
-
[Route.HOST_BOOKINGS]: {
|
|
546
|
-
orgSlug: string;
|
|
547
|
-
host: string;
|
|
548
|
-
};
|
|
549
514
|
[Route.HOST_REDIRECTS]: {
|
|
550
515
|
orgSlug: string;
|
|
551
516
|
host: string;
|
|
@@ -564,10 +529,17 @@ export interface RoutePayload {
|
|
|
564
529
|
componentId: string;
|
|
565
530
|
versionId: string;
|
|
566
531
|
};
|
|
567
|
-
[Route.
|
|
532
|
+
[Route.PLUGIN_DOCUMENT_DETAILS]: {
|
|
568
533
|
orgSlug: string;
|
|
569
534
|
host: string;
|
|
570
|
-
|
|
535
|
+
documentSegment: string;
|
|
536
|
+
docId: string;
|
|
537
|
+
};
|
|
538
|
+
[Route.PLUGIN_DOCUMENT_BESIGNER]: {
|
|
539
|
+
orgSlug: string;
|
|
540
|
+
host: string;
|
|
541
|
+
documentSegment: string;
|
|
542
|
+
docId: string;
|
|
571
543
|
versionId: string;
|
|
572
544
|
};
|
|
573
545
|
[Route.TEMPLATE_BESIGNER]: {
|
|
@@ -587,10 +559,11 @@ export interface RoutePayload {
|
|
|
587
559
|
componentId: string;
|
|
588
560
|
versionId: string;
|
|
589
561
|
};
|
|
590
|
-
[Route.
|
|
562
|
+
[Route.PLUGIN_DOCUMENT_PREVIEW]: {
|
|
591
563
|
orgSlug: string;
|
|
592
564
|
host: string;
|
|
593
|
-
|
|
565
|
+
documentSegment: string;
|
|
566
|
+
docId: string;
|
|
594
567
|
versionId: string;
|
|
595
568
|
};
|
|
596
569
|
[Route.TEMPLATE_PREVIEW]: {
|