@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.0
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/apps/storefront/astro.config.mjs +3 -3
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +12 -14
- package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
- package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
- package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
- package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
- package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
- package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
- package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
- package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
- package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
- package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
- package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
- package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
- package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
- package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
- package/apps/storefront/public/shared/commerce-chrome.css +20 -2
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
- package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
- package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
- package/apps/storefront/src/components/Img.astro +1 -1
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
- package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
- package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
- package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
- package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
- package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
- package/apps/storefront/src/components/membership/TierCards.astro +2 -2
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
- package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
- package/apps/storefront/src/config/devTenantSeed.ts +9 -9
- package/apps/storefront/src/config/resolver.ts +4 -4
- package/apps/storefront/src/layouts/Layout.astro +100 -20
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
- package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
- package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/apps/adminService.ts +10 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
- package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
- package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +264 -10
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
- package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/blog/collection.ts +97 -10
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +57 -3
- package/apps/storefront/src/lib/blog/types.ts +9 -9
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
- package/apps/storefront/src/lib/chrome/model.ts +48 -253
- package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
- package/apps/storefront/src/lib/chrome/variant.ts +39 -0
- package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
- package/apps/storefront/src/lib/content/index.ts +5 -5
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
- package/apps/storefront/src/lib/content-edit/route.ts +1 -1
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
- package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
- package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
- package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
- package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
- package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
- package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
- package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
- package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
- package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
- package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +18 -6
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
- package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
- package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
- package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
- package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
- package/apps/storefront/src/lib/membership/model.ts +1 -2
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
- package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
- package/apps/storefront/src/lib/newsletter/send.ts +3 -3
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
- package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
- package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
- package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
- package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
- package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
- package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/seo/alternates.ts +1 -1
- package/apps/storefront/src/lib/social/notify.ts +4 -4
- package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
- package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
- package/apps/storefront/src/lib/subscription/model.ts +4 -4
- package/apps/storefront/src/lib/the-build/provider.ts +9 -4
- package/apps/storefront/src/middleware/index.ts +273 -51
- package/apps/storefront/src/pages/[...slug].astro +9 -4
- package/apps/storefront/src/pages/admin/review-release.astro +265 -0
- package/apps/storefront/src/pages/admin/select.astro +96 -0
- package/apps/storefront/src/pages/admin.astro +16 -2
- package/apps/storefront/src/pages/api/activity.ts +8 -8
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
- package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
- package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
- package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
- package/apps/storefront/src/pages/blog/[slug].astro +5 -5
- package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
- package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
- package/apps/storefront/src/pages/blog/index.astro +2 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
- package/apps/storefront/src/pages/cockpit.astro +61 -1362
- package/apps/storefront/src/pages/index.astro +10 -5
- package/apps/storefront/src/pages/llms.txt.ts +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +7 -6
- package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
- package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
- package/apps/storefront/src/pages/the-build/index.astro +2 -2
- package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
- package/apps/storefront/src/styles/admin.css +27 -1
- package/apps/storefront/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +2 -1
- package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
- package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
- package/packages/public-runtime/src/activity/catalog.ts +41 -37
- package/packages/public-runtime/src/activity/event.ts +6 -6
- package/packages/public-runtime/src/activity/index.ts +2 -2
- package/packages/public-runtime/src/activity/redaction.ts +4 -4
- package/packages/public-runtime/src/candidate-index.ts +42 -8
- package/packages/public-runtime/src/checkout-orders.ts +2 -2
- package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
- package/packages/public-runtime/src/checkout-style.ts +7 -7
- package/packages/public-runtime/src/checkout.ts +3 -3
- package/packages/public-runtime/src/chrome.ts +777 -0
- package/packages/public-runtime/src/compliance-obligations.ts +310 -0
- package/packages/public-runtime/src/csp.ts +99 -0
- package/packages/public-runtime/src/customization-runtime.ts +32 -1
- package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
- package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
- package/packages/public-runtime/src/embed-catalog.mjs +40 -0
- package/packages/public-runtime/src/evidence.ts +288 -0
- package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
- package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
- package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
- package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
- package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
- package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
- package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
- package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
- package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
- package/packages/public-runtime/src/index.ts +16 -1
- package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
- package/packages/public-runtime/src/integration/assurance.ts +228 -0
- package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
- package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
- package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
- package/packages/public-runtime/src/integration/consent-config.ts +260 -0
- package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
- package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
- package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
- package/packages/public-runtime/src/integration/csp.ts +105 -0
- package/packages/public-runtime/src/integration/digest.ts +43 -0
- package/packages/public-runtime/src/integration/environment.ts +228 -0
- package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
- package/packages/public-runtime/src/integration/events.ts +701 -0
- package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
- package/packages/public-runtime/src/integration/index.ts +90 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
- package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
- package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
- package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
- package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
- package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
- package/packages/public-runtime/src/integration/manifest.ts +470 -0
- package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
- package/packages/public-runtime/src/integration/observability.ts +451 -0
- package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
- package/packages/public-runtime/src/integration/phases.ts +182 -0
- package/packages/public-runtime/src/integration/plan.ts +112 -0
- package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
- package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
- package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
- package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
- package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
- package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
- package/packages/public-runtime/src/integration/render-phases.ts +267 -0
- package/packages/public-runtime/src/integration/resolve.ts +465 -0
- package/packages/public-runtime/src/integration/result.ts +239 -0
- package/packages/public-runtime/src/integration/secrets.ts +92 -0
- package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
- package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
- package/packages/public-runtime/src/integration/version.ts +92 -0
- package/packages/public-runtime/src/integration-capabilities.ts +520 -0
- package/packages/public-runtime/src/membership.ts +2 -2
- package/packages/public-runtime/src/raw-html-policy.ts +2 -4
- package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
- package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
- package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
- package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
- package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
- package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
- package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
- package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
- package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
- package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
- package/packages/public-runtime/src/static-bundle.ts +5 -5
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +45 -9
- package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
- package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
- package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
- package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
- package/scripts/dev/file-browser.mjs +2 -2
- package/scripts/dev/file-writer.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/tenant-source-root.mjs +1 -1
- package/scripts/tot-dev.mjs +2 -2
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The neutral consent broker — one source of truth for consent purposes, default
|
|
3
|
+
* state, CMP updates, and destination eligibility across BOTH the browser and the
|
|
4
|
+
* server (ADR 0022 `receipt-consent-propagation`, ADR 0023 Q3
|
|
5
|
+
* `analytics-consent-required`; ext-policy-03).
|
|
6
|
+
*
|
|
7
|
+
* It sits on the closed capability model's {@link CONSENT_PURPOSES} vocabulary and
|
|
8
|
+
* produces the versioned {@link ConsentSnapshot} that everything downstream filters
|
|
9
|
+
* against — the browser destination loader, the server destination filter, and the
|
|
10
|
+
* commerce event envelope's {@link EventConsentSnapshot}. It also emits the default
|
|
11
|
+
* snapshot kernel-04's `consent.defaults` render phase inlines, so consent defaults
|
|
12
|
+
* exist before any vendor loader runs.
|
|
13
|
+
*
|
|
14
|
+
* Fail-closed by construction:
|
|
15
|
+
* - `essential` is the only always-eligible purpose and can never be gated off.
|
|
16
|
+
* - Every other purpose is denied until the TENANT approves it exists AND an
|
|
17
|
+
* affirmative decision (a native banner or a CMP) grants it — a manifest that
|
|
18
|
+
* merely declares a dependent purpose cannot self-approve.
|
|
19
|
+
* - An unknown or unapproved purpose can never make a non-essential destination
|
|
20
|
+
* eligible; unknown state blocks.
|
|
21
|
+
* - A CMP's disappearance reverts its grants to deny: removing a CMP defaults to
|
|
22
|
+
* deny, never a silent grant.
|
|
23
|
+
*
|
|
24
|
+
* The snapshot is a pure function of (approved purposes, effective decisions), in
|
|
25
|
+
* canonical purpose order, so a broker driven by identical inputs on the server and
|
|
26
|
+
* in the browser yields a byte-identical snapshot (including `snapshotId`) — one
|
|
27
|
+
* versioned snapshot, not two that must be kept in sync.
|
|
28
|
+
*/
|
|
29
|
+
import { CONSENT_PURPOSES, type ConsentPurpose } from "../integration-capabilities.js";
|
|
30
|
+
import { CONSENT_SNAPSHOT_SCHEMA_VERSION } from "./version.js";
|
|
31
|
+
import type { EventConsentSnapshot } from "./events.js";
|
|
32
|
+
|
|
33
|
+
/** The purpose that is always on and cannot be gated (charter floor). */
|
|
34
|
+
const ESSENTIAL: ConsentPurpose = "essential";
|
|
35
|
+
|
|
36
|
+
/** A tenant's approval decision for one purpose. */
|
|
37
|
+
export type ConsentLegalBasis = "consent" | "legitimate-interest";
|
|
38
|
+
|
|
39
|
+
/** One purpose the tenant has approved to exist, with its declared legal basis. */
|
|
40
|
+
export interface TenantPurposeApproval {
|
|
41
|
+
purpose: ConsentPurpose;
|
|
42
|
+
legalBasis: ConsentLegalBasis;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The tenant's consent policy: which non-essential purposes exist at all. A purpose
|
|
47
|
+
* absent from this list can never become eligible, whatever a manifest declares or a
|
|
48
|
+
* CMP reports. `essential` is always approved implicitly.
|
|
49
|
+
*/
|
|
50
|
+
export interface TenantConsentPolicy {
|
|
51
|
+
purposes: readonly TenantPurposeApproval[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A per-purpose consent outcome. */
|
|
55
|
+
export type ConsentDecision = "granted" | "denied";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The report a Consent Management Platform hands the broker. `decisions` is a
|
|
59
|
+
* partial map — a purpose the CMP does not mention stays at its prior state, never
|
|
60
|
+
* defaulting to granted.
|
|
61
|
+
*/
|
|
62
|
+
export interface CmpConsentReport {
|
|
63
|
+
cmpId: string;
|
|
64
|
+
decisions: Partial<Record<ConsentPurpose, ConsentDecision>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The CMP adapter contract: a neutral read seam over any concrete CMP. `read`
|
|
69
|
+
* returning `null` means the CMP is absent/not-yet-loaded — the broker treats that
|
|
70
|
+
* as no CMP grants, never as consent.
|
|
71
|
+
*/
|
|
72
|
+
export interface CmpAdapter {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
read(): CmpConsentReport | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A dependent destination whose runtime effect requires a purpose. Its `load` runs
|
|
79
|
+
* once, deferred until the destination is first eligible (so it can never run under
|
|
80
|
+
* the default-deny snapshot); `onWithdraw` fires when a previously-eligible
|
|
81
|
+
* destination loses eligibility so it can stop future events and push any supported
|
|
82
|
+
* vendor update; `onResume` fires if it later becomes eligible again, so a change
|
|
83
|
+
* updates rather than re-initializes it.
|
|
84
|
+
*/
|
|
85
|
+
export interface ConsentDestination {
|
|
86
|
+
id: string;
|
|
87
|
+
purpose: ConsentPurpose;
|
|
88
|
+
load(): void;
|
|
89
|
+
onWithdraw?(snapshot: ConsentSnapshot): void;
|
|
90
|
+
onResume?(snapshot: ConsentSnapshot): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The versioned, browser-safe consent snapshot — the single value client and server
|
|
95
|
+
* destinations filter against. `eligiblePurposes` is in canonical purpose order and
|
|
96
|
+
* always contains `essential`. `unknownState` is always `"block"` (fail closed).
|
|
97
|
+
*/
|
|
98
|
+
export interface ConsentSnapshot {
|
|
99
|
+
schemaVersion: typeof CONSENT_SNAPSHOT_SCHEMA_VERSION;
|
|
100
|
+
snapshotId: string;
|
|
101
|
+
eligiblePurposes: ConsentPurpose[];
|
|
102
|
+
unknownState: "block";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** FNV-1a 32-bit hex over an ASCII/UTF-16 string — a synchronous content digest, not a security boundary. */
|
|
106
|
+
function fnv1a32Hex(input: string): string {
|
|
107
|
+
let h = 0x811c9dc5;
|
|
108
|
+
for (let i = 0; i < input.length; i++) {
|
|
109
|
+
const code = input.charCodeAt(i);
|
|
110
|
+
h = Math.imul(h ^ (code & 0xff), 0x01000193);
|
|
111
|
+
h = Math.imul(h ^ ((code >> 8) & 0xff), 0x01000193);
|
|
112
|
+
}
|
|
113
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Content-addressed snapshot id: identical eligibility → identical id on client and server. */
|
|
117
|
+
function computeSnapshotId(eligible: readonly ConsentPurpose[]): string {
|
|
118
|
+
return `cs1-${fnv1a32Hex(`${CONSENT_SNAPSHOT_SCHEMA_VERSION}:${eligible.join(",")}`)}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Build a snapshot from an eligible set, forcing `essential` on and canonicalizing order. */
|
|
122
|
+
function makeSnapshot(eligible: Iterable<ConsentPurpose>): ConsentSnapshot {
|
|
123
|
+
const set = new Set<ConsentPurpose>(eligible);
|
|
124
|
+
set.add(ESSENTIAL);
|
|
125
|
+
const eligiblePurposes = CONSENT_PURPOSES.filter((p) => set.has(p));
|
|
126
|
+
return {
|
|
127
|
+
schemaVersion: CONSENT_SNAPSHOT_SCHEMA_VERSION,
|
|
128
|
+
snapshotId: computeSnapshotId(eligiblePurposes),
|
|
129
|
+
eligiblePurposes,
|
|
130
|
+
unknownState: "block",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The default consent snapshot kernel-04's `consent.defaults` phase inlines: only
|
|
136
|
+
* `essential` is eligible; every measurement purpose is denied until affirmatively
|
|
137
|
+
* granted. This is what precedes every vendor loader.
|
|
138
|
+
*/
|
|
139
|
+
export function defaultConsentSnapshot(): ConsentSnapshot {
|
|
140
|
+
return makeSnapshot([ESSENTIAL]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Whether a purpose is currently eligible. `essential` is always eligible; an
|
|
145
|
+
* unknown or unapproved-and-ungranted purpose is not — so this both permits
|
|
146
|
+
* essential destinations and blocks non-essential ones under unknown state.
|
|
147
|
+
*/
|
|
148
|
+
export function isPurposeEligible(snapshot: ConsentSnapshot, purpose: string): boolean {
|
|
149
|
+
return snapshot.eligiblePurposes.includes(purpose as ConsentPurpose);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Server destination filter: keep only the destinations whose required purpose is
|
|
154
|
+
* eligible under `snapshot`. The browser loader and the server share this predicate,
|
|
155
|
+
* so a purpose withdrawn client-side stops the matching server destination too.
|
|
156
|
+
*/
|
|
157
|
+
export function filterDestinationsByConsent<T extends { purpose: ConsentPurpose }>(
|
|
158
|
+
snapshot: ConsentSnapshot,
|
|
159
|
+
destinations: readonly T[],
|
|
160
|
+
): T[] {
|
|
161
|
+
return destinations.filter((d) => isPurposeEligible(snapshot, d.purpose));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Project a broker snapshot onto the commerce event envelope's consent reference. */
|
|
165
|
+
export function toEventConsentSnapshot(snapshot: ConsentSnapshot): EventConsentSnapshot {
|
|
166
|
+
return {
|
|
167
|
+
schemaVersion: snapshot.schemaVersion,
|
|
168
|
+
snapshotId: snapshot.snapshotId,
|
|
169
|
+
eligiblePurposes: [...snapshot.eligiblePurposes],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Serialize a snapshot for inlining/transport. */
|
|
174
|
+
export function serializeConsentSnapshot(snapshot: ConsentSnapshot): string {
|
|
175
|
+
return JSON.stringify(snapshot);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Parse a serialized snapshot fail-closed: an unknown schema version, a non-block
|
|
180
|
+
* unknown state, an unknown purpose, a missing `essential`, or an id that does not
|
|
181
|
+
* match the eligible set yields `null` — a caller cannot smuggle in a forged or
|
|
182
|
+
* tampered snapshot.
|
|
183
|
+
*/
|
|
184
|
+
export function parseConsentSnapshot(raw: unknown): ConsentSnapshot | null {
|
|
185
|
+
let obj: unknown = raw;
|
|
186
|
+
if (typeof raw === "string") {
|
|
187
|
+
try {
|
|
188
|
+
obj = JSON.parse(raw);
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (typeof obj !== "object" || obj === null) return null;
|
|
194
|
+
const s = obj as Record<string, unknown>;
|
|
195
|
+
if (s["schemaVersion"] !== CONSENT_SNAPSHOT_SCHEMA_VERSION) return null;
|
|
196
|
+
if (s["unknownState"] !== "block") return null;
|
|
197
|
+
if (typeof s["snapshotId"] !== "string") return null;
|
|
198
|
+
const eligibleRaw = s["eligiblePurposes"];
|
|
199
|
+
if (!Array.isArray(eligibleRaw)) return null;
|
|
200
|
+
const known = new Set<string>(CONSENT_PURPOSES);
|
|
201
|
+
for (const p of eligibleRaw) {
|
|
202
|
+
if (typeof p !== "string" || !known.has(p)) return null;
|
|
203
|
+
}
|
|
204
|
+
const eligible = CONSENT_PURPOSES.filter((p) => (eligibleRaw as string[]).includes(p));
|
|
205
|
+
if (!eligible.includes(ESSENTIAL)) return null;
|
|
206
|
+
const canonical = makeSnapshot(eligible);
|
|
207
|
+
if (canonical.snapshotId !== s["snapshotId"]) return null;
|
|
208
|
+
return canonical;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Listener invoked with the new snapshot whenever eligibility changes. */
|
|
212
|
+
export type ConsentListener = (snapshot: ConsentSnapshot) => void;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The neutral consent broker instance. ONE instance is the single source of truth
|
|
216
|
+
* for a page/request; CMP and native-banner updates mutate it in place (they never
|
|
217
|
+
* re-initialize it), so `snapshotId` advances while destinations keep their
|
|
218
|
+
* identity. Constructed identically on the server and in the browser, so both
|
|
219
|
+
* derive the same versioned snapshot.
|
|
220
|
+
*/
|
|
221
|
+
export class ConsentBroker {
|
|
222
|
+
private readonly approved = new Map<ConsentPurpose, ConsentLegalBasis>();
|
|
223
|
+
private readonly directDecisions = new Map<ConsentPurpose, ConsentDecision>();
|
|
224
|
+
private cmpDecisions = new Map<ConsentPurpose, ConsentDecision>();
|
|
225
|
+
private readonly destinations = new Map<string, ConsentDestination>();
|
|
226
|
+
private readonly loaded = new Set<string>();
|
|
227
|
+
private readonly active = new Set<string>();
|
|
228
|
+
private readonly listeners = new Set<ConsentListener>();
|
|
229
|
+
private snapshot: ConsentSnapshot;
|
|
230
|
+
|
|
231
|
+
constructor(policy: TenantConsentPolicy) {
|
|
232
|
+
for (const approval of policy.purposes) {
|
|
233
|
+
if (approval.purpose === ESSENTIAL) continue; // essential is implicit
|
|
234
|
+
this.approved.set(approval.purpose, approval.legalBasis);
|
|
235
|
+
}
|
|
236
|
+
this.snapshot = this.compute();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** The current snapshot — the value client and server destinations filter against. */
|
|
240
|
+
getSnapshot(): ConsentSnapshot {
|
|
241
|
+
return this.snapshot;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Whether a purpose is currently eligible under this broker's snapshot. */
|
|
245
|
+
isEligible(purpose: string): boolean {
|
|
246
|
+
return isPurposeEligible(this.snapshot, purpose);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The legal basis the tenant approved for a purpose, or `undefined` if unapproved. */
|
|
250
|
+
legalBasisFor(purpose: ConsentPurpose): ConsentLegalBasis | undefined {
|
|
251
|
+
return this.approved.get(purpose);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Apply a CMP report, or `null` when the CMP is removed/absent. A report merges the
|
|
256
|
+
* CMP layer; `null` clears it entirely, so any purpose that was eligible only via
|
|
257
|
+
* the CMP reverts to denied. Removing a CMP never grants. Reuses this instance.
|
|
258
|
+
*/
|
|
259
|
+
applyCmp(report: CmpConsentReport | null): ConsentSnapshot {
|
|
260
|
+
if (report === null) {
|
|
261
|
+
this.cmpDecisions = new Map();
|
|
262
|
+
} else {
|
|
263
|
+
for (const [purpose, decision] of Object.entries(report.decisions)) {
|
|
264
|
+
if (decision) this.cmpDecisions.set(purpose as ConsentPurpose, decision);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return this.update();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Apply an explicit decision layer (e.g. a native consent banner). A shopper's
|
|
272
|
+
* explicit choice overrides the CMP layer. Reuses this instance.
|
|
273
|
+
*/
|
|
274
|
+
setDecisions(decisions: Partial<Record<ConsentPurpose, ConsentDecision>>): ConsentSnapshot {
|
|
275
|
+
for (const [purpose, decision] of Object.entries(decisions)) {
|
|
276
|
+
if (decision) this.directDecisions.set(purpose as ConsentPurpose, decision);
|
|
277
|
+
}
|
|
278
|
+
return this.update();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Register a dependent destination. It loads immediately if already eligible,
|
|
283
|
+
* otherwise its `load` is deferred until it first becomes eligible — a destination
|
|
284
|
+
* registered under the default-deny snapshot does not run.
|
|
285
|
+
*/
|
|
286
|
+
registerDestination(destination: ConsentDestination): void {
|
|
287
|
+
this.destinations.set(destination.id, destination);
|
|
288
|
+
this.reconcileDestination(destination);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Whether a destination is currently active (loaded and still eligible) — its emit gate. */
|
|
292
|
+
isDestinationActive(id: string): boolean {
|
|
293
|
+
return this.active.has(id);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Subscribe to snapshot changes; returns an unsubscribe function. */
|
|
297
|
+
subscribe(listener: ConsentListener): () => void {
|
|
298
|
+
this.listeners.add(listener);
|
|
299
|
+
return () => this.listeners.delete(listener);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** The effective decision for one purpose under the current layers. */
|
|
303
|
+
private effective(purpose: ConsentPurpose): ConsentDecision {
|
|
304
|
+
if (purpose === ESSENTIAL) return "granted";
|
|
305
|
+
if (!this.approved.has(purpose)) return "denied"; // tenant never approved it exists
|
|
306
|
+
return this.directDecisions.get(purpose) ?? this.cmpDecisions.get(purpose) ?? "denied";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private compute(): ConsentSnapshot {
|
|
310
|
+
const eligible = CONSENT_PURPOSES.filter((p) => this.effective(p) === "granted");
|
|
311
|
+
return makeSnapshot(eligible);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private update(): ConsentSnapshot {
|
|
315
|
+
this.snapshot = this.compute();
|
|
316
|
+
for (const destination of this.destinations.values()) {
|
|
317
|
+
this.reconcileDestination(destination);
|
|
318
|
+
}
|
|
319
|
+
for (const listener of this.listeners) listener(this.snapshot);
|
|
320
|
+
return this.snapshot;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private reconcileDestination(destination: ConsentDestination): void {
|
|
324
|
+
const eligibleNow = isPurposeEligible(this.snapshot, destination.purpose);
|
|
325
|
+
if (eligibleNow && !this.loaded.has(destination.id)) {
|
|
326
|
+
this.loaded.add(destination.id);
|
|
327
|
+
this.active.add(destination.id);
|
|
328
|
+
destination.load(); // once, deferred until first eligible
|
|
329
|
+
} else if (eligibleNow && !this.active.has(destination.id)) {
|
|
330
|
+
this.active.add(destination.id);
|
|
331
|
+
destination.onResume?.(this.snapshot); // eligible again — update, do not re-init
|
|
332
|
+
} else if (!eligibleNow && this.active.has(destination.id)) {
|
|
333
|
+
this.active.delete(destination.id);
|
|
334
|
+
destination.onWithdraw?.(this.snapshot); // stop future events + vendor update
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CMP demand ledger — which Consent Management Platform adapters a merchant may
|
|
3
|
+
* select is DERIVED from demonstrated demand, never a hardcoded catalog (ADR 0014 P2
|
|
4
|
+
* `integration-catalog-consent-google-meta`, ADR 0025: "the first CMP adapter is
|
|
5
|
+
* selected from demonstrated demand").
|
|
6
|
+
*
|
|
7
|
+
* There is no built-in list of "supported" CMPs anywhere in this module. An adapter
|
|
8
|
+
* becomes SELECTABLE only once distinct tenants have demonstrated demand for it and
|
|
9
|
+
* that demand crosses a threshold; before then every CMP choice fails closed. So the
|
|
10
|
+
* first CMP Storefront adapts is whichever real demand first proves, not an engineering
|
|
11
|
+
* guess baked into code.
|
|
12
|
+
*
|
|
13
|
+
* Pure and edge-safe: a ledger of signals in, a derived selectable set out. Malformed
|
|
14
|
+
* signals never count toward demand (they cannot inflate a bogus adapter into being
|
|
15
|
+
* selectable).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** One recorded demand signal: a tenant asked for a named CMP adapter, once. */
|
|
19
|
+
export interface CmpDemandSignal {
|
|
20
|
+
/** The requested CMP adapter identifier (e.g. an opaque vendor slug). */
|
|
21
|
+
adapterId: string;
|
|
22
|
+
/** The tenant that demonstrated the demand — distinct tenants are what count. */
|
|
23
|
+
tenantPublicId: string;
|
|
24
|
+
/** ISO-8601 instant the demand was recorded. */
|
|
25
|
+
at: string;
|
|
26
|
+
/** Where the demand came from (a merchant request, a support ticket), for the record. */
|
|
27
|
+
source?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The accumulated demand across all tenants. */
|
|
31
|
+
export interface CmpDemandLedger {
|
|
32
|
+
signals: readonly CmpDemandSignal[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Distinct tenants that must demonstrate demand before an adapter is offered. A
|
|
37
|
+
* single request is not "demonstrated demand"; the default requires a real cohort.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_CMP_DEMAND_THRESHOLD = 3;
|
|
40
|
+
|
|
41
|
+
/** Per-adapter demand summary for the admin record and the selectable decision. */
|
|
42
|
+
export interface CmpAdapterDemand {
|
|
43
|
+
adapterId: string;
|
|
44
|
+
/** How many DISTINCT tenants requested it — the demand measure. */
|
|
45
|
+
distinctTenants: number;
|
|
46
|
+
firstRequestedAt: string;
|
|
47
|
+
lastRequestedAt: string;
|
|
48
|
+
/** True only when `distinctTenants >= threshold`: the merchant may select it. */
|
|
49
|
+
selectable: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A signal is well-formed only if it carries a non-empty adapter, tenant, and ISO instant. */
|
|
53
|
+
function isWellFormedSignal(signal: CmpDemandSignal): boolean {
|
|
54
|
+
return (
|
|
55
|
+
typeof signal.adapterId === "string" &&
|
|
56
|
+
signal.adapterId.length > 0 &&
|
|
57
|
+
typeof signal.tenantPublicId === "string" &&
|
|
58
|
+
signal.tenantPublicId.length > 0 &&
|
|
59
|
+
typeof signal.at === "string" &&
|
|
60
|
+
/\d{4}-\d{2}-\d{2}T/.test(signal.at) &&
|
|
61
|
+
!Number.isNaN(Date.parse(signal.at))
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Summarize a demand ledger into per-adapter demand, sorted by distinct-tenant demand
|
|
67
|
+
* (desc), then adapter id. Malformed signals and repeat requests from the same tenant
|
|
68
|
+
* for the same adapter never inflate the count.
|
|
69
|
+
*/
|
|
70
|
+
export function summarizeCmpDemand(
|
|
71
|
+
ledger: CmpDemandLedger,
|
|
72
|
+
threshold: number = DEFAULT_CMP_DEMAND_THRESHOLD,
|
|
73
|
+
): CmpAdapterDemand[] {
|
|
74
|
+
const byAdapter = new Map<string, { tenants: Set<string>; first: string; last: string }>();
|
|
75
|
+
for (const signal of ledger.signals) {
|
|
76
|
+
if (!isWellFormedSignal(signal)) continue;
|
|
77
|
+
const entry = byAdapter.get(signal.adapterId) ?? {
|
|
78
|
+
tenants: new Set<string>(),
|
|
79
|
+
first: signal.at,
|
|
80
|
+
last: signal.at,
|
|
81
|
+
};
|
|
82
|
+
entry.tenants.add(signal.tenantPublicId);
|
|
83
|
+
if (signal.at < entry.first) entry.first = signal.at;
|
|
84
|
+
if (signal.at > entry.last) entry.last = signal.at;
|
|
85
|
+
byAdapter.set(signal.adapterId, entry);
|
|
86
|
+
}
|
|
87
|
+
return [...byAdapter.entries()]
|
|
88
|
+
.map(([adapterId, entry]) => ({
|
|
89
|
+
adapterId,
|
|
90
|
+
distinctTenants: entry.tenants.size,
|
|
91
|
+
firstRequestedAt: entry.first,
|
|
92
|
+
lastRequestedAt: entry.last,
|
|
93
|
+
selectable: entry.tenants.size >= threshold,
|
|
94
|
+
}))
|
|
95
|
+
.sort((a, b) => b.distinctTenants - a.distinctTenants || a.adapterId.localeCompare(b.adapterId));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The adapter ids a merchant may currently select — the demonstrated-demand set. */
|
|
99
|
+
export function demonstratedCmpAdapters(
|
|
100
|
+
ledger: CmpDemandLedger,
|
|
101
|
+
threshold: number = DEFAULT_CMP_DEMAND_THRESHOLD,
|
|
102
|
+
): string[] {
|
|
103
|
+
return summarizeCmpDemand(ledger, threshold)
|
|
104
|
+
.filter((d) => d.selectable)
|
|
105
|
+
.map((d) => d.adapterId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Whether one adapter has crossed the demonstrated-demand threshold and is selectable. */
|
|
109
|
+
export function isCmpSelectable(
|
|
110
|
+
ledger: CmpDemandLedger,
|
|
111
|
+
adapterId: string,
|
|
112
|
+
threshold: number = DEFAULT_CMP_DEMAND_THRESHOLD,
|
|
113
|
+
): boolean {
|
|
114
|
+
return demonstratedCmpAdapters(ledger, threshold).includes(adapterId);
|
|
115
|
+
}
|