@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.1

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 (370) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +8 -3
  3. package/apps/storefront/env.d.ts +54 -5
  4. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  5. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  6. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  7. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  8. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  9. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  10. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  11. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  12. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  13. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  14. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  15. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  16. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  17. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  18. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  19. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  20. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  22. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  23. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  24. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  25. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  26. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  27. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  28. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  29. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  30. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  32. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  33. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  34. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  37. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  38. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  41. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  42. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  43. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  44. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  45. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  46. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  47. package/apps/storefront/src/components/Img.astro +1 -1
  48. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  49. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  50. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  51. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  52. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  53. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  54. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  55. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  59. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  60. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  61. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  62. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  63. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  64. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  65. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  66. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  67. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  68. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  69. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  70. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  71. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  72. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  73. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  74. package/apps/storefront/src/config/resolver.ts +18 -10
  75. package/apps/storefront/src/layouts/Layout.astro +100 -20
  76. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  77. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  78. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  79. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  80. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  81. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  82. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  83. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  84. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  87. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  89. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  90. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  91. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  92. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  93. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  94. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  95. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  96. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  97. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  98. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  99. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  100. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  104. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  105. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  106. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  109. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  110. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  111. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  112. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  113. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  114. package/apps/storefront/src/lib/blog/collection.ts +114 -17
  115. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  116. package/apps/storefront/src/lib/blog/provider.ts +76 -9
  117. package/apps/storefront/src/lib/blog/types.ts +9 -9
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  119. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  120. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  121. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  122. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  123. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  124. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  126. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  127. package/apps/storefront/src/lib/content/index.ts +5 -5
  128. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  129. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  130. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  131. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  132. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  133. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  134. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  135. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  136. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  137. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  138. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  139. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  140. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  141. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  142. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  143. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  144. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  145. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  146. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  147. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  148. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  149. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  150. package/apps/storefront/src/lib/membership/model.ts +1 -2
  151. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  153. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  154. package/apps/storefront/src/lib/previewReload.ts +57 -1
  155. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  156. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  157. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  158. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  159. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  160. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  161. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  162. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  163. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  164. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  165. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  166. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  173. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  176. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  177. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  178. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  179. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  180. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  181. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  182. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  183. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  184. package/apps/storefront/src/lib/social/notify.ts +4 -4
  185. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  186. package/apps/storefront/src/lib/storyblok/provider.ts +203 -69
  187. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  188. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  189. package/apps/storefront/src/middleware/index.ts +273 -51
  190. package/apps/storefront/src/pages/[...slug].astro +9 -4
  191. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  192. package/apps/storefront/src/pages/admin/select.astro +96 -0
  193. package/apps/storefront/src/pages/admin.astro +16 -2
  194. package/apps/storefront/src/pages/api/activity.ts +8 -8
  195. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  196. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  197. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  199. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  201. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  202. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  203. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  204. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  205. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  206. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  207. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  208. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  209. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  210. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  211. package/apps/storefront/src/pages/blog/index.astro +2 -2
  212. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  213. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  214. package/apps/storefront/src/pages/index.astro +10 -5
  215. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  216. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  217. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  218. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +42 -31
  219. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +8 -3
  220. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +6 -2
  221. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +6 -2
  222. package/apps/storefront/src/pages/style-guide/index.astro +19 -5
  223. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  224. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  225. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  226. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  227. package/apps/storefront/src/styles/admin.css +27 -1
  228. package/apps/storefront/src/themes/tenants/index.ts +19 -7
  229. package/apps/storefront/tsconfig.json +1 -1
  230. package/package.json +1 -1
  231. package/packages/public-runtime/package.json +2 -1
  232. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  250. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  251. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  252. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  253. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  254. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  255. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  256. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  257. package/packages/public-runtime/src/activity/event.ts +6 -6
  258. package/packages/public-runtime/src/activity/index.ts +2 -2
  259. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  260. package/packages/public-runtime/src/candidate-index.ts +42 -8
  261. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  262. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  263. package/packages/public-runtime/src/checkout-style.ts +7 -7
  264. package/packages/public-runtime/src/checkout.ts +3 -3
  265. package/packages/public-runtime/src/chrome.ts +777 -0
  266. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  267. package/packages/public-runtime/src/csp.ts +99 -0
  268. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  269. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  270. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  271. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  272. package/packages/public-runtime/src/evidence.ts +288 -0
  273. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  274. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  275. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  276. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  277. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  278. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  279. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  280. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  281. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  282. package/packages/public-runtime/src/index.ts +16 -1
  283. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  284. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  285. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  286. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  287. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  288. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  289. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  290. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  291. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  292. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  293. package/packages/public-runtime/src/integration/csp.ts +105 -0
  294. package/packages/public-runtime/src/integration/digest.ts +43 -0
  295. package/packages/public-runtime/src/integration/environment.ts +228 -0
  296. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  297. package/packages/public-runtime/src/integration/events.ts +701 -0
  298. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  299. package/packages/public-runtime/src/integration/index.ts +90 -0
  300. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  301. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  302. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  303. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  305. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  306. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  307. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  308. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  309. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  310. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  311. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  312. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  313. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  314. package/packages/public-runtime/src/integration/observability.ts +451 -0
  315. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  316. package/packages/public-runtime/src/integration/phases.ts +182 -0
  317. package/packages/public-runtime/src/integration/plan.ts +112 -0
  318. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  319. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  320. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  321. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  322. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  323. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  324. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  325. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  326. package/packages/public-runtime/src/integration/result.ts +239 -0
  327. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  328. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  329. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  330. package/packages/public-runtime/src/integration/version.ts +92 -0
  331. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  332. package/packages/public-runtime/src/membership.ts +2 -2
  333. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  334. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  335. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  336. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  337. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  338. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  339. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  340. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  341. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  342. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  343. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  344. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  345. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  346. package/packages/public-runtime/src/static-bundle.ts +5 -5
  347. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  348. package/packages/public-runtime/src/tenant.ts +136 -9
  349. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  350. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  351. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  352. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  353. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  354. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  355. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  356. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  357. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  358. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  359. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  360. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  361. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  362. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  363. package/scripts/build/copy-tenant-assets.mjs +12 -5
  364. package/scripts/dev/ai-edit.mjs +2 -1
  365. package/scripts/dev/file-browser.mjs +2 -2
  366. package/scripts/dev/file-writer.mjs +2 -2
  367. package/scripts/dev/integration-guardrails.mjs +498 -0
  368. package/scripts/dev/safe-path.mjs +2 -2
  369. package/scripts/dev/tenant-source-root.mjs +3 -2
  370. package/scripts/tot-dev.mjs +5 -4
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Version-scoped integration CSP compiler (ADR 0016 curated profiles, ADR 0018/0019
3
+ * exact-origin grammar + strict-dynamic rejection; ext-policy-02).
4
+ *
5
+ * Compiles the Content-Security-Policy additions a resolved integration plan
6
+ * contributes: it merges the exact origins a curated adapter PROFILE expands to
7
+ * with the exact reviewed origins an advanced declaration lists ({@link
8
+ * EffectivePlan.cspNeeds}), into the fixed set of directives a declaration may
9
+ * touch ({@link INTEGRATION_CSP_DIRECTIVES}) — and NOTHING else. The strict base
10
+ * policy ({@link import("../csp.js").buildCsp}) always seeds `'self'` + the
11
+ * per-request nonce first; this only ever APPENDS reviewed origins to those six
12
+ * directives, so integration policy can never replace or weaken the base:
13
+ * wildcards, bare schemes, insecure origins, `unsafe-inline`, `unsafe-eval`,
14
+ * `strict-dynamic`, unknown directives, and any full-header replacement are all
15
+ * outside the grammar and rejected here.
16
+ *
17
+ * The compiler is PURE and DETERMINISTIC: the same profiles + sources + revision
18
+ * resolve to a byte-identical additions object (origins sorted and deduped per
19
+ * directive), independent of input order. The output binds to the plan's
20
+ * `planRevision`, so it is version-scoped — the same value the candidate/edge
21
+ * cache identity folds in, which is what makes a rollback/uninstall drop the
22
+ * removed integration's origins on both the cold (recompute) and warm (new key)
23
+ * paths rather than surviving to TTL.
24
+ *
25
+ * It is FAIL-CLOSED PRE-RELEASE: {@link compileIntegrationCsp} returns violations
26
+ * (and no additions) for any forbidden source, so the promotion gate rejects the
27
+ * plan before it ships. On the render hot path a plan has already passed that
28
+ * gate; {@link compileIntegrationCspForRender} returns EMPTY additions on the
29
+ * defensive off-chance a source is invalid, leaving the strict base untouched
30
+ * rather than throwing.
31
+ */
32
+ import type { CspOptions } from "../csp.js";
33
+ import type { ContractResult } from "./result.js";
34
+ import { Violations } from "./result.js";
35
+ import {
36
+ INTEGRATION_CSP_DIRECTIVES,
37
+ validateCspSourceSet,
38
+ type CspSourceSet,
39
+ type IntegrationCspDirective,
40
+ } from "./csp.js";
41
+ import type { EffectivePlan } from "./plan.js";
42
+ import type { IntegrationDeclarationInput } from "./resolve.js";
43
+
44
+ /**
45
+ * The curated adapter CSP catalog: a closed, platform-owned map from a stable
46
+ * profile slug to the EXACT origins that adapter needs. A declaration references a
47
+ * slug in `browser.csp.profiles`; the platform (never the tenant) owns the origins,
48
+ * so a novel adapter requires a reviewed entry here — not a config edit — exactly
49
+ * like {@link import("../csp.js").EMBED_PROVIDERS} for tenant embeds. Every origin
50
+ * is an exact `https://host[:port][/path]`, never a wildcard or scheme keyword; a
51
+ * module-load parity test re-validates the whole catalog against the exact-origin
52
+ * grammar as defense in depth.
53
+ */
54
+ export const INTEGRATION_CSP_PROFILES: Readonly<Record<string, CspSourceSet>> = Object.freeze({
55
+ // Privacy-first analytics — a lightweight, cookieless script + its beacon
56
+ // endpoint, both first-party to the vendor. Sandbox/broker friendly.
57
+ plausible: {
58
+ "script-src": ["https://plausible.io"],
59
+ "connect-src": ["https://plausible.io"],
60
+ },
61
+ fathom: {
62
+ "script-src": ["https://cdn.usefathom.com"],
63
+ "connect-src": ["https://cdn.usefathom.com"],
64
+ },
65
+
66
+ // Native Google Analytics / Ads measurement. The adapter sends reviewed event
67
+ // payloads only to Google's measurement endpoints; it does not load a remote
68
+ // browser container. Every origin is exact and platform-owned.
69
+ "google-measurement": {
70
+ "connect-src": ["https://www.google-analytics.com"],
71
+ },
72
+
73
+ // Meta pixel (fbevents.js). The loader is served from connect.facebook.net; the
74
+ // pixel's collect endpoint is the facebook.com tracking origin. The tenant
75
+ // supplies only its public pixel id.
76
+ "meta-pixel": {
77
+ "script-src": ["https://connect.facebook.net"],
78
+ "img-src": ["https://www.facebook.com"],
79
+ "connect-src": ["https://www.facebook.com"],
80
+ },
81
+
82
+ // Native Amplitude ingestion. Storefront-owned code posts the reviewed,
83
+ // minimized envelope directly to HTTP API v2. No vendor SDK, loader, remote
84
+ // configuration, recording, or browser-observation origin is permitted.
85
+ "amplitude-http": {
86
+ "connect-src": ["https://api2.amplitude.com"],
87
+ },
88
+
89
+ });
90
+
91
+ /** A curated profile slug known to {@link INTEGRATION_CSP_PROFILES}. */
92
+ export type IntegrationCspProfile = keyof typeof INTEGRATION_CSP_PROFILES;
93
+
94
+ /**
95
+ * Compiled additions for the six contributable directives — sorted, deduped exact
96
+ * origins. A directive is present only when it has at least one origin; an empty
97
+ * object is the no-integration case (folds into the base as a no-op).
98
+ */
99
+ export type IntegrationCspAdditions = Partial<Record<IntegrationCspDirective, string[]>>;
100
+
101
+ /** The compiled, version-scoped integration CSP contribution. */
102
+ export interface CompiledIntegrationCsp {
103
+ /** Binds these additions to the exact resolved plan revision (version identity). */
104
+ planRevision: string;
105
+ /** Sorted, deduped exact origins per contributable directive; empty when nothing declares CSP. */
106
+ additions: IntegrationCspAdditions;
107
+ /** The curated profile slugs that expanded into `additions` (sorted, deduped). */
108
+ profiles: string[];
109
+ }
110
+
111
+ export interface CompileIntegrationCspInput {
112
+ /** Curated profile slugs declared across the plan's integrations (order-independent). */
113
+ profiles: readonly string[];
114
+ /** Exact reviewed origins the resolver merged ({@link EffectivePlan.cspNeeds}); advanced declarations. */
115
+ sources: CspSourceSet;
116
+ /** The resolved plan revision these additions bind to ({@link EffectivePlan.planRevision}). */
117
+ planRevision: string;
118
+ }
119
+
120
+ const DIRECTIVE_TO_CSP_OPTION: Readonly<Record<IntegrationCspDirective, keyof CspOptions>> = {
121
+ "script-src": "scriptSrc",
122
+ "connect-src": "connectSrc",
123
+ "frame-src": "frameSrc",
124
+ "img-src": "imgSrc",
125
+ "style-src": "styleSrc",
126
+ "font-src": "fontSrc",
127
+ };
128
+
129
+ /**
130
+ * Compile a resolved plan's curated profiles + advanced exact origins into
131
+ * version-scoped CSP additions. Fail-closed and all-or-nothing: an unknown profile
132
+ * slug, or any source outside the exact-origin grammar (wildcard, bare scheme,
133
+ * insecure origin, `unsafe-inline`/`unsafe-eval`/`strict-dynamic`, unknown
134
+ * directive, or a non-object/full-header shape), yields violations and NO
135
+ * additions — never a partial merge. This is the pre-release entry the promotion
136
+ * gate uses to reject an invalid plan before it can reach a live response.
137
+ */
138
+ export function compileIntegrationCsp(
139
+ input: CompileIntegrationCspInput,
140
+ ): ContractResult<CompiledIntegrationCsp> {
141
+ const v = new Violations();
142
+
143
+ if (typeof input.planRevision !== "string" || input.planRevision.length === 0) {
144
+ v.add("planRevision", "type", "planRevision must be a non-empty string.");
145
+ }
146
+
147
+ // Advanced declarations: re-validate the whole source set (unknown directives,
148
+ // non-array/-string shapes, non-exact origins, duplicates) even though the
149
+ // resolver already validated `plan.cspNeeds`. A forbidden shape must be caught
150
+ // PRE-RELEASE, not weaken the base at render — this is the fail-closed floor.
151
+ validateCspSourceSet(input.sources, v, "sources");
152
+
153
+ const merged = new Map<IntegrationCspDirective, Set<string>>();
154
+
155
+ // Curated adapters expand first. An unknown slug is a closed-set violation; a
156
+ // known profile's origins are re-validated as defense in depth (the catalog is
157
+ // platform-owned, but the grammar floor is never assumed).
158
+ const profiles = [...new Set(input.profiles)].sort();
159
+ for (const slug of profiles) {
160
+ const profile = INTEGRATION_CSP_PROFILES[slug];
161
+ if (!profile) {
162
+ v.add(
163
+ `profiles.${slug}`,
164
+ "unknown_profile",
165
+ `Unknown CSP profile "${slug}"; curated adapters are a closed, platform-owned set.`,
166
+ );
167
+ continue;
168
+ }
169
+ validateCspSourceSet(profile, v, `profiles.${slug}`);
170
+ mergeSourceSet(profile, merged);
171
+ }
172
+
173
+ // Advanced exact origins merge on top of the expanded profiles.
174
+ mergeSourceSet(input.sources, merged);
175
+
176
+ if (!v.ok) return { ok: false, violations: v.items };
177
+
178
+ const additions: IntegrationCspAdditions = {};
179
+ for (const directive of INTEGRATION_CSP_DIRECTIVES) {
180
+ const set = merged.get(directive);
181
+ if (set && set.size > 0) additions[directive] = [...set].sort();
182
+ }
183
+
184
+ return { ok: true, value: { planRevision: input.planRevision, additions, profiles } };
185
+ }
186
+
187
+ /** {@link compileIntegrationCsp} keyed directly off a resolved effective plan. */
188
+ export function compileIntegrationCspFromPlan(
189
+ plan: EffectivePlan,
190
+ profiles: readonly string[],
191
+ ): ContractResult<CompiledIntegrationCsp> {
192
+ return compileIntegrationCsp({
193
+ profiles,
194
+ sources: plan.cspNeeds,
195
+ planRevision: plan.planRevision,
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Compile for the RENDER hot path: the identical deterministic merge, but a
201
+ * fail-closed result yields EMPTY additions (the strict base, never weakened)
202
+ * instead of violations. A plan reaching render has already passed the promotion
203
+ * gate's {@link compileIntegrationCsp}, so this only ever returns empty as a
204
+ * defensive backstop — it never throws on the request path.
205
+ */
206
+ export function compileIntegrationCspForRender(
207
+ input: CompileIntegrationCspInput,
208
+ ): IntegrationCspAdditions {
209
+ const result = compileIntegrationCsp(input);
210
+ return result.ok ? result.value.additions : {};
211
+ }
212
+
213
+ /**
214
+ * Union the curated profile slugs declared across a plan's input declarations. The
215
+ * control plane collects these alongside resolving the plan (the resolver merges
216
+ * the advanced `sources` into `cspNeeds` but leaves profile EXPANSION to this
217
+ * policy layer, which owns the catalog).
218
+ */
219
+ export function collectDeclaredCspProfiles(
220
+ declarations: readonly IntegrationDeclarationInput[],
221
+ ): string[] {
222
+ const set = new Set<string>();
223
+ for (const decl of declarations) {
224
+ for (const slug of decl.manifest.browser.csp.profiles) set.add(slug);
225
+ }
226
+ return [...set].sort();
227
+ }
228
+
229
+ /**
230
+ * Fold compiled integration additions into an assembled {@link CspOptions}
231
+ * object, APPENDING exact origins to the six contributable directives only. It
232
+ * never touches `default-src`, `base-uri`, `object-src`, `frame-ancestors`,
233
+ * `form-action`, `style-src-attr`, or the nonce/`'self'` seeds `buildCsp` injects
234
+ * — so integration policy can only ever ADD reviewed origins to the strict base,
235
+ * never weaken it. Empty additions return an equivalent options object, so the
236
+ * built header is byte-identical for a tenant with no integration (zero cost when
237
+ * disabled).
238
+ */
239
+ export function foldIntegrationCsp(
240
+ base: CspOptions,
241
+ additions: IntegrationCspAdditions,
242
+ ): CspOptions {
243
+ const next: CspOptions = { ...base };
244
+ const mutable = next as unknown as Record<string, readonly string[] | undefined>;
245
+ for (const directive of INTEGRATION_CSP_DIRECTIVES) {
246
+ const origins = additions[directive];
247
+ if (!origins || origins.length === 0) continue;
248
+ const key = DIRECTIVE_TO_CSP_OPTION[directive];
249
+ const existing = next[key] as readonly string[] | undefined;
250
+ mutable[key] = existing ? [...existing, ...origins] : [...origins];
251
+ }
252
+ return next;
253
+ }
254
+
255
+ /** Merge one exact-origin source set into the per-directive accumulator (post-validation; no revalidation). */
256
+ function mergeSourceSet(
257
+ sources: CspSourceSet,
258
+ merged: Map<IntegrationCspDirective, Set<string>>,
259
+ ): void {
260
+ for (const directive of INTEGRATION_CSP_DIRECTIVES) {
261
+ const origins = sources[directive];
262
+ if (!origins || origins.length === 0) continue;
263
+ let set = merged.get(directive);
264
+ if (!set) merged.set(directive, (set = new Set()));
265
+ for (const origin of origins) set.add(origin);
266
+ }
267
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * CSP source contract for integration declarations (ADR 0014 E2
3
+ * `integration-csp-hybrid`, ADR 0018/0019).
4
+ *
5
+ * A declaration may add exact reviewed origins to a fixed set of directives. It
6
+ * can never replace or weaken the base CSP: wildcards, bare schemes,
7
+ * `unsafe-inline`, `unsafe-eval`, and `strict-dynamic` are outside the grammar
8
+ * and rejected. Nonces are per-response and never serialized, so they never
9
+ * appear here.
10
+ */
11
+ import type { Violations } from "./result.js";
12
+ import { assertUnique, joinPath } from "./result.js";
13
+
14
+ /** The fixed set of directives a declaration may contribute exact origins to. */
15
+ export const INTEGRATION_CSP_DIRECTIVES = [
16
+ "script-src",
17
+ "connect-src",
18
+ "frame-src",
19
+ "img-src",
20
+ "style-src",
21
+ "font-src",
22
+ ] as const;
23
+ export type IntegrationCspDirective = (typeof INTEGRATION_CSP_DIRECTIVES)[number];
24
+
25
+ export type CspSourceSet = Partial<Record<IntegrationCspDirective, string[]>>;
26
+
27
+ const REJECTED_SUBSTRINGS = [
28
+ "*",
29
+ "unsafe-inline",
30
+ "unsafe-eval",
31
+ "strict-dynamic",
32
+ ];
33
+
34
+ const EXACT_ORIGIN = /^https:\/\/[a-z0-9.-]+(:\d+)?(\/[^\s?#*]*)?$/i;
35
+
36
+ /** True for an exact `https://host[:port][/path]` origin with no wildcard, scheme keyword, or query/fragment. */
37
+ export function isExactOrigin(source: string): boolean {
38
+ if (REJECTED_SUBSTRINGS.some((bad) => source.includes(bad))) return false;
39
+ return EXACT_ORIGIN.test(source);
40
+ }
41
+
42
+ export function validateExactOrigin(
43
+ source: string,
44
+ v: Violations,
45
+ path: string,
46
+ ): void {
47
+ if (!isExactOrigin(source)) {
48
+ v.add(
49
+ path,
50
+ "csp_source",
51
+ `"${source}" is not an exact https origin; wildcards, bare schemes, ` +
52
+ `unsafe-inline, unsafe-eval, and strict-dynamic are rejected.`,
53
+ );
54
+ }
55
+ }
56
+
57
+ /** Validate an array of exact CSP origins under `path`, rejecting duplicates. */
58
+ export function validateOriginList(
59
+ sources: readonly string[],
60
+ v: Violations,
61
+ path: string,
62
+ ): void {
63
+ assertUnique(sources, v, path, "CSP origin");
64
+ sources.forEach((source, i) => validateExactOrigin(source, v, `${path}[${i}]`));
65
+ }
66
+
67
+ /**
68
+ * Validate a `{ directive: origin[] }` map: every key must be an allowed
69
+ * directive and every value an array of exact origins.
70
+ */
71
+ export function validateCspSourceSet(
72
+ value: unknown,
73
+ v: Violations,
74
+ path: string,
75
+ ): void {
76
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
77
+ v.add(path, "type", "CSP sources must be an object of directive -> origin[].");
78
+ return;
79
+ }
80
+ const allowed = new Set<string>(INTEGRATION_CSP_DIRECTIVES);
81
+ for (const [directive, sources] of Object.entries(value as Record<string, unknown>)) {
82
+ const directivePath = joinPath(path, directive);
83
+ if (!allowed.has(directive)) {
84
+ v.add(
85
+ directivePath,
86
+ "unknown_field",
87
+ `Unknown CSP directive "${directive}"; free-form directives are rejected.`,
88
+ );
89
+ continue;
90
+ }
91
+ if (!Array.isArray(sources)) {
92
+ v.add(directivePath, "type", "Directive value must be an array of origins.");
93
+ continue;
94
+ }
95
+ const originStrings: string[] = [];
96
+ sources.forEach((source, i) => {
97
+ if (typeof source !== "string") {
98
+ v.add(`${directivePath}[${i}]`, "type", "CSP origin must be a string.");
99
+ return;
100
+ }
101
+ originStrings.push(source);
102
+ });
103
+ validateOriginList(originStrings, v, directivePath);
104
+ }
105
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Synchronous, engine-independent content digests for cache/identity keys.
3
+ *
4
+ * A digest here is an IDENTITY primitive, never a security boundary: `planRevision`
5
+ * and the candidate-evidence served fingerprint both fold the same FNV-1a-128 over a
6
+ * deterministic, key-sorted serialization, so equal content yields a byte-identical
7
+ * digest across engines without an async WebCrypto call.
8
+ */
9
+
10
+ /** Deterministic, key-sorted serialization; `undefined` object fields are dropped. */
11
+ export function stableStringify(value: unknown): string {
12
+ if (value === null || typeof value !== "object") {
13
+ return JSON.stringify(value) ?? "null";
14
+ }
15
+ if (Array.isArray(value)) {
16
+ return `[${value.map(stableStringify).join(",")}]`;
17
+ }
18
+ const obj = value as Record<string, unknown>;
19
+ const keys = Object.keys(obj)
20
+ .filter((k) => obj[k] !== undefined)
21
+ .sort();
22
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
23
+ }
24
+
25
+ const FNV_OFFSET_128 = 0x6c62272e07bb014262b821756295c58dn;
26
+ const FNV_PRIME_128 = 0x0000000001000000000000000000013bn;
27
+ const MASK_128 = (1n << 128n) - 1n;
28
+
29
+ /** FNV-1a 128-bit over the string's UTF-16 code-unit bytes, as a 32-char hex string. */
30
+ export function fnv1a128Hex(input: string): string {
31
+ let hash = FNV_OFFSET_128;
32
+ for (let i = 0; i < input.length; i++) {
33
+ const code = input.charCodeAt(i);
34
+ hash = ((hash ^ BigInt(code & 0xff)) * FNV_PRIME_128) & MASK_128;
35
+ hash = ((hash ^ BigInt((code >> 8) & 0xff)) * FNV_PRIME_128) & MASK_128;
36
+ }
37
+ return hash.toString(16).padStart(32, "0");
38
+ }
39
+
40
+ /** Digest an arbitrary browser-safe value by its deterministic serialization. */
41
+ export function digestValue(value: unknown): string {
42
+ return fnv1a128Hex(stableStringify(value));
43
+ }
@@ -0,0 +1,228 @@
1
+ /**
2
+ * The universal four-stage public browser environment (ADR 0014 E1
3
+ * `integration-environment-universal`, refined by ADR 0017 as a renderer-owned
4
+ * platform block).
5
+ *
6
+ * Every document gets this small renderer-owned object, serialized before app
7
+ * code in all render paths. It carries only public values — never a token,
8
+ * binding, or signed URL. Missing or unknown values are never treated as `live`.
9
+ */
10
+ import type { ContractResult } from "./result.js";
11
+ import {
12
+ Violations,
13
+ reqObject,
14
+ reqEnum,
15
+ reqLiteral,
16
+ reqString,
17
+ rejectUnknownKeys,
18
+ } from "./result.js";
19
+ import { ENVIRONMENT_SCHEMA_VERSION } from "./version.js";
20
+ import { findForbiddenFields } from "./secrets.js";
21
+
22
+ /** The four environment stages. A candidate never reports `live`. */
23
+ export const INTEGRATION_ENVIRONMENTS = [
24
+ "local",
25
+ "candidate",
26
+ "staged",
27
+ "live",
28
+ ] as const;
29
+ export type IntegrationEnvironment = (typeof INTEGRATION_ENVIRONMENTS)[number];
30
+
31
+ /** Document surfaces that receive an environment block. */
32
+ export const INTEGRATION_SITE_SURFACES = [
33
+ "storefront",
34
+ "checkout",
35
+ "admin",
36
+ "marketing",
37
+ ] as const;
38
+ export type IntegrationSiteSurface = (typeof INTEGRATION_SITE_SURFACES)[number];
39
+
40
+ export interface BrowserEnvironment {
41
+ schemaVersion: typeof ENVIRONMENT_SCHEMA_VERSION;
42
+ siteSurface: IntegrationSiteSurface;
43
+ environment: IntegrationEnvironment;
44
+ canonicalHost: string;
45
+ revision: string;
46
+ }
47
+
48
+ const HOSTNAME = /^[a-z0-9.-]+$/i;
49
+
50
+ /** Fail-closed environment parse: unknown or missing values never yield `live`. */
51
+ export function parseEnvironment(value: unknown): IntegrationEnvironment | null {
52
+ return typeof value === "string" &&
53
+ (INTEGRATION_ENVIRONMENTS as readonly string[]).includes(value)
54
+ ? (value as IntegrationEnvironment)
55
+ : null;
56
+ }
57
+
58
+ /** True only for exactly `"live"`; anything unknown is treated as not-live. */
59
+ export function isLiveEnvironment(value: unknown): boolean {
60
+ return value === "live";
61
+ }
62
+
63
+ /**
64
+ * The authoritative server-render signals the stage derivation consumes. Each is
65
+ * a fact the renderer already knows about the request it is answering; none is a
66
+ * client-supplied value.
67
+ */
68
+ export interface IntegrationEnvironmentSignals {
69
+ /** `import.meta.env.DEV` — the `astro dev` (Node) render. CSP is report-only here. */
70
+ dev: boolean;
71
+ /** The request host is `localhost`/`127.0.0.1`: a locally-served built Worker. */
72
+ localHost: boolean;
73
+ /** This request is a per-change candidate render (`/preview/render/…`, revision, PR). */
74
+ candidate: boolean;
75
+ /** The request host is a `preview.` host. */
76
+ previewHost: boolean;
77
+ /** The resolved customization environment: `live` production vs `test` preview. */
78
+ customizationEnv: "live" | "test";
79
+ }
80
+
81
+ /**
82
+ * Derive the authoritative stage from server-render signals. The order is
83
+ * fail-closed and candidate-never-live:
84
+ * - a per-change candidate render is always `candidate` (never `live`, even on a
85
+ * live host or in dev);
86
+ * - a local render (`astro dev` OR a Worker served on localhost) is `local`;
87
+ * - a preview/test surface is `staged`;
88
+ * - only a genuine live production read is `live`.
89
+ * Any shape the branches do not reach stays `staged` — a non-live default, so an
90
+ * unforeseen signal combination can never be mistaken for production.
91
+ */
92
+ export function deriveIntegrationEnvironment(
93
+ signals: IntegrationEnvironmentSignals,
94
+ ): IntegrationEnvironment {
95
+ if (signals.candidate) return "candidate";
96
+ if (signals.dev || signals.localHost) return "local";
97
+ if (signals.previewHost || signals.customizationEnv === "test") return "staged";
98
+ if (signals.customizationEnv === "live") return "live";
99
+ return "staged";
100
+ }
101
+
102
+ /** Reduce a host (possibly with scheme/port/path) to the bare hostname the contract requires. */
103
+ function bareHostname(value: string): string {
104
+ const noScheme = value.trim().replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
105
+ const hostAndPort = noScheme.split("/")[0] ?? noScheme;
106
+ return hostAndPort.split(":")[0] ?? hostAndPort;
107
+ }
108
+
109
+ /** Everything the renderer needs to build the immutable environment object. */
110
+ export interface BrowserEnvironmentInput extends IntegrationEnvironmentSignals {
111
+ siteSurface: IntegrationSiteSurface;
112
+ /** The canonical host; scheme/port/path are stripped to a bare hostname. */
113
+ canonicalHost: string;
114
+ /** A non-secret render revision (deployed code sha, candidate versionId, …). */
115
+ revision: string;
116
+ }
117
+
118
+ /**
119
+ * Build the renderer-owned, immutable environment object from authoritative
120
+ * server state. The result is frozen so no downstream code can mutate the stage
121
+ * after the renderer has fixed it; it carries only the closed, public shape —
122
+ * never a token, binding, or signed URL (that is impossible by construction, the
123
+ * shape has no such field).
124
+ */
125
+ export function resolveBrowserEnvironment(
126
+ input: BrowserEnvironmentInput,
127
+ ): BrowserEnvironment {
128
+ return Object.freeze({
129
+ schemaVersion: ENVIRONMENT_SCHEMA_VERSION,
130
+ siteSurface: input.siteSurface,
131
+ environment: deriveIntegrationEnvironment(input),
132
+ canonicalHost: bareHostname(input.canonicalHost),
133
+ revision: input.revision,
134
+ });
135
+ }
136
+
137
+ /** The browser global the environment object is published as, frozen, in every document. */
138
+ export const BROWSER_ENVIRONMENT_GLOBAL = "__TOT_INTEGRATION_ENV__";
139
+
140
+ /**
141
+ * Serialize an arbitrary browser-safe value to a JSON literal safe to embed in an
142
+ * inline `<script>`: the HTML-significant characters `<`, `>`, `&` and the JS line
143
+ * terminators U+2028/U+2029 are `\uXXXX`-escaped so the payload can neither break
144
+ * out of the surrounding `<script>` nor be mis-scanned as a nested tag by a raw-mount
145
+ * nonce stamper. This is the ONE serializer every renderer-owned inline global shares.
146
+ */
147
+ export function serializeForInlineScript(value: unknown): string {
148
+ // `<` `>` `&` `U+2028` `U+2029` — escaped by code point so no line terminator or
149
+ // HTML-significant character is ever emitted literally into the <script>.
150
+ const escaped = new Set([0x3c, 0x3e, 0x26, 0x2028, 0x2029]);
151
+ let out = "";
152
+ for (const ch of JSON.stringify(value)) {
153
+ const code = ch.charCodeAt(0);
154
+ out += escaped.has(code) ? `\\u${code.toString(16).padStart(4, "0")}` : ch;
155
+ }
156
+ return out;
157
+ }
158
+
159
+ /**
160
+ * Serialize the environment object to an XSS-safe JSON literal with a fixed key
161
+ * order (so both document mounts emit byte-identical bytes for the same object).
162
+ */
163
+ export function serializeBrowserEnvironment(env: BrowserEnvironment): string {
164
+ return serializeForInlineScript({
165
+ schemaVersion: env.schemaVersion,
166
+ siteSurface: env.siteSurface,
167
+ environment: env.environment,
168
+ canonicalHost: env.canonicalHost,
169
+ revision: env.revision,
170
+ });
171
+ }
172
+
173
+ /**
174
+ * The single serialization engine both document mounts use (ADR 0015, one engine,
175
+ * two mounts): emit the frozen environment object as the renderer-owned global,
176
+ * before any tenant/app code. The `nonce` is a renderer-applied SCRIPT ATTRIBUTE —
177
+ * never part of the serialized object (ADR 0018 nonce non-serialization). Pass an
178
+ * empty nonce on the raw mount, where the shared nonce stamper applies it uniformly.
179
+ */
180
+ export function renderBrowserEnvironmentScript(
181
+ env: BrowserEnvironment,
182
+ nonce: string,
183
+ ): string {
184
+ const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
185
+ return `<script${nonceAttr}>window.${BROWSER_ENVIRONMENT_GLOBAL}=Object.freeze(${serializeBrowserEnvironment(env)});</script>`;
186
+ }
187
+
188
+ export function validateBrowserEnvironment(
189
+ input: unknown,
190
+ ): ContractResult<BrowserEnvironment> {
191
+ const v = new Violations();
192
+ const obj = reqObject(input, v, "");
193
+ if (!obj) return { ok: false, violations: v.items };
194
+
195
+ rejectUnknownKeys(
196
+ obj,
197
+ ["schemaVersion", "siteSurface", "environment", "canonicalHost", "revision"],
198
+ v,
199
+ "",
200
+ );
201
+
202
+ const schemaVersion = reqLiteral(obj, "schemaVersion", ENVIRONMENT_SCHEMA_VERSION, v, "");
203
+ const siteSurface = reqEnum(obj, "siteSurface", INTEGRATION_SITE_SURFACES, v, "");
204
+ const environment = reqEnum(obj, "environment", INTEGRATION_ENVIRONMENTS, v, "");
205
+ const canonicalHost = reqString(obj, "canonicalHost", v, "");
206
+ const revision = reqString(obj, "revision", v, "");
207
+
208
+ if (canonicalHost !== undefined && !HOSTNAME.test(canonicalHost)) {
209
+ v.add("canonicalHost", "format", "canonicalHost must be a bare hostname (no scheme or path).");
210
+ }
211
+
212
+ for (const violation of findForbiddenFields(obj)) v.items.push(violation);
213
+
214
+ if (
215
+ !v.ok ||
216
+ schemaVersion === undefined ||
217
+ siteSurface === undefined ||
218
+ environment === undefined ||
219
+ canonicalHost === undefined ||
220
+ revision === undefined
221
+ ) {
222
+ return { ok: false, violations: v.items };
223
+ }
224
+ return {
225
+ ok: true,
226
+ value: { schemaVersion, siteSurface, environment, canonicalHost, revision },
227
+ };
228
+ }