@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.
Files changed (363) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/env.d.ts +54 -5
  3. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  4. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  5. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  6. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  7. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  8. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  9. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  10. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  11. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  12. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  13. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  14. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  15. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  16. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  17. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  18. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  19. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  20. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  22. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  23. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  24. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  25. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  26. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  27. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  28. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  29. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  30. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  32. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  33. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  34. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  37. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  38. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  41. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  42. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  43. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  44. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  45. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  46. package/apps/storefront/src/components/Img.astro +1 -1
  47. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  48. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  49. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  50. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  51. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  52. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  53. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  54. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  55. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  56. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  57. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  59. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  60. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  61. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  62. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  63. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  64. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  65. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  66. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  67. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  68. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  69. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  70. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  71. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  72. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  73. package/apps/storefront/src/config/resolver.ts +4 -4
  74. package/apps/storefront/src/layouts/Layout.astro +100 -20
  75. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  76. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  77. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  78. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  79. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  80. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  81. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  82. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  83. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  84. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  87. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  89. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  90. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  91. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  92. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  93. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  94. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  95. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  96. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  97. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  98. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  99. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  100. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  104. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  105. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  106. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  109. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  110. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  111. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  112. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  113. package/apps/storefront/src/lib/blog/collection.ts +97 -10
  114. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  115. package/apps/storefront/src/lib/blog/provider.ts +57 -3
  116. package/apps/storefront/src/lib/blog/types.ts +9 -9
  117. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  119. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  120. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  121. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  122. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  123. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  124. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  126. package/apps/storefront/src/lib/content/index.ts +5 -5
  127. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  128. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  129. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  130. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  131. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  132. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  133. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  134. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  135. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  136. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  137. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  138. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  139. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  140. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  141. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  142. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  143. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  144. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  145. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  146. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  147. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  148. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  149. package/apps/storefront/src/lib/membership/model.ts +1 -2
  150. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  151. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  153. package/apps/storefront/src/lib/previewReload.ts +57 -1
  154. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  155. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  156. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  157. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  158. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  159. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  160. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  161. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  162. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  163. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  164. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  165. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  166. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  173. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  176. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  177. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  178. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  179. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  180. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  181. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  182. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  183. package/apps/storefront/src/lib/social/notify.ts +4 -4
  184. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  185. package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
  186. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  187. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  188. package/apps/storefront/src/middleware/index.ts +273 -51
  189. package/apps/storefront/src/pages/[...slug].astro +9 -4
  190. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  191. package/apps/storefront/src/pages/admin/select.astro +96 -0
  192. package/apps/storefront/src/pages/admin.astro +16 -2
  193. package/apps/storefront/src/pages/api/activity.ts +8 -8
  194. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  195. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  196. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  197. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  199. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  201. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  202. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  203. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  204. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  205. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  206. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  207. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  208. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  209. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  210. package/apps/storefront/src/pages/blog/index.astro +2 -2
  211. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  212. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  213. package/apps/storefront/src/pages/index.astro +10 -5
  214. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  215. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  216. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  217. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
  218. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
  219. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  220. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  221. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  222. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  223. package/apps/storefront/src/styles/admin.css +27 -1
  224. package/apps/storefront/tsconfig.json +1 -1
  225. package/package.json +1 -1
  226. package/packages/public-runtime/package.json +2 -1
  227. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  228. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  229. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  230. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  231. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  232. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  250. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  251. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  252. package/packages/public-runtime/src/activity/event.ts +6 -6
  253. package/packages/public-runtime/src/activity/index.ts +2 -2
  254. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  255. package/packages/public-runtime/src/candidate-index.ts +42 -8
  256. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  257. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  258. package/packages/public-runtime/src/checkout-style.ts +7 -7
  259. package/packages/public-runtime/src/checkout.ts +3 -3
  260. package/packages/public-runtime/src/chrome.ts +777 -0
  261. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  262. package/packages/public-runtime/src/csp.ts +99 -0
  263. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  264. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  265. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  266. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  267. package/packages/public-runtime/src/evidence.ts +288 -0
  268. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  269. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  270. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  271. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  272. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  273. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  274. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  275. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  276. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  277. package/packages/public-runtime/src/index.ts +16 -1
  278. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  279. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  280. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  281. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  282. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  283. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  284. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  285. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  286. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  287. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  288. package/packages/public-runtime/src/integration/csp.ts +105 -0
  289. package/packages/public-runtime/src/integration/digest.ts +43 -0
  290. package/packages/public-runtime/src/integration/environment.ts +228 -0
  291. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  292. package/packages/public-runtime/src/integration/events.ts +701 -0
  293. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  294. package/packages/public-runtime/src/integration/index.ts +90 -0
  295. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  296. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  297. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  298. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  299. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  300. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  301. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  302. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  303. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  305. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  306. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  307. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  308. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  309. package/packages/public-runtime/src/integration/observability.ts +451 -0
  310. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  311. package/packages/public-runtime/src/integration/phases.ts +182 -0
  312. package/packages/public-runtime/src/integration/plan.ts +112 -0
  313. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  314. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  315. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  316. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  317. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  318. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  319. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  320. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  321. package/packages/public-runtime/src/integration/result.ts +239 -0
  322. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  323. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  324. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  325. package/packages/public-runtime/src/integration/version.ts +92 -0
  326. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  327. package/packages/public-runtime/src/membership.ts +2 -2
  328. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  329. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  330. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  331. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  332. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  333. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  334. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  335. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  336. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  337. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  338. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  339. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  340. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  341. package/packages/public-runtime/src/static-bundle.ts +5 -5
  342. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  343. package/packages/public-runtime/src/tenant.ts +45 -9
  344. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  345. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  346. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  347. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  348. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  349. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  350. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  351. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  352. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  353. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  354. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  355. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  356. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  357. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  358. package/scripts/dev/file-browser.mjs +2 -2
  359. package/scripts/dev/file-writer.mjs +2 -2
  360. package/scripts/dev/integration-guardrails.mjs +498 -0
  361. package/scripts/dev/safe-path.mjs +2 -2
  362. package/scripts/dev/tenant-source-root.mjs +1 -1
  363. package/scripts/tot-dev.mjs +2 -2
@@ -0,0 +1,383 @@
1
+ /**
2
+ * The document-scoped bridge from platform-owned semantic action boundaries to the
3
+ * versioned commerce-event broker.
4
+ *
5
+ * This bridge deliberately has no DOM input. Its action boundary does not accept
6
+ * selectors, elements, labels, text, classes, forms, destinations, analytics commands,
7
+ * or arbitrary event data. Platform components call {@link activate} (or publish
8
+ * through the injected typed source) with one approved semantic name and an opaque
9
+ * logical identity. The existing {@link CommerceEventBroker} remains the only authority
10
+ * that stamps trusted context, validates the closed event contract, applies consent,
11
+ * deduplicates, and isolates subscriber failures.
12
+ */
13
+ import type { CommerceEventBroker } from "./commerce-broker.js";
14
+ import {
15
+ NAVIGATION_KINDS,
16
+ type ApprovedMarketingActionName,
17
+ type NavigationKind,
18
+ } from "./events.js";
19
+ import type { ContractViolation } from "./result.js";
20
+ import { resemblesManagedAnalyticsPii } from "./managed-analytics-value-safety.js";
21
+ import {
22
+ emitManagedActionActivated,
23
+ emitManagedPageView,
24
+ pageViewOwnerForNavigation,
25
+ type ManagedActionActivationInput,
26
+ type ManagedAnalyticsEmitResult,
27
+ type ManagedPageViewInput,
28
+ } from "./managed-analytics.js";
29
+
30
+ /** One explicit activation raised at a platform component's semantic handler. */
31
+ export interface PlatformManagedActionActivation
32
+ extends Omit<ManagedActionActivationInput, "name"> {
33
+ /** One of the program-approved v1 identifiers; labels and selectors are not accepted. */
34
+ name: ApprovedMarketingActionName;
35
+ }
36
+
37
+ /**
38
+ * Optional platform-owned activation bus. It is an injected typed seam, not a browser
39
+ * event or DOM-discovery surface. The returned function must remove this listener.
40
+ */
41
+ export interface PlatformManagedActionSource {
42
+ subscribe(listener: (activation: PlatformManagedActionActivation) => void): () => void;
43
+ }
44
+
45
+ export interface ManagedAnalyticsNavigationCommit extends ManagedPageViewInput {
46
+ kind: NavigationKind;
47
+ /** An aborted navigation never became a viewed resource. */
48
+ aborted?: boolean;
49
+ }
50
+
51
+ export type ManagedAnalyticsBridgeSkipReason =
52
+ | "already-initialized"
53
+ | "lifecycle-unchanged"
54
+ | "not-initialized"
55
+ | "not-page-view-owner"
56
+ | "disposed";
57
+
58
+ export interface ManagedAnalyticsBridgeSkipped {
59
+ ok: true;
60
+ emitted: false;
61
+ skipReason: ManagedAnalyticsBridgeSkipReason;
62
+ deliveries: [];
63
+ }
64
+
65
+ export type ManagedAnalyticsBridgeResult =
66
+ | ManagedAnalyticsEmitResult
67
+ | ManagedAnalyticsBridgeSkipped;
68
+
69
+ export interface ManagedAnalyticsEventBrokerBridgeOptions {
70
+ /** The existing versioned broker; this bridge never constructs a parallel bus. */
71
+ broker: CommerceEventBroker;
72
+ /** Optional typed source for platform-owned action handler activations. */
73
+ actionSource?: PlatformManagedActionSource;
74
+ }
75
+
76
+ type BridgeState = "idle" | "initializing" | "initialized" | "disposed";
77
+
78
+ function skipped(skipReason: ManagedAnalyticsBridgeSkipReason): ManagedAnalyticsBridgeSkipped {
79
+ return { ok: true, emitted: false, skipReason, deliveries: [] };
80
+ }
81
+
82
+ const PLATFORM_ACTION_FIELDS = ["logicalId", "name", "correlation", "sequence"] as const;
83
+ const DOCUMENT_PAGE_FIELDS = ["logicalId", "path", "correlation", "attribution", "sequence"] as const;
84
+ const NAVIGATION_FIELDS = [...DOCUMENT_PAGE_FIELDS, "kind", "aborted"] as const;
85
+
86
+ function failure(
87
+ path: string,
88
+ code: string,
89
+ message: string,
90
+ ): { ok: false; violations: ContractViolation[] } {
91
+ return { ok: false, violations: [{ path, code, message }] };
92
+ }
93
+
94
+ /** Validate only bridge-owned top-level shape and value canaries; the broker validates the envelope. */
95
+ function bridgeInputViolations(
96
+ input: unknown,
97
+ allowed: readonly string[],
98
+ label: string,
99
+ ): ContractViolation[] {
100
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
101
+ return [{ path: "(root)", code: "type", message: `${label} must be an object.` }];
102
+ }
103
+ const record = input as Record<string, unknown>;
104
+ if (Object.keys(record).some((key) => !allowed.includes(key))) {
105
+ return [{
106
+ path: "(root)",
107
+ code: "unknown_field",
108
+ message: `${label} contains a field outside its closed public contract.`,
109
+ }];
110
+ }
111
+
112
+ const violations: ContractViolation[] = [];
113
+ if (resemblesManagedAnalyticsPii(record["logicalId"])) {
114
+ violations.push({
115
+ path: "logicalId",
116
+ code: "pii_value",
117
+ message: "Logical ids must be opaque and cannot resemble customer PII.",
118
+ });
119
+ }
120
+ if (resemblesManagedAnalyticsPii(record["path"])) {
121
+ violations.push({
122
+ path: "path",
123
+ code: "pii_value",
124
+ message: "Page paths cannot contain values resembling customer PII.",
125
+ });
126
+ }
127
+ const correlation = record["correlation"];
128
+ if (
129
+ typeof correlation === "object" &&
130
+ correlation !== null &&
131
+ !Array.isArray(correlation) &&
132
+ resemblesManagedAnalyticsPii((correlation as Record<string, unknown>)["id"])
133
+ ) {
134
+ violations.push({
135
+ path: "correlation.id",
136
+ code: "pii_value",
137
+ message: "Correlation ids must be opaque and cannot resemble customer PII.",
138
+ });
139
+ }
140
+ return violations;
141
+ }
142
+
143
+ function invalidNavigationKind(): { ok: false; violations: ContractViolation[] } {
144
+ return {
145
+ ok: false,
146
+ violations: [
147
+ {
148
+ path: "kind",
149
+ code: "unknown_enum",
150
+ message: `Navigation kind must be one of: ${NAVIGATION_KINDS.join(", ")}.`,
151
+ },
152
+ ],
153
+ };
154
+ }
155
+
156
+ /**
157
+ * One bridge instance belongs to one traditional document.
158
+ *
159
+ * `initialize` emits the document-owned page view and binds the optional platform
160
+ * action source exactly once. Consent changes do not rebind or reinitialize it: the
161
+ * broker reads its current consent source at every later emission. A same-document
162
+ * route commit can emit its page view through {@link navigationCommitted}; document
163
+ * navigations remain owned by the destination document bootstrap, while hash moves
164
+ * and aborts are never page views. `dispose` is idempotent and terminal.
165
+ */
166
+ export class ManagedAnalyticsEventBrokerBridge {
167
+ private state: BridgeState = "idle";
168
+ private unsubscribeActions?: () => void;
169
+ private readonly broker: CommerceEventBroker;
170
+ private readonly actionSource?: PlatformManagedActionSource;
171
+
172
+ constructor(options: ManagedAnalyticsEventBrokerBridgeOptions) {
173
+ this.broker = options.broker;
174
+ this.actionSource = options.actionSource;
175
+ }
176
+
177
+ get isInitialized(): boolean {
178
+ return this.state === "initialized";
179
+ }
180
+
181
+ get isDisposed(): boolean {
182
+ return this.state === "disposed";
183
+ }
184
+
185
+ /**
186
+ * Bind this document once and emit its bootstrap-owned page view. Repeated calls —
187
+ * including a mistaken consent-change re-init — neither subscribe nor emit again.
188
+ */
189
+ initialize(page: ManagedPageViewInput): ManagedAnalyticsBridgeResult {
190
+ if (this.state === "disposed") return skipped("disposed");
191
+ if (this.state !== "idle") return skipped("already-initialized");
192
+
193
+ try {
194
+ const violations = bridgeInputViolations(page, DOCUMENT_PAGE_FIELDS, "Document page input");
195
+ if (violations.length > 0) return { ok: false, violations };
196
+ } catch {
197
+ return failure(
198
+ "(root)",
199
+ "input_access_failed",
200
+ "Document page input could not be read safely.",
201
+ );
202
+ }
203
+
204
+ this.state = "initializing";
205
+ if (this.actionSource) {
206
+ try {
207
+ const unsubscribe = this.actionSource.subscribe((activation) => {
208
+ try {
209
+ // The result is intentionally ignored: analytics never breaks the action.
210
+ this.activate(activation);
211
+ } catch {
212
+ // A hostile collaborator/proxy cannot escape the platform-owned listener.
213
+ }
214
+ });
215
+ if (typeof unsubscribe !== "function") throw new TypeError("invalid unsubscribe");
216
+ this.unsubscribeActions = unsubscribe;
217
+ } catch {
218
+ this.state = "idle";
219
+ return failure(
220
+ "actionSource",
221
+ "source_subscribe_failed",
222
+ "The platform action source could not be bound.",
223
+ );
224
+ }
225
+ }
226
+
227
+ try {
228
+ const result = emitManagedPageView(this.broker, page);
229
+ if (!result.ok) {
230
+ return this.rollbackInitialization() ?? result;
231
+ }
232
+ this.state = "initialized";
233
+ return result;
234
+ } catch {
235
+ return this.rollbackInitialization() ?? failure(
236
+ "initialize",
237
+ "bridge_initialization_failed",
238
+ "Managed analytics initialization failed safely.",
239
+ );
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Consent changes are intentionally an in-place no-op for bridge lifecycle. The
245
+ * broker consults its live consent snapshot on every future activation/navigation.
246
+ */
247
+ consentUpdated(): ManagedAnalyticsBridgeResult {
248
+ if (this.state === "disposed") return skipped("disposed");
249
+ if (this.state !== "initialized") return skipped("not-initialized");
250
+ return skipped("lifecycle-unchanged");
251
+ }
252
+
253
+ /** Emit an explicitly named platform action through the existing broker. */
254
+ activate(activation: PlatformManagedActionActivation): ManagedAnalyticsBridgeResult {
255
+ if (this.state === "disposed") return skipped("disposed");
256
+ if (this.state !== "initialized") return skipped("not-initialized");
257
+
258
+ let projected: ManagedActionActivationInput;
259
+ try {
260
+ const violations = bridgeInputViolations(
261
+ activation,
262
+ PLATFORM_ACTION_FIELDS,
263
+ "Platform action activation",
264
+ );
265
+ if (violations.length > 0) return { ok: false, violations };
266
+ projected = {
267
+ logicalId: activation.logicalId,
268
+ name: activation.name,
269
+ correlation: activation.correlation,
270
+ ...(activation.sequence !== undefined ? { sequence: activation.sequence } : {}),
271
+ };
272
+ } catch {
273
+ return failure(
274
+ "(root)",
275
+ "input_access_failed",
276
+ "Platform action activation could not be read safely.",
277
+ );
278
+ }
279
+
280
+ try {
281
+ return emitManagedActionActivated(this.broker, projected);
282
+ } catch {
283
+ return failure(
284
+ "activate",
285
+ "action_emit_failed",
286
+ "Managed action emission failed safely.",
287
+ );
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Emit the page view owned by a committed same-document route. Full-document loads
293
+ * are counted by the new document's `initialize`; hashes and aborts are not views.
294
+ */
295
+ navigationCommitted(
296
+ navigation: ManagedAnalyticsNavigationCommit,
297
+ ): ManagedAnalyticsBridgeResult {
298
+ if (this.state === "disposed") return skipped("disposed");
299
+ if (this.state !== "initialized") return skipped("not-initialized");
300
+
301
+ let page: ManagedPageViewInput;
302
+ let kind: NavigationKind;
303
+ let aborted: boolean | undefined;
304
+ try {
305
+ const violations = bridgeInputViolations(
306
+ navigation,
307
+ NAVIGATION_FIELDS,
308
+ "Navigation commit",
309
+ );
310
+ if (violations.length > 0) return { ok: false, violations };
311
+ if (!(NAVIGATION_KINDS as readonly unknown[]).includes(navigation.kind)) {
312
+ return invalidNavigationKind();
313
+ }
314
+ kind = navigation.kind;
315
+ aborted = navigation.aborted;
316
+ page = {
317
+ logicalId: navigation.logicalId,
318
+ path: navigation.path,
319
+ correlation: navigation.correlation,
320
+ ...(navigation.attribution ? { attribution: navigation.attribution } : {}),
321
+ ...(navigation.sequence !== undefined ? { sequence: navigation.sequence } : {}),
322
+ };
323
+ } catch {
324
+ return failure(
325
+ "(root)",
326
+ "input_access_failed",
327
+ "Navigation commit could not be read safely.",
328
+ );
329
+ }
330
+ if (pageViewOwnerForNavigation(kind, aborted) !== "navigation-commit") {
331
+ return skipped("not-page-view-owner");
332
+ }
333
+ try {
334
+ return emitManagedPageView(this.broker, page);
335
+ } catch {
336
+ return failure(
337
+ "navigation",
338
+ "page_emit_failed",
339
+ "Managed page emission failed safely.",
340
+ );
341
+ }
342
+ }
343
+
344
+ /** Remove the typed platform source listener once; disposal is terminal. */
345
+ dispose(): ManagedAnalyticsBridgeResult {
346
+ if (this.state === "disposed" && !this.unsubscribeActions) return skipped("disposed");
347
+ this.state = "disposed";
348
+ const unbindFailure = this.unbindActions();
349
+ return unbindFailure ?? skipped("lifecycle-unchanged");
350
+ }
351
+
352
+ private rollbackInitialization(): ManagedAnalyticsBridgeResult | null {
353
+ const unbindFailure = this.unbindActions();
354
+ // A listener whose removal could not be proven remains owned by this bridge.
355
+ // Stay terminal so a corrected initialize cannot stack another listener; a
356
+ // later dispose may retry the retained teardown.
357
+ this.state = unbindFailure ? "disposed" : "idle";
358
+ return unbindFailure;
359
+ }
360
+
361
+ private unbindActions(): ManagedAnalyticsBridgeResult | null {
362
+ const unsubscribe = this.unsubscribeActions;
363
+ if (!unsubscribe) return null;
364
+ try {
365
+ unsubscribe();
366
+ this.unsubscribeActions = undefined;
367
+ return null;
368
+ } catch {
369
+ return failure(
370
+ "actionSource",
371
+ "source_unsubscribe_failed",
372
+ "The platform action source could not be unbound cleanly.",
373
+ );
374
+ }
375
+ }
376
+ }
377
+
378
+ /** Convenience factory for hosts that prefer a function over `new`. */
379
+ export function createManagedAnalyticsEventBrokerBridge(
380
+ options: ManagedAnalyticsEventBrokerBridgeOptions,
381
+ ): ManagedAnalyticsEventBrokerBridge {
382
+ return new ManagedAnalyticsEventBrokerBridge(options);
383
+ }
@@ -0,0 +1,14 @@
1
+ /** Shared PII-value canary for managed browser events and their diagnostics. */
2
+ import { MANAGED_ANALYTICS_PII_VALUE_PATTERN } from "./managed-analytics-api-v1-safety.mjs";
3
+
4
+ const MANAGED_ANALYTICS_PII_VALUE = new RegExp(MANAGED_ANALYTICS_PII_VALUE_PATTERN);
5
+
6
+ /** True when a public string resembles an email, SSN, payment card, or phone number. */
7
+ export function resemblesManagedAnalyticsPii(value: unknown): boolean {
8
+ return typeof value === "string" && MANAGED_ANALYTICS_PII_VALUE.test(value);
9
+ }
10
+
11
+ /** Defense-in-depth for identifiers copied into structured broker observations. */
12
+ export function safeManagedAnalyticsDiagnosticId(value: string): string {
13
+ return resemblesManagedAnalyticsPii(value) ? "[redacted]" : value;
14
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Storefront-managed analytics semantics.
3
+ *
4
+ * This module is deliberately DOM-free. Platform components call the named emit
5
+ * functions at the semantic action boundary; the runtime never discovers actions by
6
+ * inspecting selectors, text, classes, forms, globals, or a dataLayer. Tenant and
7
+ * environment are stamped by CommerceEventBroker's trusted construction context.
8
+ */
9
+ import {
10
+ reqArray,
11
+ reqLiteral,
12
+ reqObject,
13
+ reqString,
14
+ rejectUnknownKeys,
15
+ Violations,
16
+ type ContractResult,
17
+ } from "./result.js";
18
+ import { findForbiddenFields } from "./secrets.js";
19
+ import type { CommerceEmitResult, CommerceEventBroker } from "./commerce-broker.js";
20
+ import {
21
+ APPROVED_MARKETING_ACTION_NAMES,
22
+ MARKETING_ACTION_NAME_PATTERN,
23
+ NAVIGATION_KINDS,
24
+ type ApprovedMarketingActionName,
25
+ type EventCorrelation,
26
+ type NavigationKind,
27
+ } from "./events.js";
28
+
29
+ export const TENANT_ACTION_REGISTRY_SCHEMA_VERSION = 1 as const;
30
+
31
+ /**
32
+ * Stable action identifiers are authored data, not labels. The `_mktg` suffix keeps
33
+ * marketing-program actions in a distinct namespace while allowing each tenant to
34
+ * register only the actions it actually renders.
35
+ */
36
+ export const TENANT_MARKETING_ACTION_NAME_PATTERN = MARKETING_ACTION_NAME_PATTERN;
37
+
38
+ export interface TenantAnalyticsAction {
39
+ name: ApprovedMarketingActionName;
40
+ }
41
+
42
+ /** The explicit, server-loaded action allowlist for one tenant. */
43
+ export interface TenantActionRegistry {
44
+ schemaVersion: typeof TENANT_ACTION_REGISTRY_SCHEMA_VERSION;
45
+ actions: TenantAnalyticsAction[];
46
+ }
47
+
48
+ /**
49
+ * Validate an authored tenant registry fail-closed. Action declarations carry only a
50
+ * semantic name: DOM binding details and free-form payloads are not part of the
51
+ * contract, so they are rejected as unknown fields.
52
+ */
53
+ export function validateTenantActionRegistry(input: unknown): ContractResult<TenantActionRegistry> {
54
+ const v = new Violations();
55
+ const obj = reqObject(input, v, "");
56
+ if (!obj) return { ok: false, violations: v.items };
57
+ rejectUnknownKeys(obj, ["schemaVersion", "actions"], v, "");
58
+ reqLiteral(obj, "schemaVersion", TENANT_ACTION_REGISTRY_SCHEMA_VERSION, v, "");
59
+
60
+ const rawActions = reqArray(obj, "actions", v, "");
61
+ const actions: TenantAnalyticsAction[] = [];
62
+ if (rawActions) {
63
+ rawActions.forEach((raw, index) => {
64
+ const path = `actions[${index}]`;
65
+ const action = reqObject(raw, v, path);
66
+ if (!action) return;
67
+ rejectUnknownKeys(action, ["name"], v, path);
68
+ const name = reqString(action, "name", v, path);
69
+ if (name === undefined) return;
70
+ if (!TENANT_MARKETING_ACTION_NAME_PATTERN.test(name)) {
71
+ v.add(
72
+ `${path}.name`,
73
+ "format",
74
+ "Action name must be lowercase snake_case, end in _mktg, and contain no free-form text.",
75
+ );
76
+ } else if (!(APPROVED_MARKETING_ACTION_NAMES as readonly string[]).includes(name)) {
77
+ v.add(
78
+ `${path}.name`,
79
+ "unknown_enum",
80
+ "Action name is not in the approved v1 marketing action allowlist.",
81
+ );
82
+ }
83
+ actions.push({ name: name as ApprovedMarketingActionName });
84
+ });
85
+ const seen = new Set<string>();
86
+ actions.forEach((action, index) => {
87
+ if (seen.has(action.name)) {
88
+ v.add(
89
+ `actions[${index}].name`,
90
+ "duplicate",
91
+ "Duplicate action name; duplicates are rejected.",
92
+ );
93
+ }
94
+ seen.add(action.name);
95
+ });
96
+ }
97
+
98
+ for (const violation of findForbiddenFields(obj)) v.items.push(violation);
99
+ if (!v.ok) return { ok: false, violations: v.items };
100
+ return {
101
+ ok: true,
102
+ value: {
103
+ schemaVersion: TENANT_ACTION_REGISTRY_SCHEMA_VERSION,
104
+ actions,
105
+ },
106
+ };
107
+ }
108
+
109
+ /** A minimized attribution object; raw URLs never cross the analytics contract. */
110
+ export interface ManagedPageAttribution {
111
+ referrer?: { origin: string };
112
+ utm?: {
113
+ source?: string;
114
+ medium?: string;
115
+ campaign?: string;
116
+ term?: string;
117
+ content?: string;
118
+ };
119
+ }
120
+
121
+ export interface AttributionSourceInput {
122
+ /** Explicit renderer/runtime input. This module never reads document.referrer. */
123
+ referrerUrl?: string | null;
124
+ /** Explicit route URL used only to read allowlisted UTM keys. */
125
+ routeUrl?: string | null;
126
+ }
127
+
128
+ const UTM_QUERY_FIELDS = [
129
+ ["utm_source", "source"],
130
+ ["utm_medium", "medium"],
131
+ ["utm_campaign", "campaign"],
132
+ ["utm_term", "term"],
133
+ ["utm_content", "content"],
134
+ ] as const;
135
+ const SAFE_CAMPAIGN_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/;
136
+ const PROTECTED_CAMPAIGN_TOKEN =
137
+ /(?:@|\d{7,}|(?:^|[._~-])(?:bearer|password|secret|token|authorization|preview)(?:$|[._~-]))/i;
138
+
139
+ function safeCampaignToken(value: string): string | undefined {
140
+ return SAFE_CAMPAIGN_TOKEN.test(value) && !PROTECTED_CAMPAIGN_TOKEN.test(value)
141
+ ? value
142
+ : undefined;
143
+ }
144
+
145
+ /**
146
+ * Reduce explicit URL inputs to an origin-only referrer and the five standard UTM
147
+ * tokens. Query keys outside the UTM allowlist, duplicate keys, unsafe free-form
148
+ * values, credentials, paths, and fragments are discarded. No browser global is read.
149
+ */
150
+ export function minimizePageAttribution(input: AttributionSourceInput): ManagedPageAttribution | undefined {
151
+ const result: ManagedPageAttribution = {};
152
+
153
+ if (input.referrerUrl) {
154
+ try {
155
+ const referrer = new URL(input.referrerUrl);
156
+ if (
157
+ ["http:", "https:"].includes(referrer.protocol) &&
158
+ referrer.username === "" &&
159
+ referrer.password === ""
160
+ ) {
161
+ result.referrer = { origin: referrer.origin };
162
+ }
163
+ } catch {
164
+ // Invalid referrer input is omitted, never forwarded raw.
165
+ }
166
+ }
167
+
168
+ if (input.routeUrl) {
169
+ try {
170
+ const route = new URL(input.routeUrl, "https://route.invalid");
171
+ const utm: NonNullable<ManagedPageAttribution["utm"]> = {};
172
+ for (const [queryName, field] of UTM_QUERY_FIELDS) {
173
+ const values = route.searchParams.getAll(queryName);
174
+ if (values.length !== 1) continue;
175
+ const value = safeCampaignToken(values[0] ?? "");
176
+ if (value !== undefined) utm[field] = value;
177
+ }
178
+ if (Object.keys(utm).length > 0) result.utm = utm;
179
+ } catch {
180
+ // Invalid route input contributes no attribution.
181
+ }
182
+ }
183
+
184
+ return result.referrer || result.utm ? result : undefined;
185
+ }
186
+
187
+ export type ManagedAnalyticsEmitResult =
188
+ | CommerceEmitResult
189
+ | {
190
+ ok: true;
191
+ emitted: false;
192
+ skipReason: "consent-ineligible";
193
+ deliveries: [];
194
+ };
195
+
196
+ function consentIneligible(): ManagedAnalyticsEmitResult {
197
+ return { ok: true, emitted: false, skipReason: "consent-ineligible", deliveries: [] };
198
+ }
199
+
200
+ export interface ManagedPageViewInput {
201
+ logicalId: string;
202
+ path: string;
203
+ correlation: EventCorrelation;
204
+ attribution?: ManagedPageAttribution;
205
+ sequence?: number;
206
+ }
207
+
208
+ /**
209
+ * Emit one page view at a semantic document/route commit. The path and optional
210
+ * minimized attribution are the complete data allowlist; title and DOM state are not
211
+ * accepted by this managed seam.
212
+ */
213
+ export function emitManagedPageView(
214
+ broker: CommerceEventBroker,
215
+ input: ManagedPageViewInput,
216
+ ): ManagedAnalyticsEmitResult {
217
+ if (!broker.isConsentEligible("analytics")) return consentIneligible();
218
+ return broker.emit({
219
+ type: "storefront.page.viewed.v2",
220
+ logicalId: input.logicalId,
221
+ subject: "page",
222
+ correlation: input.correlation,
223
+ data: {
224
+ path: input.path,
225
+ ...(input.attribution ? { attribution: input.attribution } : {}),
226
+ },
227
+ ...(input.sequence !== undefined ? { sequence: input.sequence } : {}),
228
+ });
229
+ }
230
+
231
+ export interface ManagedActionActivationInput {
232
+ logicalId: string;
233
+ name: string;
234
+ correlation: EventCorrelation;
235
+ sequence?: number;
236
+ }
237
+
238
+ /**
239
+ * Emit one explicitly registered semantic action. The caller supplies no tenant,
240
+ * environment, selector, label, destination, or arbitrary properties; the emitted
241
+ * data object is exactly `{ name }`.
242
+ */
243
+ export function emitManagedActionActivated(
244
+ broker: CommerceEventBroker,
245
+ input: ManagedActionActivationInput,
246
+ ): ManagedAnalyticsEmitResult {
247
+ if (!broker.isConsentEligible("analytics")) return consentIneligible();
248
+ return broker.emit({
249
+ type: "storefront.action.activated.v1",
250
+ logicalId: input.logicalId,
251
+ subject: "action",
252
+ correlation: input.correlation,
253
+ data: { name: input.name },
254
+ ...(input.sequence !== undefined ? { sequence: input.sequence } : {}),
255
+ });
256
+ }
257
+
258
+ /** Where the page-view paired with a completed navigation is emitted. */
259
+ export type NavigationPageViewOwner = "document-bootstrap" | "navigation-commit" | "none";
260
+
261
+ /**
262
+ * Page/navigation compatibility policy:
263
+ * - a full document navigation is counted once by the destination document bootstrap;
264
+ * - a committed same-document back/forward or redirect is counted at route commit;
265
+ * - a hash move emits the navigation lifecycle event but is not a new page view;
266
+ * - an aborted navigation emits neither event.
267
+ */
268
+ export function pageViewOwnerForNavigation(
269
+ kind: NavigationKind,
270
+ aborted = false,
271
+ ): NavigationPageViewOwner {
272
+ if (aborted) return "none";
273
+ if (!(NAVIGATION_KINDS as readonly unknown[]).includes(kind)) return "none";
274
+ if (kind === "hash") return "none";
275
+ if (kind === "document") return "document-bootstrap";
276
+ return "navigation-commit";
277
+ }