@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,444 @@
1
+ /**
2
+ * The signed, opaque checkout-correlation token (ADR 0022 `receipt-consent-propagation`,
3
+ * ADR 0023 Q3 `resolve-gate-legal-q3-correlation-token` = analytics-consent-required;
4
+ * ext-commerce-03). It is the ONE cross-site attribution handle that survives the
5
+ * managed-checkout handoff: minted by the storefront at `checkout.handoff`, carried
6
+ * through the provider (Foxy) as a never-signed passthrough cart field, echoed back on
7
+ * the verified order webhook, and verified server-side by the order join
8
+ * (ext-commerce-04) to thread a browser handoff to its authoritative conversion.
9
+ *
10
+ * It is a CORRELATION HANDLE, never a capability:
11
+ * - It binds exactly `{ v, tenant, env, eventId, iat, exp, jti }` and NOTHING else —
12
+ * no scope, role, price, permission, or action. Verifying it authorizes nothing;
13
+ * it only asserts "this handoff, in this tenant/environment, is still within its
14
+ * bounded lifetime." Downstream code decides what to do with a verified thread.
15
+ * - It carries NO PII and NO secret: `tenant`/`env` are public execution-context
16
+ * facts (already in the host/URL), `eventId`/`jti` are opaque random-ish handles,
17
+ * and the timestamps are integers. No email, name, address, identity result, cart
18
+ * secret, or signing secret ever enters it.
19
+ * - Its integrity is an HMAC-SHA256 over the SERVER-ONLY correlation secret. That
20
+ * secret is an env-injected Worker binding resolved per request (like the cart
21
+ * secret, and DISTINCT from it — key separation), passed in explicitly here and
22
+ * never read from a global, never serialized, never shipped to the browser.
23
+ *
24
+ * Consent gate (ADR 0023 Q3): the token is carried through the handoff ONLY when the
25
+ * shopper is analytics-eligible ({@link isPurposeEligible} for `analytics`). When not
26
+ * eligible the token is DROPPED — the handoff proceeds without the correlation field
27
+ * and the eventual order is explicitly UNATTRIBUTED. The correlation is never smuggled
28
+ * past a withheld analytics consent (see {@link correlationForHandoff}).
29
+ *
30
+ * Wire format, provider limits, and canonical encoding:
31
+ * `docs/architecture/commerce-correlation-token.md`.
32
+ */
33
+ import type { IntegrationEnvironment } from "./environment.js";
34
+ import { INTEGRATION_ENVIRONMENTS } from "./environment.js";
35
+ import type { EventCorrelation } from "./events.js";
36
+ import type { ConsentSnapshot } from "./consent-broker.js";
37
+ import { isPurposeEligible } from "./consent-broker.js";
38
+ import type { ConsentPurpose } from "../integration-capabilities.js";
39
+
40
+ /** The token version tag; an unknown version fails closed on verify. */
41
+ export const CORRELATION_TOKEN_VERSION = 1 as const;
42
+
43
+ /** The consent purpose the cross-site correlation carry requires (ADR 0023 Q3). */
44
+ export const CORRELATION_CONSENT_PURPOSE: ConsentPurpose = "analytics";
45
+
46
+ /**
47
+ * Default bounded lifetime: the window a shopper has to complete the hosted checkout
48
+ * so the order webhook still joins to the handoff. One hour comfortably spans a normal
49
+ * checkout while keeping the replay surface small; callers may override per surface.
50
+ */
51
+ export const DEFAULT_CORRELATION_TTL_SECONDS = 60 * 60;
52
+
53
+ /**
54
+ * The never-signed passthrough cart field the token rides in through the provider.
55
+ * The `h:` prefix marks a Foxy hidden/custom field that is passed to the transaction
56
+ * verbatim and is NOT part of the cart HMAC (`checkout.ts` never signs `h:`/`x:`/
57
+ * `__`/`utm_` params), so carrying the token needs no cart secret and cannot perturb
58
+ * price signing. It surfaces on the verified order webhook for the order join to read.
59
+ */
60
+ export const CORRELATION_CART_FIELD_NAME = "h:tot_correlation";
61
+
62
+ /**
63
+ * The provider-safe upper bound on the encoded token length. The token is well under
64
+ * this (~160 chars) for the fixed claim set; the bound is a conservative guard against
65
+ * a provider truncating an over-long custom field value. Minting asserts it.
66
+ */
67
+ export const MAX_CORRELATION_TOKEN_LENGTH = 512;
68
+
69
+ /** The exact wire charset: two base64url segments joined by a single `.` — URL/form safe, no padding. */
70
+ export const CORRELATION_TOKEN_PATTERN = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
71
+
72
+ /**
73
+ * The complete, closed set of bound claims — the ONLY thing the token asserts. Adding a
74
+ * field here is a deliberate contract change; a verifier rejects any decoded payload
75
+ * with an unknown key so the claim set can never silently grow.
76
+ */
77
+ export interface CorrelationTokenClaims {
78
+ /** Contract version; a token whose `v` this build does not know fails closed. */
79
+ v: typeof CORRELATION_TOKEN_VERSION;
80
+ /** The tenant the handoff ran in — a public execution-context fact, never caller-set. */
81
+ tenant: string;
82
+ /** The stage the handoff ran in; a candidate/staged token can never satisfy a `live` verify. */
83
+ env: IntegrationEnvironment;
84
+ /** The opaque logical correlation/handoff event id the order join threads to. */
85
+ eventId: string;
86
+ /** Issued-at, Unix seconds. */
87
+ iat: number;
88
+ /** Expiry, Unix seconds. After this the token is dead — join yields an unattributed order. */
89
+ exp: number;
90
+ /** A unique per-mint nonce; the single-use key a replay guard consumes. */
91
+ jti: string;
92
+ }
93
+
94
+ const CLAIM_KEYS: readonly (keyof CorrelationTokenClaims)[] = [
95
+ "v",
96
+ "tenant",
97
+ "env",
98
+ "eventId",
99
+ "iat",
100
+ "exp",
101
+ "jti",
102
+ ];
103
+
104
+ export interface MintCorrelationTokenInput {
105
+ /** The tenant, from trusted server execution context — never a caller-supplied value. */
106
+ tenant: string;
107
+ /** The stage, from trusted server execution context. */
108
+ environment: IntegrationEnvironment;
109
+ /** The logical correlation/handoff event id this token threads. */
110
+ eventId: string;
111
+ /** Bounded lifetime; defaults to {@link DEFAULT_CORRELATION_TTL_SECONDS}. */
112
+ ttlSeconds?: number;
113
+ /** Injectable wall clock in ms (defaults to `Date.now()`) for deterministic tests. */
114
+ now?: number;
115
+ }
116
+
117
+ export interface MintedCorrelationToken {
118
+ /** The opaque wire token: `<base64url(payload)>.<base64url(hmac)>`. */
119
+ token: string;
120
+ /** The bound claims, so the caller can build the event envelope's correlation. */
121
+ claims: CorrelationTokenClaims;
122
+ }
123
+
124
+ /** The tenant/environment a verify is performed against, from trusted server context. */
125
+ export interface CorrelationVerifyContext {
126
+ tenant: string;
127
+ environment: IntegrationEnvironment;
128
+ /** Injectable wall clock in ms (defaults to `Date.now()`) for deterministic tests. */
129
+ now?: number;
130
+ }
131
+
132
+ /** Why a token was not accepted. Every path is a returned reason, never a thrown error. */
133
+ export type CorrelationRejectReason =
134
+ /** No token was echoed back (provider dropped it, or the shopper was unattributed). */
135
+ | "missing"
136
+ /** The token is not two base64url segments, or its payload is not the closed claim shape. */
137
+ | "malformed"
138
+ /** The HMAC does not verify — the token (or its payload) was tampered with. */
139
+ | "bad_signature"
140
+ /** A valid token minted for a different tenant or environment (cross-tenant/stage). */
141
+ | "context_mismatch"
142
+ /** The token is past its `exp`. */
143
+ | "expired"
144
+ /** A structurally valid, in-context, unexpired token whose `jti` was already consumed. */
145
+ | "replay";
146
+
147
+ export type CorrelationVerifyResult =
148
+ | { ok: true; claims: CorrelationTokenClaims }
149
+ | { ok: false; reason: CorrelationRejectReason };
150
+
151
+ // --- encoding -------------------------------------------------------------------
152
+
153
+ function b64urlEncodeBytes(bytes: ArrayBuffer | Uint8Array): string {
154
+ return Buffer.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)).toString("base64url");
155
+ }
156
+
157
+ function b64urlEncodeUtf8(text: string): string {
158
+ return Buffer.from(text, "utf8").toString("base64url");
159
+ }
160
+
161
+ /** Decode a base64url segment to UTF-8, or `null` if it is not clean base64url. */
162
+ function b64urlDecodeUtf8(segment: string): string | null {
163
+ try {
164
+ const buf = Buffer.from(segment, "base64url");
165
+ // Reject non-canonical input: re-encoding must reproduce the exact segment.
166
+ if (buf.toString("base64url") !== segment) return null;
167
+ return buf.toString("utf8");
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ async function importHmacKey(secret: string, usage: "sign" | "verify"): Promise<CryptoKey> {
174
+ return crypto.subtle.importKey(
175
+ "raw",
176
+ new TextEncoder().encode(secret) as unknown as BufferSource,
177
+ { name: "HMAC", hash: "SHA-256" },
178
+ false,
179
+ [usage],
180
+ );
181
+ }
182
+
183
+ /** A compact 96-bit random single-use nonce as lowercase hex. */
184
+ function randomJti(): string {
185
+ const bytes = crypto.getRandomValues(new Uint8Array(12));
186
+ return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
187
+ }
188
+
189
+ // --- mint -----------------------------------------------------------------------
190
+
191
+ /**
192
+ * Mint a signed correlation token. The `secret` is the server-only correlation-signing
193
+ * key, injected by the caller from its Worker binding; it is used to sign and then
194
+ * discarded — it never enters the token, the claims, or any serialized surface.
195
+ */
196
+ export async function mintCorrelationToken(
197
+ input: MintCorrelationTokenInput,
198
+ secret: string,
199
+ ): Promise<MintedCorrelationToken> {
200
+ const nowMs = input.now ?? Date.now();
201
+ const iat = Math.floor(nowMs / 1000);
202
+ const ttl = input.ttlSeconds ?? DEFAULT_CORRELATION_TTL_SECONDS;
203
+ const claims: CorrelationTokenClaims = {
204
+ v: CORRELATION_TOKEN_VERSION,
205
+ tenant: input.tenant,
206
+ env: input.environment,
207
+ eventId: input.eventId,
208
+ iat,
209
+ exp: iat + ttl,
210
+ jti: randomJti(),
211
+ };
212
+
213
+ const payloadB64 = b64urlEncodeUtf8(canonicalClaims(claims));
214
+ const key = await importHmacKey(secret, "sign");
215
+ const sig = await crypto.subtle.sign(
216
+ "HMAC",
217
+ key,
218
+ new TextEncoder().encode(payloadB64) as unknown as BufferSource,
219
+ );
220
+ const token = `${payloadB64}.${b64urlEncodeBytes(sig)}`;
221
+
222
+ if (token.length > MAX_CORRELATION_TOKEN_LENGTH) {
223
+ throw new Error(
224
+ `correlation token length ${token.length} exceeds provider-safe limit ${MAX_CORRELATION_TOKEN_LENGTH}`,
225
+ );
226
+ }
227
+ return { token, claims };
228
+ }
229
+
230
+ /**
231
+ * Canonical, fixed-order serialization of the claims — the exact bytes both mint and
232
+ * the payload decode agree on. Fixed key order (not input order) so an equal claim set
233
+ * always yields byte-identical output; `JSON.stringify` escapes any delimiter inside a
234
+ * value, so no field can be smuggled across the claim boundary.
235
+ */
236
+ function canonicalClaims(claims: CorrelationTokenClaims): string {
237
+ return JSON.stringify({
238
+ v: claims.v,
239
+ tenant: claims.tenant,
240
+ env: claims.env,
241
+ eventId: claims.eventId,
242
+ iat: claims.iat,
243
+ exp: claims.exp,
244
+ jti: claims.jti,
245
+ });
246
+ }
247
+
248
+ // --- verify ---------------------------------------------------------------------
249
+
250
+ /**
251
+ * Verify a token's signature, tenant/environment binding, and expiry against trusted
252
+ * context. This is the PURE verify — it does not consume single-use state; layer
253
+ * {@link verifyCorrelationTokenOnce} over it for replay defense. A `null`/`undefined`/
254
+ * empty token (a missing provider echo) is the explicit `missing` reason, never a throw.
255
+ */
256
+ export async function verifyCorrelationToken(
257
+ token: string | null | undefined,
258
+ ctx: CorrelationVerifyContext,
259
+ secret: string,
260
+ ): Promise<CorrelationVerifyResult> {
261
+ if (typeof token !== "string" || token.length === 0) {
262
+ return { ok: false, reason: "missing" };
263
+ }
264
+ if (token.length > MAX_CORRELATION_TOKEN_LENGTH || !CORRELATION_TOKEN_PATTERN.test(token)) {
265
+ return { ok: false, reason: "malformed" };
266
+ }
267
+
268
+ const [payloadB64, sigB64] = token.split(".") as [string, string];
269
+
270
+ const key = await importHmacKey(secret, "verify");
271
+ let sigBytes: Buffer;
272
+ try {
273
+ sigBytes = Buffer.from(sigB64, "base64url");
274
+ } catch {
275
+ return { ok: false, reason: "malformed" };
276
+ }
277
+ const valid = await crypto.subtle.verify(
278
+ "HMAC",
279
+ key,
280
+ sigBytes as unknown as BufferSource,
281
+ new TextEncoder().encode(payloadB64) as unknown as BufferSource,
282
+ );
283
+ if (!valid) return { ok: false, reason: "bad_signature" };
284
+
285
+ const claims = decodeClaims(payloadB64);
286
+ if (!claims) return { ok: false, reason: "malformed" };
287
+
288
+ if (claims.tenant !== ctx.tenant || claims.env !== ctx.environment) {
289
+ return { ok: false, reason: "context_mismatch" };
290
+ }
291
+
292
+ const nowSec = Math.floor((ctx.now ?? Date.now()) / 1000);
293
+ if (nowSec >= claims.exp) return { ok: false, reason: "expired" };
294
+
295
+ return { ok: true, claims };
296
+ }
297
+
298
+ /** A pluggable single-use store. The durable KV/DO store is injected by the consumer. */
299
+ export interface CorrelationReplayGuard {
300
+ /** True if this `jti` was already consumed. */
301
+ seen(jti: string): boolean | Promise<boolean>;
302
+ /** Record `jti` as consumed until `expiresAtSec` (so the store can prune it later). */
303
+ remember(jti: string, expiresAtSec: number): void | Promise<void>;
304
+ }
305
+
306
+ /**
307
+ * Verify AND consume a token exactly once. On a fully valid token whose `jti` the guard
308
+ * has not seen, it records the `jti` and returns the claims; a second presentation of
309
+ * the same token is rejected `replay`. Signature/context/expiry rejections never consume
310
+ * the guard. This is the order-join-facing entry point.
311
+ */
312
+ export async function verifyCorrelationTokenOnce(
313
+ token: string | null | undefined,
314
+ ctx: CorrelationVerifyContext,
315
+ secret: string,
316
+ guard: CorrelationReplayGuard,
317
+ ): Promise<CorrelationVerifyResult> {
318
+ const base = await verifyCorrelationToken(token, ctx, secret);
319
+ if (!base.ok) return base;
320
+ if (await guard.seen(base.claims.jti)) return { ok: false, reason: "replay" };
321
+ await guard.remember(base.claims.jti, base.claims.exp);
322
+ return base;
323
+ }
324
+
325
+ /** Decode a base64url payload to the closed claim shape, or `null` if it is not exactly that. */
326
+ function decodeClaims(payloadB64: string): CorrelationTokenClaims | null {
327
+ const json = b64urlDecodeUtf8(payloadB64);
328
+ if (json === null) return null;
329
+ let parsed: unknown;
330
+ try {
331
+ parsed = JSON.parse(json);
332
+ } catch {
333
+ return null;
334
+ }
335
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
336
+ const obj = parsed as Record<string, unknown>;
337
+
338
+ for (const key of Object.keys(obj)) {
339
+ if (!(CLAIM_KEYS as readonly string[]).includes(key)) return null;
340
+ }
341
+ if (obj.v !== CORRELATION_TOKEN_VERSION) return null;
342
+ if (typeof obj.tenant !== "string" || obj.tenant.length === 0) return null;
343
+ if (typeof obj.env !== "string" || !(INTEGRATION_ENVIRONMENTS as readonly string[]).includes(obj.env)) {
344
+ return null;
345
+ }
346
+ if (typeof obj.eventId !== "string" || obj.eventId.length === 0) return null;
347
+ if (!Number.isSafeInteger(obj.iat) || !Number.isSafeInteger(obj.exp)) return null;
348
+ if (typeof obj.jti !== "string" || obj.jti.length === 0) return null;
349
+
350
+ return {
351
+ v: CORRELATION_TOKEN_VERSION,
352
+ tenant: obj.tenant,
353
+ env: obj.env as IntegrationEnvironment,
354
+ eventId: obj.eventId,
355
+ iat: obj.iat as number,
356
+ exp: obj.exp as number,
357
+ jti: obj.jti,
358
+ };
359
+ }
360
+
361
+ // --- handoff carry (ADR 0023 Q3 gate) -------------------------------------------
362
+
363
+ /**
364
+ * The correlation outcome for one checkout handoff. `attributed: true` carries the
365
+ * token to put on the provider cart field and the matching {@link EventCorrelation}
366
+ * for the event envelope; `attributed: false` means the shopper is not analytics-
367
+ * eligible, so NO token is carried and the resulting order is explicitly unattributed.
368
+ */
369
+ export type HandoffCorrelation =
370
+ | {
371
+ attributed: true;
372
+ token: string;
373
+ /** The cross-site carrier field: put `{ name, value }` on the provider cart. */
374
+ cartField: { name: string; value: string };
375
+ /** The event-envelope correlation thread (`id` === bound `eventId`). */
376
+ correlation: EventCorrelation;
377
+ claims: CorrelationTokenClaims;
378
+ }
379
+ | { attributed: false; reason: "analytics-consent-required" };
380
+
381
+ /**
382
+ * Resolve the correlation carry for a checkout handoff under ADR 0023 Q3. Mints and
383
+ * returns the token ONLY when the consent snapshot is analytics-eligible; otherwise it
384
+ * returns `attributed: false` and the handoff must proceed WITHOUT the correlation
385
+ * field — the order is unattributed, never covertly correlated.
386
+ */
387
+ export async function correlationForHandoff(
388
+ input: MintCorrelationTokenInput,
389
+ snapshot: ConsentSnapshot,
390
+ secret: string,
391
+ ): Promise<HandoffCorrelation> {
392
+ if (!isPurposeEligible(snapshot, CORRELATION_CONSENT_PURPOSE)) {
393
+ return { attributed: false, reason: "analytics-consent-required" };
394
+ }
395
+ const { token, claims } = await mintCorrelationToken(input, secret);
396
+ return {
397
+ attributed: true,
398
+ token,
399
+ cartField: { name: CORRELATION_CART_FIELD_NAME, value: token },
400
+ correlation: { id: claims.eventId, expiresAt: new Date(claims.exp * 1000).toISOString() },
401
+ claims,
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Read the correlation token a provider echoed back on the verified order webhook. The
407
+ * provider carries the {@link CORRELATION_CART_FIELD_NAME} field verbatim; an absent or
408
+ * non-string value yields `undefined`, which {@link verifyCorrelationToken} treats as
409
+ * the explicit `missing` (unattributed) outcome.
410
+ */
411
+ export function readEchoedCorrelation(
412
+ fields: Record<string, unknown> | null | undefined,
413
+ ): string | undefined {
414
+ if (!fields) return undefined;
415
+ const raw = fields[CORRELATION_CART_FIELD_NAME];
416
+ return typeof raw === "string" && raw.length > 0 ? raw : undefined;
417
+ }
418
+
419
+ /**
420
+ * A simple in-memory single-use store for local dev and tests. Production supplies a
421
+ * durable KV/Durable-Object-backed {@link CorrelationReplayGuard}; this one prunes
422
+ * expired `jti`s lazily on each `seen` so it does not grow unbounded in a long process.
423
+ */
424
+ export class InMemoryCorrelationReplayGuard implements CorrelationReplayGuard {
425
+ private readonly consumed = new Map<string, number>();
426
+
427
+ constructor(private readonly nowSec: () => number = () => Math.floor(Date.now() / 1000)) {}
428
+
429
+ seen(jti: string): boolean {
430
+ this.prune();
431
+ return this.consumed.has(jti);
432
+ }
433
+
434
+ remember(jti: string, expiresAtSec: number): void {
435
+ this.consumed.set(jti, expiresAtSec);
436
+ }
437
+
438
+ private prune(): void {
439
+ const now = this.nowSec();
440
+ for (const [jti, exp] of this.consumed) {
441
+ if (now >= exp) this.consumed.delete(jti);
442
+ }
443
+ }
444
+ }