@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,701 @@
1
+ /**
2
+ * The canonical semantic commerce event contract — the shared spine for dual
3
+ * client/server measurement (ADR 0014 M3 `integration-events-dual-delivery`,
4
+ * M2 `integration-commerce-data-minimized`, M1 `integration-checkout-provider-specific`).
5
+ *
6
+ * One contract feeds two delivery channels. Every event is a CloudEvents 1.0
7
+ * envelope with a small server-owned storefront extension:
8
+ *
9
+ * - The envelope is SEMANTIC and VERSIONED (`storefront.<domain>.<action>.vN`),
10
+ * never DOM-selector based. Unknown or unreleased types fail closed.
11
+ * - `id` is the OPAQUE LOGICAL EVENT IDENTITY: the same logical action delivered
12
+ * to both a browser destination and a server destination carries one `id`, so
13
+ * downstream vendors deduplicate on it (`eventDedupeKey`). Replays and late
14
+ * duplicates collapse to that key.
15
+ * - `correlation.id` is the attribution THREAD that spans a browser
16
+ * `checkout.handoff` and the later server-authoritative `order.*` — distinct
17
+ * logical events, one bounded-lifetime correlation.
18
+ * - `storefront.tenant`, `storefront.environment`, and `storefront.siteSurface`
19
+ * are SERVER-OWNED: stamped from trusted execution context, never read from a
20
+ * caller-supplied payload. `validateCommerceEventInContext` rejects any event
21
+ * whose claimed tenant/environment/surface disagrees with the trusted context,
22
+ * so tenant/env cannot be caller-forged.
23
+ * - `storefront.emitter` names the delivery channel of THIS instance. A
24
+ * verified-provider (server-authoritative) event type — `order.created`,
25
+ * `order.fulfilled` — cannot be emitted by the browser: a browser-emitted
26
+ * conversion is rejected (`forged_authority`), so generic browser code cannot
27
+ * invent a purchase.
28
+ * - Payloads are anonymous and field-allowlisted per event type: no customer
29
+ * identity, address, payment data, identity result, preview token, signed-cart
30
+ * material, or nonce. Data keys outside a type's catalog are rejected.
31
+ *
32
+ * The per-field owner/purpose/sensitivity catalog and retention classes live in
33
+ * `./events-catalog.ts`; the human-readable contract is
34
+ * `docs/architecture/commerce-event-contract.md`. The reserved completed-
35
+ * navigation event (charter invariant 9) shares the same envelope roots and ships
36
+ * before any client router.
37
+ */
38
+ import type { ContractResult } from "./result.js";
39
+ import {
40
+ Violations,
41
+ reqObject,
42
+ childObject,
43
+ reqEnum,
44
+ reqLiteral,
45
+ reqString,
46
+ reqInt,
47
+ reqStringArray,
48
+ reqIsoTimestamp,
49
+ rejectUnknownKeys,
50
+ joinPath,
51
+ } from "./result.js";
52
+ import {
53
+ COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION,
54
+ CONSENT_SNAPSHOT_SCHEMA_VERSION,
55
+ } from "./version.js";
56
+ import { INTEGRATION_ENVIRONMENTS, INTEGRATION_SITE_SURFACES } from "./environment.js";
57
+ import type { IntegrationEnvironment, IntegrationSiteSurface } from "./environment.js";
58
+ import { findForbiddenFields } from "./secrets.js";
59
+ import { resemblesManagedAnalyticsPii } from "./managed-analytics-value-safety.js";
60
+
61
+ /**
62
+ * The reserved commerce event taxonomy. Every accepted type is here; an unknown
63
+ * type fails closed (`unknown_enum`). New behavior is a NEW `.vN` type or a new
64
+ * name — a type is never silently reinterpreted.
65
+ */
66
+ export const COMMERCE_EVENT_TYPES = [
67
+ "storefront.page.viewed.v1",
68
+ "storefront.page.viewed.v2",
69
+ "storefront.action.activated.v1",
70
+ "storefront.product.viewed.v1",
71
+ "storefront.cart.item_added.v1",
72
+ "storefront.cart.item_removed.v1",
73
+ "storefront.cart.updated.v1",
74
+ "storefront.checkout.handoff.v1",
75
+ "storefront.purchase.completed.v1",
76
+ "storefront.consent.changed.v1",
77
+ "storefront.order.created.v1",
78
+ "storefront.order.fulfilled.v1",
79
+ ] as const;
80
+ export type CommerceEventType = (typeof COMMERCE_EVENT_TYPES)[number];
81
+
82
+ /**
83
+ * The browser-deliverable, consent-gated post-purchase signal a sandboxed
84
+ * third-party subscriber reacts to (post-purchase NPS / review / loyalty /
85
+ * referral widgets). It is NOT the authoritative verified order —
86
+ * `storefront.order.created.v1` / `.fulfilled.v1` stay `verified-provider` and are
87
+ * broker-refused for the browser (`forged_authority`), and the financial record
88
+ * lives only on the server. This is an ANONYMOUS, minimized (M2), analytics-gated
89
+ * signal that *a purchase completed*, carrying no order id, customer identity,
90
+ * payment data, or signed-cart material — safe to fan out to a browser subscriber
91
+ * under analytics consent.
92
+ */
93
+ export const PURCHASE_COMPLETED_EVENT_TYPE = "storefront.purchase.completed.v1" as const;
94
+
95
+ /**
96
+ * Forward-declared types whose NAMES are reserved but whose semantics are not yet
97
+ * stable. They require provider truth (M1) before they can be emitted, so an
98
+ * instance of one is rejected `reserved_unavailable` rather than silently
99
+ * accepted or confused with an unknown name.
100
+ */
101
+ export const FORWARD_DECLARED_COMMERCE_EVENT_TYPES = [
102
+ "storefront.order.cancelled.v1",
103
+ "storefront.order.refunded.v1",
104
+ "storefront.checkout.completed.v1",
105
+ ] as const;
106
+ export type ForwardDeclaredCommerceEventType =
107
+ (typeof FORWARD_DECLARED_COMMERCE_EVENT_TYPES)[number];
108
+
109
+ /** The reserved completed-navigation event that must ship before any client router. */
110
+ export const NAVIGATION_COMPLETED_EVENT_TYPE = "storefront.navigation.completed.v1" as const;
111
+
112
+ export const NAVIGATION_KINDS = ["document", "hash", "back-forward", "redirect"] as const;
113
+ export type NavigationKind = (typeof NAVIGATION_KINDS)[number];
114
+
115
+ /** Stable semantic marketing-action namespace; action labels and DOM bindings never qualify. */
116
+ export const MARKETING_ACTION_NAME_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*_mktg$/;
117
+
118
+ /** The program-approved v1 marketing action identifiers. */
119
+ export const APPROVED_MARKETING_ACTION_NAMES = [
120
+ "user_clicked_contact_sales_mktg",
121
+ "user_clicked_create_account_mktg",
122
+ "user_clicked_login_mktg",
123
+ "user_clicked_contact_support_consumer_mktg",
124
+ "user_clicked_contact_support_partner_mktg",
125
+ ] as const;
126
+ export type ApprovedMarketingActionName = (typeof APPROVED_MARKETING_ACTION_NAMES)[number];
127
+
128
+ /**
129
+ * Which channel is AUTHORITATIVE for a given event type. `browser` events
130
+ * originate in the document (a server destination is a mirror). `verified-provider`
131
+ * events are authored only from a verified provider webhook and can never be
132
+ * emitted by the browser.
133
+ */
134
+ export type EventSourceOfTruth = "browser" | "verified-provider";
135
+ export const EVENT_SOURCE_OF_TRUTH: Record<CommerceEventType, EventSourceOfTruth> = {
136
+ "storefront.page.viewed.v1": "browser",
137
+ "storefront.page.viewed.v2": "browser",
138
+ "storefront.action.activated.v1": "browser",
139
+ "storefront.product.viewed.v1": "browser",
140
+ "storefront.cart.item_added.v1": "browser",
141
+ "storefront.cart.item_removed.v1": "browser",
142
+ "storefront.cart.updated.v1": "browser",
143
+ "storefront.checkout.handoff.v1": "browser",
144
+ "storefront.purchase.completed.v1": "browser",
145
+ "storefront.consent.changed.v1": "browser",
146
+ "storefront.order.created.v1": "verified-provider",
147
+ "storefront.order.fulfilled.v1": "verified-provider",
148
+ };
149
+
150
+ /**
151
+ * Whether a released type can be delivered to a browser subscriber — its
152
+ * source of truth is the browser. A `verified-provider` type (a conversion) is
153
+ * never browser-deliverable: the broker refuses to emit it (`forged_authority`)
154
+ * and no browser path carries it to a sandbox subscriber. This is the check a
155
+ * subscription-wiring layer runs over a manifest's declared `permissions.events`
156
+ * before it wires a delivery, so an impossible (verified-provider), unknown, or
157
+ * forward-declared event is rejected rather than silently never firing.
158
+ */
159
+ export function isBrowserDeliverableEventType(type: string): type is CommerceEventType {
160
+ return (
161
+ (COMMERCE_EVENT_TYPES as readonly string[]).includes(type) &&
162
+ EVENT_SOURCE_OF_TRUTH[type as CommerceEventType] === "browser"
163
+ );
164
+ }
165
+
166
+ /** The delivery channel that emitted a specific envelope instance. */
167
+ export const EVENT_EMITTERS = ["browser", "server"] as const;
168
+ export type EventEmitter = (typeof EVENT_EMITTERS)[number];
169
+
170
+ /**
171
+ * The server-owned identity block. `tenant`, `environment`, and `siteSurface` are
172
+ * stamped from trusted execution context (the renderer for a browser emitter, the
173
+ * verified webhook receiver for a server emitter) — never from caller input.
174
+ * `sequence` is an optional per-emitter monotonic counter for out-of-order
175
+ * detection independent of wall-clock `time`.
176
+ */
177
+ export interface CommerceEventContext {
178
+ schemaVersion: typeof COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION;
179
+ tenant: string;
180
+ environment: IntegrationEnvironment;
181
+ siteSurface: IntegrationSiteSurface;
182
+ emitter: EventEmitter;
183
+ sequence?: number;
184
+ }
185
+
186
+ /** The base storefront context the navigation lifecycle event carries. */
187
+ export interface StorefrontEventContext {
188
+ schemaVersion: typeof COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION;
189
+ environment: IntegrationEnvironment;
190
+ siteSurface: IntegrationSiteSurface;
191
+ }
192
+
193
+ /** The consent snapshot a browser event was filtered against (a reference, not raw consent state). */
194
+ export interface EventConsentSnapshot {
195
+ schemaVersion: typeof CONSENT_SNAPSHOT_SCHEMA_VERSION;
196
+ snapshotId: string;
197
+ eligiblePurposes: string[];
198
+ }
199
+
200
+ /** The bounded-lifetime attribution thread that spans handoff and verified order events. */
201
+ export interface EventCorrelation {
202
+ id: string;
203
+ expiresAt: string;
204
+ }
205
+
206
+ export interface CommerceEvent {
207
+ specversion: "1.0";
208
+ id: string;
209
+ source: string;
210
+ type: CommerceEventType;
211
+ subject: string;
212
+ time: string;
213
+ datacontenttype: "application/json";
214
+ dataschema?: string;
215
+ storefront: CommerceEventContext;
216
+ correlation: EventCorrelation;
217
+ consent: EventConsentSnapshot;
218
+ data: Record<string, unknown>;
219
+ }
220
+
221
+ export interface NavigationCompletedEvent {
222
+ specversion: "1.0";
223
+ id: string;
224
+ source: string;
225
+ type: typeof NAVIGATION_COMPLETED_EVENT_TYPE;
226
+ time: string;
227
+ storefront: StorefrontEventContext;
228
+ data: { from: string; to: string; kind: NavigationKind };
229
+ }
230
+
231
+ /** The trusted execution context a submitted event's server-owned fields must match. */
232
+ export interface TrustedEventContext {
233
+ tenant: string;
234
+ environment: IntegrationEnvironment;
235
+ siteSurface: IntegrationSiteSurface;
236
+ }
237
+
238
+ /**
239
+ * The one canonical dedupe/idempotency key for a logical event: its `id`. Both the
240
+ * browser and the server delivery of a single logical action carry the same `id`,
241
+ * so a consumer that keys on this value processes each logical event exactly once
242
+ * regardless of channel, retry, or replay.
243
+ */
244
+ export function eventDedupeKey(event: Pick<CommerceEvent, "id">): string {
245
+ return event.id;
246
+ }
247
+
248
+ /** Allowed data keys per event type. Data is CLOSED: an unlisted key is rejected. */
249
+ export const COMMERCE_EVENT_DATA_FIELDS: Record<CommerceEventType, readonly string[]> = {
250
+ "storefront.page.viewed.v1": ["path", "title"],
251
+ "storefront.page.viewed.v2": ["path", "attribution"],
252
+ "storefront.action.activated.v1": ["name"],
253
+ "storefront.product.viewed.v1": ["productId", "variantId", "listId", "position", "currency", "unitPrice"],
254
+ "storefront.cart.item_added.v1": ["productId", "variantId", "quantity", "unitPrice", "currency"],
255
+ "storefront.cart.item_removed.v1": ["productId", "variantId", "quantity", "unitPrice", "currency"],
256
+ "storefront.cart.updated.v1": ["currency", "value", "itemCount", "items"],
257
+ "storefront.checkout.handoff.v1": ["currency", "value", "itemCount", "items"],
258
+ "storefront.purchase.completed.v1": ["currency", "value", "itemCount", "items"],
259
+ "storefront.consent.changed.v1": ["eligiblePurposes", "previousPurposes"],
260
+ "storefront.order.created.v1": ["orderId", "currency", "value", "itemCount", "items"],
261
+ "storefront.order.fulfilled.v1": ["orderId", "currency", "value", "itemCount", "items"],
262
+ };
263
+
264
+ /** Allowed keys on a line item within a cart/handoff/order `items[]` array. */
265
+ export const COMMERCE_EVENT_LINE_ITEM_FIELDS = [
266
+ "productId",
267
+ "variantId",
268
+ "quantity",
269
+ "unitPrice",
270
+ ] as const;
271
+
272
+ /** Key tokens that would carry customer identity or payment data (ADR 0014 M2). */
273
+ const PII_TOKENS = new Set([
274
+ "email",
275
+ "phone",
276
+ "name",
277
+ "firstname",
278
+ "lastname",
279
+ "address",
280
+ "dob",
281
+ "birthdate",
282
+ "ssn",
283
+ "card",
284
+ "cardnumber",
285
+ "pan",
286
+ "cvv",
287
+ "cvc",
288
+ "payment",
289
+ ]);
290
+
291
+ /** A non-negative decimal money string, e.g. `"42.50"` — never a float, never a locale form. */
292
+ const DECIMAL_STRING = /^\d+(\.\d+)?$/;
293
+
294
+ function findPiiFields(value: unknown, path: string, out: { path: string; code: string; message: string }[]): void {
295
+ if (Array.isArray(value)) {
296
+ value.forEach((item, i) => findPiiFields(item, `${path}[${i}]`, out));
297
+ return;
298
+ }
299
+ if (typeof value !== "object" || value === null) return;
300
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
301
+ const collapsed = key.replace(/[_\-.]/g, "").toLowerCase();
302
+ // action.activated's data.name is a constrained *_mktg registry identifier,
303
+ // not a person's name. Its type-specific validator enforces that closed syntax.
304
+ const semanticActionName = path === "data" && key === "name";
305
+ if (PII_TOKENS.has(collapsed) && !semanticActionName) {
306
+ out.push({
307
+ path: joinPath(path, key),
308
+ code: "pii_field",
309
+ message: `Field "${key}" looks like customer PII/payment data; browser events must be anonymous and minimal.`,
310
+ });
311
+ }
312
+ findPiiFields(child, joinPath(path, key), out);
313
+ }
314
+ }
315
+
316
+ function reqDecimalString(
317
+ obj: Record<string, unknown>,
318
+ key: string,
319
+ v: Violations,
320
+ path: string,
321
+ ): void {
322
+ const value = reqString(obj, key, v, path);
323
+ if (value !== undefined && !DECIMAL_STRING.test(value)) {
324
+ v.add(joinPath(path, key), "format", `"${key}" must be a non-negative decimal string, e.g. "42.50".`);
325
+ }
326
+ }
327
+
328
+ function validateLineItems(parent: Record<string, unknown>, v: Violations, path: string): void {
329
+ const items = parent["items"];
330
+ if (!Array.isArray(items)) {
331
+ v.add(joinPath(path, "items"), "type", "Expected an array at \"items\".");
332
+ return;
333
+ }
334
+ items.forEach((raw, i) => {
335
+ const itemPath = `${joinPath(path, "items")}[${i}]`;
336
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
337
+ v.add(itemPath, "type", "Expected an object.");
338
+ return;
339
+ }
340
+ const item = raw as Record<string, unknown>;
341
+ rejectUnknownKeys(item, COMMERCE_EVENT_LINE_ITEM_FIELDS, v, itemPath);
342
+ reqString(item, "productId", v, itemPath);
343
+ if ("variantId" in item) reqString(item, "variantId", v, itemPath);
344
+ reqInt(item, "quantity", v, itemPath);
345
+ reqDecimalString(item, "unitPrice", v, itemPath);
346
+ });
347
+ }
348
+
349
+ const UTM_FIELDS = ["source", "medium", "campaign", "term", "content"] as const;
350
+ const SAFE_UTM_VALUE = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/;
351
+ const PROTECTED_ATTRIBUTION_VALUE =
352
+ /(?:@|\d{7,}|(?:^|[._~-])(?:bearer|password|secret|token|authorization|preview)(?:$|[._~-]))/i;
353
+
354
+ function validatePageAttribution(parent: Record<string, unknown>, v: Violations): void {
355
+ const attribution = childObject(parent, "attribution", v, "data");
356
+ if (!attribution) return;
357
+ rejectUnknownKeys(attribution, ["referrer", "utm"], v, "data.attribution");
358
+ if (!("referrer" in attribution) && !("utm" in attribution)) {
359
+ v.add("data.attribution", "empty", "Attribution must contain a minimized referrer or UTM object.");
360
+ }
361
+
362
+ if ("referrer" in attribution) {
363
+ const referrer = childObject(attribution, "referrer", v, "data.attribution");
364
+ if (referrer) {
365
+ rejectUnknownKeys(referrer, ["origin"], v, "data.attribution.referrer");
366
+ const origin = reqString(referrer, "origin", v, "data.attribution.referrer");
367
+ if (origin !== undefined) {
368
+ try {
369
+ const url = new URL(origin);
370
+ if (
371
+ !["http:", "https:"].includes(url.protocol) ||
372
+ url.origin !== origin ||
373
+ url.username !== "" ||
374
+ url.password !== ""
375
+ ) {
376
+ v.add(
377
+ "data.attribution.referrer.origin",
378
+ "format",
379
+ "Referrer must be an http(s) origin only; paths, query strings, fragments, and credentials are forbidden.",
380
+ );
381
+ }
382
+ } catch {
383
+ v.add("data.attribution.referrer.origin", "format", "Referrer must be a valid http(s) origin.");
384
+ }
385
+ }
386
+ }
387
+ }
388
+
389
+ if ("utm" in attribution) {
390
+ const utm = childObject(attribution, "utm", v, "data.attribution");
391
+ if (utm) {
392
+ rejectUnknownKeys(utm, UTM_FIELDS, v, "data.attribution.utm");
393
+ if (Object.keys(utm).length === 0) {
394
+ v.add("data.attribution.utm", "empty", "UTM must contain at least one allowlisted field.");
395
+ }
396
+ for (const key of UTM_FIELDS) {
397
+ if (!(key in utm)) continue;
398
+ const value = reqString(utm, key, v, "data.attribution.utm");
399
+ if (value !== undefined && !SAFE_UTM_VALUE.test(value)) {
400
+ v.add(
401
+ `data.attribution.utm.${key}`,
402
+ "format",
403
+ `UTM ${key} must be a 1-128 character opaque campaign token.`,
404
+ );
405
+ }
406
+ if (value !== undefined && PROTECTED_ATTRIBUTION_VALUE.test(value)) {
407
+ v.add(
408
+ `data.attribution.utm.${key}`,
409
+ "protected_value",
410
+ `UTM ${key} resembles PII or protected material and must not be emitted.`,
411
+ );
412
+ }
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ /** Validate a type's `data` against its closed field catalog. */
419
+ function validateEventData(
420
+ type: CommerceEventType,
421
+ data: Record<string, unknown>,
422
+ v: Violations,
423
+ ): void {
424
+ rejectUnknownKeys(data, COMMERCE_EVENT_DATA_FIELDS[type], v, "data");
425
+ switch (type) {
426
+ case "storefront.page.viewed.v1":
427
+ reqString(data, "path", v, "data");
428
+ if ("title" in data) reqString(data, "title", v, "data");
429
+ break;
430
+ case "storefront.page.viewed.v2":
431
+ {
432
+ const path = reqString(data, "path", v, "data");
433
+ if (
434
+ path !== undefined &&
435
+ (!path.startsWith("/") ||
436
+ path.startsWith("//") ||
437
+ path.includes("\\") ||
438
+ path.includes("?") ||
439
+ path.includes("#"))
440
+ ) {
441
+ v.add(
442
+ "data.path",
443
+ "format",
444
+ "Page path must be a root-relative local path and exclude network-path forms, backslashes, query strings, and fragments.",
445
+ );
446
+ }
447
+ if (resemblesManagedAnalyticsPii(path)) {
448
+ v.add(
449
+ "data.path",
450
+ "pii_value",
451
+ "Page paths cannot contain values resembling customer PII.",
452
+ );
453
+ }
454
+ }
455
+ if ("attribution" in data) validatePageAttribution(data, v);
456
+ break;
457
+ case "storefront.action.activated.v1": {
458
+ const name = reqString(data, "name", v, "data");
459
+ if (name !== undefined && !MARKETING_ACTION_NAME_PATTERN.test(name)) {
460
+ v.add("data.name", "format", "Action name must be lowercase snake_case and end in _mktg.");
461
+ } else if (
462
+ name !== undefined &&
463
+ !(APPROVED_MARKETING_ACTION_NAMES as readonly string[]).includes(name)
464
+ ) {
465
+ v.add("data.name", "unknown_enum", "Action name is not in the approved v1 marketing action allowlist.");
466
+ }
467
+ break;
468
+ }
469
+ case "storefront.product.viewed.v1":
470
+ reqString(data, "productId", v, "data");
471
+ if ("variantId" in data) reqString(data, "variantId", v, "data");
472
+ if ("listId" in data) reqString(data, "listId", v, "data");
473
+ if ("position" in data) reqInt(data, "position", v, "data");
474
+ if ("currency" in data) reqString(data, "currency", v, "data");
475
+ if ("unitPrice" in data) reqDecimalString(data, "unitPrice", v, "data");
476
+ break;
477
+ case "storefront.cart.item_added.v1":
478
+ case "storefront.cart.item_removed.v1":
479
+ reqString(data, "productId", v, "data");
480
+ if ("variantId" in data) reqString(data, "variantId", v, "data");
481
+ reqInt(data, "quantity", v, "data");
482
+ if ("unitPrice" in data) reqDecimalString(data, "unitPrice", v, "data");
483
+ if ("currency" in data) reqString(data, "currency", v, "data");
484
+ break;
485
+ case "storefront.cart.updated.v1":
486
+ case "storefront.checkout.handoff.v1":
487
+ case "storefront.purchase.completed.v1":
488
+ reqString(data, "currency", v, "data");
489
+ reqDecimalString(data, "value", v, "data");
490
+ reqInt(data, "itemCount", v, "data");
491
+ validateLineItems(data, v, "data");
492
+ break;
493
+ case "storefront.consent.changed.v1":
494
+ reqStringArray(data, "eligiblePurposes", v, "data");
495
+ if ("previousPurposes" in data) reqStringArray(data, "previousPurposes", v, "data");
496
+ break;
497
+ case "storefront.order.created.v1":
498
+ case "storefront.order.fulfilled.v1":
499
+ reqString(data, "orderId", v, "data");
500
+ reqString(data, "currency", v, "data");
501
+ reqDecimalString(data, "value", v, "data");
502
+ reqInt(data, "itemCount", v, "data");
503
+ validateLineItems(data, v, "data");
504
+ break;
505
+ }
506
+ }
507
+
508
+ function validateCommerceContext(
509
+ parent: Record<string, unknown>,
510
+ type: CommerceEventType | undefined,
511
+ v: Violations,
512
+ ): void {
513
+ const ctx = childObject(parent, "storefront", v, "");
514
+ if (!ctx) return;
515
+ rejectUnknownKeys(
516
+ ctx,
517
+ ["schemaVersion", "tenant", "environment", "siteSurface", "emitter", "sequence"],
518
+ v,
519
+ "storefront",
520
+ );
521
+ reqLiteral(ctx, "schemaVersion", COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION, v, "storefront");
522
+ reqString(ctx, "tenant", v, "storefront");
523
+ reqEnum(ctx, "environment", INTEGRATION_ENVIRONMENTS, v, "storefront");
524
+ reqEnum(ctx, "siteSurface", INTEGRATION_SITE_SURFACES, v, "storefront");
525
+ const emitter = reqEnum(ctx, "emitter", EVENT_EMITTERS, v, "storefront");
526
+ if ("sequence" in ctx) reqInt(ctx, "sequence", v, "storefront");
527
+
528
+ // No-forge of authority: a verified-provider event type can only be emitted by
529
+ // the server. A browser-emitted conversion is rejected, not trusted.
530
+ if (type && emitter === "browser" && EVENT_SOURCE_OF_TRUTH[type] === "verified-provider") {
531
+ v.add(
532
+ "storefront.emitter",
533
+ "forged_authority",
534
+ `"${type}" is server-authoritative (verified provider); a browser emitter cannot author it.`,
535
+ );
536
+ }
537
+ }
538
+
539
+ function validateBaseStorefrontContext(parent: Record<string, unknown>, v: Violations): void {
540
+ const ctx = childObject(parent, "storefront", v, "");
541
+ if (!ctx) return;
542
+ rejectUnknownKeys(ctx, ["schemaVersion", "environment", "siteSurface"], v, "storefront");
543
+ reqLiteral(ctx, "schemaVersion", COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION, v, "storefront");
544
+ reqEnum(ctx, "environment", INTEGRATION_ENVIRONMENTS, v, "storefront");
545
+ reqEnum(ctx, "siteSurface", INTEGRATION_SITE_SURFACES, v, "storefront");
546
+ }
547
+
548
+ export function validateCommerceEvent(input: unknown): ContractResult<CommerceEvent> {
549
+ const v = new Violations();
550
+ const obj = reqObject(input, v, "");
551
+ if (!obj) return { ok: false, violations: v.items };
552
+
553
+ rejectUnknownKeys(
554
+ obj,
555
+ [
556
+ "specversion",
557
+ "id",
558
+ "source",
559
+ "type",
560
+ "subject",
561
+ "time",
562
+ "datacontenttype",
563
+ "dataschema",
564
+ "storefront",
565
+ "correlation",
566
+ "consent",
567
+ "data",
568
+ ],
569
+ v,
570
+ "",
571
+ );
572
+
573
+ reqLiteral(obj, "specversion", "1.0", v, "");
574
+ const id = reqString(obj, "id", v, "");
575
+ if (resemblesManagedAnalyticsPii(id)) {
576
+ v.add("id", "pii_value", "Event ids must be opaque and cannot resemble customer PII.");
577
+ }
578
+ reqString(obj, "source", v, "");
579
+
580
+ // Type resolution fails closed: a reserved-but-unreleased forward declaration is
581
+ // rejected distinctly from an entirely unknown name, and only a released type
582
+ // resolves to a concrete `data` schema.
583
+ const rawType = obj["type"];
584
+ let type: CommerceEventType | undefined;
585
+ if (
586
+ typeof rawType === "string" &&
587
+ (FORWARD_DECLARED_COMMERCE_EVENT_TYPES as readonly string[]).includes(rawType)
588
+ ) {
589
+ v.add(
590
+ "type",
591
+ "reserved_unavailable",
592
+ `"${rawType}" is reserved but not yet available; it requires verified provider truth and stable semantics.`,
593
+ );
594
+ } else {
595
+ type = reqEnum(obj, "type", COMMERCE_EVENT_TYPES, v, "");
596
+ }
597
+
598
+ reqString(obj, "subject", v, "");
599
+ reqIsoTimestamp(obj, "time", v, "");
600
+ reqLiteral(obj, "datacontenttype", "application/json", v, "");
601
+ if ("dataschema" in obj) reqString(obj, "dataschema", v, "");
602
+
603
+ validateCommerceContext(obj, type, v);
604
+
605
+ const correlation = childObject(obj, "correlation", v, "");
606
+ if (correlation) {
607
+ rejectUnknownKeys(correlation, ["id", "expiresAt"], v, "correlation");
608
+ const correlationId = reqString(correlation, "id", v, "correlation");
609
+ if (resemblesManagedAnalyticsPii(correlationId)) {
610
+ v.add(
611
+ "correlation.id",
612
+ "pii_value",
613
+ "Correlation ids must be opaque and cannot resemble customer PII.",
614
+ );
615
+ }
616
+ reqIsoTimestamp(correlation, "expiresAt", v, "correlation");
617
+ }
618
+
619
+ const consent = childObject(obj, "consent", v, "");
620
+ if (consent) {
621
+ rejectUnknownKeys(consent, ["schemaVersion", "snapshotId", "eligiblePurposes"], v, "consent");
622
+ reqLiteral(consent, "schemaVersion", CONSENT_SNAPSHOT_SCHEMA_VERSION, v, "consent");
623
+ reqString(consent, "snapshotId", v, "consent");
624
+ reqStringArray(consent, "eligiblePurposes", v, "consent");
625
+ }
626
+
627
+ const data = childObject(obj, "data", v, "");
628
+ if (data) {
629
+ if (type) validateEventData(type, data, v);
630
+ findPiiFields(data, "data", v.items);
631
+ }
632
+
633
+ for (const violation of findForbiddenFields(obj)) v.items.push(violation);
634
+
635
+ if (!v.ok) return { ok: false, violations: v.items };
636
+ return { ok: true, value: input as CommerceEvent };
637
+ }
638
+
639
+ /**
640
+ * Validate a submitted event AND prove its server-owned fields were not forged.
641
+ * `trusted` is the tenant/environment/surface the server already knows for the
642
+ * execution context that produced the event; any disagreement is a
643
+ * `context_mismatch` violation, so a caller cannot assert a different tenant or a
644
+ * more-privileged environment than the one it is actually running in.
645
+ */
646
+ export function validateCommerceEventInContext(
647
+ input: unknown,
648
+ trusted: TrustedEventContext,
649
+ ): ContractResult<CommerceEvent> {
650
+ const base = validateCommerceEvent(input);
651
+ if (!base.ok) return base;
652
+ const v = new Violations();
653
+ const ctx = base.value.storefront;
654
+ if (ctx.tenant !== trusted.tenant) {
655
+ v.add("storefront.tenant", "context_mismatch", "tenant is server-owned and must match the trusted execution context.");
656
+ }
657
+ if (ctx.environment !== trusted.environment) {
658
+ v.add("storefront.environment", "context_mismatch", "environment is server-owned and must match the trusted execution context.");
659
+ }
660
+ if (ctx.siteSurface !== trusted.siteSurface) {
661
+ v.add("storefront.siteSurface", "context_mismatch", "siteSurface is server-owned and must match the trusted execution context.");
662
+ }
663
+ if (!v.ok) return { ok: false, violations: v.items };
664
+ return base;
665
+ }
666
+
667
+ export function validateNavigationCompletedEvent(
668
+ input: unknown,
669
+ ): ContractResult<NavigationCompletedEvent> {
670
+ const v = new Violations();
671
+ const obj = reqObject(input, v, "");
672
+ if (!obj) return { ok: false, violations: v.items };
673
+
674
+ rejectUnknownKeys(
675
+ obj,
676
+ ["specversion", "id", "source", "type", "time", "storefront", "data"],
677
+ v,
678
+ "",
679
+ );
680
+
681
+ reqLiteral(obj, "specversion", "1.0", v, "");
682
+ reqString(obj, "id", v, "");
683
+ reqString(obj, "source", v, "");
684
+ reqLiteral(obj, "type", NAVIGATION_COMPLETED_EVENT_TYPE, v, "");
685
+ reqIsoTimestamp(obj, "time", v, "");
686
+ validateBaseStorefrontContext(obj, v);
687
+
688
+ const data = childObject(obj, "data", v, "");
689
+ if (data) {
690
+ rejectUnknownKeys(data, ["from", "to", "kind"], v, "data");
691
+ reqString(data, "from", v, "data");
692
+ reqString(data, "to", v, "data");
693
+ reqEnum(data, "kind", NAVIGATION_KINDS, v, "data");
694
+ }
695
+
696
+ // Payloads exclude candidate wrappers/tokens.
697
+ for (const violation of findForbiddenFields(obj)) v.items.push(violation);
698
+
699
+ if (!v.ok) return { ok: false, violations: v.items };
700
+ return { ok: true, value: input as NavigationCompletedEvent };
701
+ }