@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
@@ -32,6 +32,10 @@ import {
32
32
  STOREFRONT_CSP_ALLOWLIST,
33
33
  checkoutCspAdditions,
34
34
  appWidgetCspAdditions,
35
+ embedCspAdditions,
36
+ foldIntegrationCsp,
37
+ embedsArtifactPath,
38
+ resolveEffectiveEmbeds,
35
39
  CSP_REPORT_GROUP,
36
40
  CSP_REPORT_PATH,
37
41
  readPublishedArtifact,
@@ -43,23 +47,35 @@ import {
43
47
  resolveServableChannelVersion,
44
48
  channelFromEnv,
45
49
  serveStaticBundle,
50
+ resolveEnvironmentOnlyPlan,
51
+ resolveInterimBridgePlan,
52
+ compileIntegrationCspFromPlan,
53
+ collectDeclaredCspProfiles,
54
+ INTEGRATION_NONCE_PLACEHOLDER,
46
55
  } from "@tot/public-runtime";
47
- import type { CapabilityConfig, StaticBundleR2BucketLike } from "@tot/public-runtime";
56
+ import type { CapabilityConfig, StaticBundleR2BucketLike, BrowserEnvironment } from "@tot/public-runtime";
57
+ import { loadTenantIntegrationDeclarations } from "@/lib/integration/tenantDeclarations";
58
+ import { loadActiveManagedInstallDeclarations } from "@/lib/integration/managedInstallDeclarations";
59
+ import { fromD1 } from "@/lib/d1/catalog";
48
60
  import { resolveTenantCommerce } from "@/lib/checkoutCommerce";
49
61
  import { evaluateDemoGate, hostInGateList } from "@/lib/demoGate";
50
62
  import {
51
63
  evaluateLoginGate,
52
64
  parseGateMode,
53
65
  isAuthPath,
66
+ isAdminPath,
54
67
  sessionAdmitsHost,
55
68
  sessionHostCapability,
56
69
  PUBLIC_HOME_TENANT_ID,
57
70
  } from "@/lib/auth/loginGate";
71
+ import { resolveAdminLanding, isAdminSelectPath } from "@/lib/auth/adminLanding";
58
72
  import { gatedHoldingResponse } from "@/lib/auth/gatePage";
59
73
  import { readSession, KvSessionStore, SESSION_COOKIE } from "@/lib/auth/session";
60
74
  import { isMaintenanceOn, maintenanceResponse } from "@/lib/maintenance";
61
75
  import { HEALTH_PATH, healthResponse } from "@/lib/health";
62
76
  import { observeDeployVersion } from "@/lib/activity/deployVersion";
77
+ import { requestBrowserEnvironment, deriveSiteSurface, cspHeaderName } from "@/lib/integration/browserEnvironment";
78
+ import { freshNonce, stampResponseNonce } from "@/lib/integration/nonceStamp";
63
79
  import { recordActivity } from "@/lib/activity/recordActivity";
64
80
  import { primeActivityKillSwitch } from "@/lib/activity/killSwitch";
65
81
  import type { DeepPartial, ThemeTokens } from "@tot/public-runtime";
@@ -70,19 +86,19 @@ export const onRequest = defineMiddleware(async (context, next) => {
70
86
  const host =
71
87
  request.headers.get("host") ?? url.host ?? "localhost";
72
88
 
73
- // ---- Activity telemetry kill switch (card D8) -----------------------------
89
+ // ---- Activity telemetry kill switch -----------------------------
74
90
  // Prime the ONE `ACTIVITY_TELEMETRY_DISABLED` flag from the (async) Worker env
75
91
  // once per isolate, BEFORE the first emit, so every telemetry chokepoint in this
76
92
  // isolate (deploy-provenance, recordActivity, ingest) observes it. Fail-open,
77
93
  // never throws. Off by default (unset ⇒ telemetry on).
78
94
  await primeActivityKillSwitch();
79
95
 
80
- // ---- Deploy provenance (card D2) — worker_commit / deploy.version_observed -
96
+ // ---- Deploy provenance — worker_commit / deploy.version_observed -
81
97
  // Once per worker isolate (cold start), emit `deploy.version_observed` carrying
82
- // this build's commit SHA so D5 can derive deploy skew (running build vs umbrella
83
- // tip). Idempotent per isolate, best-effort, never throws — so it's safe here at
84
- // the very front door, ahead of /health, and fires regardless of Host/tenant/gate.
85
- // A no-op when the D8 kill switch above is set.
98
+ // this build's commit SHA for downstream deploy-skew derivation (running build vs
99
+ // umbrella tip). Idempotent per isolate, best-effort, never throws — so it's safe
100
+ // here at the very front door, ahead of /health, and fires regardless of
101
+ // Host/tenant/gate. A no-op when the kill switch above is set.
86
102
  observeDeployVersion();
87
103
 
88
104
  // ---- /health — liveness + deploy-identity probe ---------------------------
@@ -178,17 +194,59 @@ export const onRequest = defineMiddleware(async (context, next) => {
178
194
  const bypassToken = await readEnv("DEMO_GATE_BYPASS_TOKEN");
179
195
  const qaBypass =
180
196
  !!bypassToken && request.headers.get("x-demo-gate-bypass") === bypassToken;
197
+
198
+ // Canonical admin tenant landing: an AUTHENTICATED viewer who is not a
199
+ // member/staff of THIS host's tenant must be routed to their OWN tenant
200
+ // (auto when sole) or the tenant picker (when several / none) — never bounced
201
+ // into a sign-in loop and never silently shown the wrong tenant. Only for
202
+ // admin paths, only for an authed non-member, and never on the /admin/select
203
+ // picker itself (whose whole purpose is to be reachable by such a viewer).
204
+ if (
205
+ isAdminPath(url.pathname) &&
206
+ !isAdminSelectPath(url.pathname) &&
207
+ !admitted &&
208
+ viewerRecord &&
209
+ !qaBypass
210
+ ) {
211
+ const landing = resolveAdminLanding({
212
+ record: viewerRecord,
213
+ routedTenant: tenant.appDomain,
214
+ basePath,
215
+ intended: url.pathname + url.search,
216
+ nowSeconds,
217
+ });
218
+ if (landing.action === "auto" || landing.action === "select") {
219
+ return context.redirect(landing.location, 303);
220
+ }
221
+ }
222
+
223
+ // The /admin/select picker requires only a VALID SESSION (any authed viewer),
224
+ // not membership of the routed tenant — it lists the viewer's OWN tenants. So
225
+ // it passes the gate on the strength of the session alone.
226
+ const hasValidSession =
227
+ admitted || (isAdminSelectPath(url.pathname) && !!viewerRecord);
181
228
  const decision = evaluateLoginGate({
182
229
  host,
183
230
  pathname: url.pathname,
184
231
  search: url.search,
185
232
  mode: gateMode,
186
233
  gatedHosts: demoGateHosts,
187
- hasValidSession: admitted,
234
+ hasValidSession,
188
235
  tenantExempt,
189
236
  qaBypass,
190
237
  });
191
238
  if (decision.action === "redirect") {
239
+ // ADMIN (and any privileged surface) hard-redirects to sign-in rather than
240
+ // rendering the merchant-facing "Coming soon" holding page: an unauthenticated
241
+ // admin visitor must land on the sign-in prompt, never an ambiguous 200 that
242
+ // reads as "this store isn't ready." The holding page's whole audience is a
243
+ // public browser of a gated STOREFRONT; it is the wrong signal for /admin. The
244
+ // gate already decided to withhold — this only changes HOW (303 vs holding
245
+ // page); it never weakens the gate. `decision.location` already carries
246
+ // `?next=` so the admin returns to exactly where they were headed.
247
+ if (isAdminPath(url.pathname)) {
248
+ return context.redirect(decision.location, 303);
249
+ }
192
250
  // Withhold content but offer sign-in (carrying the intended destination).
193
251
  return gatedHoldingResponse(decision.location);
194
252
  }
@@ -198,7 +256,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
198
256
  // in: a member sees their tenants[] role; the owner of their own tenant sees
199
257
  // the resource-scoped session's capability; a ToT-staff viewer admitted via
200
258
  // an explicit vendor selection sees the staffRoles-scoped capability. The
201
- // ship gates (u10 decideIsOwner/resolveShipPrincipal) read this capability,
259
+ // ship gates (decideIsOwner/resolveShipPrincipal) read this capability,
202
260
  // so binding it here keeps owner-resolution server-side + tenant-correct.
203
261
  const hostCapability = sessionHostCapability(viewerRecord, tenant.appDomain, nowSeconds);
204
262
  locals.viewer = {
@@ -214,12 +272,26 @@ export const onRequest = defineMiddleware(async (context, next) => {
214
272
  const isPreviewHost = host.toLowerCase().startsWith("preview.");
215
273
  const queryToken = url.searchParams.get("__preview");
216
274
  const cookieToken = context.cookies.get("tot_preview")?.value ?? null;
217
- // Per-developer AI-edit draft (decision draft-overlay-same-origin): the signed
275
+ // Per-developer AI-edit draft: the signed
218
276
  // tot_dev_draft cookie's mere PRESENCE opts the request out of the shared page
219
277
  // cache below (a draft view must never be cached publicly); it's verified —
220
278
  // signature, expiry, tenant binding — before the overlay is applied further down.
221
279
  const draftCookie = context.cookies.get(DEV_DRAFT_COOKIE)?.value ?? null;
222
280
 
281
+ // Active managed installs share the same declaration/resolver/CSP/broker/cache
282
+ // plane as tenant-config integrations. Read only canonical active rows and their
283
+ // current immutable snapshot; candidates, suspended rows, and drafts produce no
284
+ // declaration. A missing/unmigrated binding fails closed to no managed effects.
285
+ const appsD1 = await readD1("STOREFRONT_APPS_DB");
286
+ let managedIntegrationDeclarations = [] as Awaited<ReturnType<typeof loadActiveManagedInstallDeclarations>>;
287
+ if (appsD1) {
288
+ try {
289
+ managedIntegrationDeclarations = await loadActiveManagedInstallDeclarations(fromD1(appsD1), tenant.tenant_id);
290
+ } catch {
291
+ managedIntegrationDeclarations = [];
292
+ }
293
+ }
294
+
223
295
  // ---- Digest-addressed static-bundle serving (ADR 0012) --------------------
224
296
  // For a static-publish tenant's PRODUCTION reads only (never preview/test — the
225
297
  // "Worker serves interactive preview, R2 serves prod" split), read the live
@@ -242,6 +314,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
242
314
  !queryToken &&
243
315
  !cookieToken &&
244
316
  !draftCookie &&
317
+ managedIntegrationDeclarations.length === 0 &&
245
318
  !url.pathname.startsWith("/api/");
246
319
  if (staticBundleEligible) {
247
320
  const bundleBucket = await readR2("STATIC_BUNDLES");
@@ -293,7 +366,62 @@ export const onRequest = defineMiddleware(async (context, next) => {
293
366
  // usable without provisioning R2.
294
367
  }
295
368
 
296
- // ---- Edge page cache (D1): short-circuit production browse reads on a hit,
369
+ // ---- Tenant-local integration materialization (ext-kernel-09) --------------
370
+ // Resolve a tenant's OWN declared, exact-version integrations into the effective
371
+ // plan the render engine serves, THROUGH the interim marketing bridge (which
372
+ // refuses a regulated tenant and fail-closes an invalid declaration set). Done
373
+ // HERE, before the edge-cache read, so a declared tenant's `planRevision` folds
374
+ // into the page-cache key below (a rollback/uninstall resolves a new revision →
375
+ // the old warm entry is unreachable → a clean re-render without the removed
376
+ // origins, never a TTL wait) AND its reviewed CSP origins reach the strict header.
377
+ //
378
+ // An UNDECLARED tenant does NONE of this: `loadTenantIntegrationDeclarations`
379
+ // returns the empty set, so `integrationCsp` stays undefined (the page-cache key
380
+ // is byte-identical to the non-integration form) and the environment-only plan is
381
+ // resolved below exactly as before — zero cost.
382
+ let resolvedBrowserEnvironment: BrowserEnvironment | null = null;
383
+ const integrationDeclarations = [
384
+ ...loadTenantIntegrationDeclarations(tenant),
385
+ ...managedIntegrationDeclarations,
386
+ ];
387
+ if (integrationDeclarations.length > 0) {
388
+ const declPreviewSecret = (await readEnv("CUSTOMIZATION_PREVIEW_SECRET")) ?? null;
389
+ const declEnv = await resolveCustomizationEnv({
390
+ isPreviewHost,
391
+ tenantId: tenant.tenant_id,
392
+ token: queryToken ?? cookieToken,
393
+ secret: declPreviewSecret,
394
+ nowSeconds: Math.floor(Date.now() / 1000),
395
+ pathname: url.pathname,
396
+ });
397
+ const declFeatures = resolveTenantFeatures(tenant);
398
+ resolvedBrowserEnvironment = requestBrowserEnvironment({
399
+ host,
400
+ isPreviewHost,
401
+ customizationEnv: declEnv.env,
402
+ canonicalHost: tenant.canonicalDomain || host,
403
+ siteSurface: deriveSiteSurface({ catalog: declFeatures.catalog, pathname: renderPath }),
404
+ });
405
+ const bridge = resolveInterimBridgePlan({
406
+ tenantPublicId: tenant.tenant_id,
407
+ tenant,
408
+ environment: resolvedBrowserEnvironment,
409
+ declarations: integrationDeclarations,
410
+ });
411
+ if (bridge.ok) {
412
+ // A resolved plan serves; its compiled CSP additions + `planRevision` reach the
413
+ // cache key and the header. A refusal (regulated) or an invalid set leaves the
414
+ // plan unset here → the environment-only fallback below fills it (nothing loads).
415
+ locals.integrationPlan = bridge.plan;
416
+ const compiled = compileIntegrationCspFromPlan(
417
+ bridge.plan,
418
+ collectDeclaredCspProfiles(integrationDeclarations),
419
+ );
420
+ if (compiled.ok) locals.integrationCsp = compiled.value;
421
+ }
422
+ }
423
+
424
+ // ---- Edge page cache: short-circuit production browse reads on a hit,
297
425
  // BEFORE building the client / theme / rendering. Misses store the final
298
426
  // response at the end of this middleware.
299
427
  //
@@ -312,12 +440,27 @@ export const onRequest = defineMiddleware(async (context, next) => {
312
440
  let pageKey: Request | null = null;
313
441
  if (pageCacheEligible && cache && kv) {
314
442
  const version = await readCacheVersion(kv, tenant.tenant_id);
315
- pageKey = pageCacheKey(tenant.tenant_id, url.pathname, url.search, version);
443
+ // Fold the resolved integration plan revision into the key so a warm entry is
444
+ // version-scoped: a rollback/uninstall resolves a new revision → old entry
445
+ // unreachable → clean re-render without the removed origins (never a TTL wait).
446
+ // Undefined until the integration plane is resolved for this tenant — the key
447
+ // is then byte-identical to the non-integration form. The store below reuses
448
+ // this same `pageKey`, so read and write stay consistent.
449
+ pageKey = pageCacheKey(
450
+ tenant.tenant_id,
451
+ url.pathname,
452
+ url.search,
453
+ version,
454
+ locals.integrationCsp?.planRevision,
455
+ );
316
456
  const hit = await getCachedPage(cache, pageKey);
317
- if (hit) return hit;
457
+ // The cached artifact is nonce-free (carries the placeholder). Stamp a fresh
458
+ // per-response nonce into its body + CSP header so a warm hit is served with a
459
+ // brand-new nonce, never the one an earlier response rendered with.
460
+ if (hit) return await stampResponseNonce(hit, freshNonce());
318
461
  }
319
462
 
320
- // Catalog source selection (F17 graduation path — docs/architecture/product-catalog.md).
463
+ // Catalog source selection (docs/architecture/product-catalog.md).
321
464
  // A store's product source graduates over its life: JSON fixtures (dev default) ->
322
465
  // D1 edge replica (staging) -> hosted ToT catalog (go-live). Here we pick the read
323
466
  // backend: USE_D1_CATALOG=1 (+ a CATALOG_DB binding) reads the D1 edge replica;
@@ -337,7 +480,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
337
480
  ? `${proto}://${host}${basePath}`
338
481
  : `https://${tenant.canonicalDomain}`;
339
482
 
340
- // Live vs test customization (D4). Production hosts always serve "live" and
483
+ // Live vs test customization. Production hosts always serve "live" and
341
484
  // ignore any token; a "preview." host is auth-gated by a signed, tenant-bound,
342
485
  // short-TTL preview token (?__preview= on first hit, then the tot_preview
343
486
  // cookie). Test is never indexable (X-Robots-Tag below).
@@ -459,9 +602,34 @@ export const onRequest = defineMiddleware(async (context, next) => {
459
602
  }
460
603
  const capabilities = resolveCapabilities(tenant, publishedCaps);
461
604
 
462
- // Per-request CSP nonce (D2): goes on the platform's own inline tags in
463
- // Layout.astro so a strict CSP can allow them without 'unsafe-inline'.
464
- const cspNonce = crypto.randomUUID();
605
+ // Embed opt-in (self-serve, config-as-code on the same spine as capabilities):
606
+ // the build-time TenantConfig.embeds default is overlaid with the tenant's
607
+ // published embeds.json for THIS env, then filtered to the vetted catalog. A
608
+ // tenant enables a catalogued embed by committing embeds.json — no platform
609
+ // change — but can never introduce a non-catalog origin.
610
+ let publishedEmbeds: string[] | null = null;
611
+ const publishedEmbedsRaw = await readPublishedArtifact(
612
+ kvGet,
613
+ tenant.tenant_id,
614
+ customization.env,
615
+ embedsArtifactPath(tenant.tenant_id),
616
+ );
617
+ if (publishedEmbedsRaw) {
618
+ try {
619
+ const parsed = JSON.parse(publishedEmbedsRaw);
620
+ if (Array.isArray(parsed)) publishedEmbeds = parsed.filter((s) => typeof s === "string");
621
+ } catch {
622
+ /* malformed published artifact — keep the build-time default */
623
+ }
624
+ }
625
+ const effectiveEmbeds = resolveEffectiveEmbeds(tenant.embeds, publishedEmbeds);
626
+
627
+ // Nonce placeholder: the platform's inline tags AND the CSP header are rendered
628
+ // with this sentinel, never a live nonce (ADR 0018). A fresh per-response nonce
629
+ // is stamped in at serve time (stampResponseNonce below), so the cached artifact
630
+ // is nonce-free and every response — cold render or warm cache hit — gets a fresh
631
+ // matching nonce/CSP pair.
632
+ const cspNonce = INTEGRATION_NONCE_PLACEHOLDER;
465
633
 
466
634
  locals.tenant = tenant;
467
635
  // Effective capability flags for this tenant (commerce defaults unless the
@@ -504,6 +672,31 @@ export const onRequest = defineMiddleware(async (context, next) => {
504
672
  // tenant-agnostic; the Layout uses this to keep root-absolute links inside
505
673
  // the tenant prefix on the client.
506
674
  locals.basePath = basePath;
675
+ // Renderer-owned four-stage browser environment (ADR 0014 E1 / 0017), computed
676
+ // ONCE here from authoritative request state and serialized before app code by
677
+ // both document mounts (Layout.astro + rawHtmlResponse). The candidate render
678
+ // paths build their own `candidate`-stage object instead of this one.
679
+ // Reuse the environment resolved for a declared tenant's integration plane above
680
+ // (identical inputs → identical object) so the plan's environment and this object
681
+ // never diverge; an undeclared tenant computes it here for the first time.
682
+ locals.browserEnvironment =
683
+ resolvedBrowserEnvironment ??
684
+ requestBrowserEnvironment({
685
+ host,
686
+ isPreviewHost,
687
+ customizationEnv: customization.env,
688
+ canonicalHost: tenant.canonicalDomain || host,
689
+ siteSurface: deriveSiteSurface({ catalog: locals.features.catalog, pathname: renderPath }),
690
+ });
691
+ // Resolved effective plan (ext-kernel-03) for this request — the fixed-phase
692
+ // render engine (ext-kernel-04) consumes it in BOTH document mounts (Layout.astro
693
+ // + rawHtmlResponse). A declared tenant's plan is already resolved above (through
694
+ // the interim bridge); an undeclared tenant — or one the bridge refused / failed
695
+ // closed — resolves the environment-only plan here, so a page with no serving
696
+ // integration emits no vendor runtime/markup.
697
+ locals.integrationPlan =
698
+ locals.integrationPlan ??
699
+ resolveEnvironmentOnlyPlan(locals.browserEnvironment, tenant.tenant_id);
507
700
 
508
701
  // /dev was renamed to /cockpit (2026-07-20). 302-redirect any old /dev link —
509
702
  // host-routed (/dev) or path-routed (/<vendor>/dev) — to the new path, preserving
@@ -532,7 +725,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
532
725
  nonce: cspNonce,
533
726
  });
534
727
 
535
- // Baseline security headers (S1) on every response — set before the page-cache
728
+ // Baseline security headers on every response — set before the page-cache
536
729
  // store below so cached copies carry them too.
537
730
  applySecurityHeaders(response.headers);
538
731
 
@@ -556,9 +749,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
556
749
  // site unstyled. The BUILD is CSP-clean (styles are externalized via
557
750
  // `inlineStylesheets:"never"`), so we enforce there and only REPORT in dev —
558
751
  // violations are still surfaced, but styling/HMR work locally.
559
- const cspHeader = import.meta.env.DEV
560
- ? "Content-Security-Policy-Report-Only"
561
- : "Content-Security-Policy";
752
+ const cspHeader = cspHeaderName(import.meta.env.DEV);
562
753
  response.headers.set(
563
754
  "Reporting-Endpoints",
564
755
  `${CSP_REPORT_GROUP}="${CSP_REPORT_PATH}"`,
@@ -567,40 +758,66 @@ export const onRequest = defineMiddleware(async (context, next) => {
567
758
  // strict policy only when this tenant has a store — so add-to-cart, the
568
759
  // sidecart iframe/XHR, and the cross-origin form POST all pass.
569
760
  const checkoutCsp = commerce ? checkoutCspAdditions(commerce.origin) : null;
570
- // Private-app widget origins (D5) — read back AFTER next() so this reflects
761
+ // Private-app widget origins — read back AFTER next() so this reflects
571
762
  // exactly the placements that rendered on THIS page, never a wildcard.
572
763
  const widgetCsp = appWidgetCspAdditions(locals.activeWidgetPlacements ?? []);
764
+ // Third-party marketing embeds (e.g. a Pipedrive form) the tenant opted into
765
+ // via .tot/config.json `embeds:`. Only scan the HTML body when the tenant
766
+ // declared at least one embed — zero cost for everyone else — and add each
767
+ // provider's origins ONLY if its marker is actually on THIS page.
768
+ // Prefer an opt-in the render handler resolved for a tenant the URL router
769
+ // couldn't (the candidate renderer sets locals.embedOptIn — see candidateRender);
770
+ // otherwise use the router-resolved tenant's effective embeds.
771
+ const embedsForScan = locals.embedOptIn ?? effectiveEmbeds;
772
+ let embedCsp = { scriptSrc: [] as string[], connectSrc: [] as string[], frameSrc: [] as string[] };
773
+ if (
774
+ embedsForScan.length > 0 &&
775
+ (response.headers.get("content-type") ?? "").includes("text/html")
776
+ ) {
777
+ embedCsp = embedCspAdditions(embedsForScan, await response.clone().text());
778
+ }
779
+ const cspOptions = {
780
+ nonce: cspNonce,
781
+ ...STOREFRONT_CSP_ALLOWLIST,
782
+ // Pin Astro's static inline hydration scripts + island style by hash
783
+ // (they're identical every request); my dynamic inline tags use the nonce.
784
+ scriptSrc: [
785
+ ...STOREFRONT_CSP_ALLOWLIST.scriptSrc,
786
+ ...STATIC_SCRIPT_HASHES,
787
+ ...(checkoutCsp?.scriptSrc ?? []),
788
+ ...embedCsp.scriptSrc,
789
+ ],
790
+ styleSrc: [
791
+ ...STOREFRONT_CSP_ALLOWLIST.styleSrc,
792
+ ...STATIC_STYLE_HASHES,
793
+ ...(checkoutCsp?.styleSrc ?? []),
794
+ ],
795
+ fontSrc: [...STOREFRONT_CSP_ALLOWLIST.fontSrc, ...(checkoutCsp?.fontSrc ?? [])],
796
+ connectSrc: [
797
+ ...STOREFRONT_CSP_ALLOWLIST.connectSrc,
798
+ ...(checkoutCsp?.connectSrc ?? []),
799
+ ...embedCsp.connectSrc,
800
+ ],
801
+ imgSrc: [...STOREFRONT_CSP_ALLOWLIST.imgSrc, ...(checkoutCsp?.imgSrc ?? [])],
802
+ frameSrc: [
803
+ ...(checkoutCsp?.frameSrc ?? []),
804
+ ...widgetCsp.frameSrc,
805
+ ...embedCsp.frameSrc,
806
+ ],
807
+ formAction: checkoutCsp?.formAction ?? [],
808
+ reportGroup: CSP_REPORT_GROUP,
809
+ reportUri: CSP_REPORT_PATH,
810
+ };
811
+ // Version-scoped integration plane: APPEND the resolved plan's compiled origins
812
+ // (curated adapter profiles + advanced exact origins) to the six contributable
813
+ // directives only — never the base. Absent (no integration) this is a no-op, so
814
+ // the header is byte-identical to a non-integration tenant's.
573
815
  response.headers.set(
574
816
  cspHeader,
575
- buildCsp({
576
- nonce: cspNonce,
577
- ...STOREFRONT_CSP_ALLOWLIST,
578
- // Pin Astro's static inline hydration scripts + island style by hash
579
- // (they're identical every request); my dynamic inline tags use the nonce.
580
- scriptSrc: [
581
- ...STOREFRONT_CSP_ALLOWLIST.scriptSrc,
582
- ...STATIC_SCRIPT_HASHES,
583
- ...(checkoutCsp?.scriptSrc ?? []),
584
- ],
585
- styleSrc: [
586
- ...STOREFRONT_CSP_ALLOWLIST.styleSrc,
587
- ...STATIC_STYLE_HASHES,
588
- ...(checkoutCsp?.styleSrc ?? []),
589
- ],
590
- fontSrc: [...STOREFRONT_CSP_ALLOWLIST.fontSrc, ...(checkoutCsp?.fontSrc ?? [])],
591
- connectSrc: [
592
- ...STOREFRONT_CSP_ALLOWLIST.connectSrc,
593
- ...(checkoutCsp?.connectSrc ?? []),
594
- ],
595
- imgSrc: [...STOREFRONT_CSP_ALLOWLIST.imgSrc, ...(checkoutCsp?.imgSrc ?? [])],
596
- frameSrc: [...(checkoutCsp?.frameSrc ?? []), ...widgetCsp.frameSrc],
597
- formAction: checkoutCsp?.formAction ?? [],
598
- reportGroup: CSP_REPORT_GROUP,
599
- reportUri: CSP_REPORT_PATH,
600
- }),
817
+ buildCsp(foldIntegrationCsp(cspOptions, locals.integrationCsp?.additions ?? {})),
601
818
  );
602
819
 
603
- // D1: store the fully-rendered, CSP-stamped response for the next reader. Only
820
+ // Store the fully-rendered, CSP-stamped response for the next reader. Only
604
821
  // production (no preview token reached here) cacheable GETs have a pageKey;
605
822
  // putCachedPage additionally skips non-200, non-HTML, and cookie-bearing
606
823
  // responses. Also expose Cache-Control to any fronting CDN.
@@ -609,7 +826,12 @@ export const onRequest = defineMiddleware(async (context, next) => {
609
826
  "Cache-Control",
610
827
  "public, s-maxage=300, stale-while-revalidate=86400",
611
828
  );
829
+ // Store the nonce-free artifact (the placeholder is still in the body + CSP
830
+ // header) BEFORE stamping — so the cache never holds a live/reusable nonce.
612
831
  await putCachedPage(cache, pageKey, response);
613
832
  }
614
- return response;
833
+ // Stamp a fresh per-response nonce into this cold response's body + CSP header
834
+ // (the warm-hit path above stamps its own). Both paths therefore serve a fresh,
835
+ // matching nonce/CSP pair, and no nonce is reused across responses (ADR 0018).
836
+ return await stampResponseNonce(response, freshNonce());
615
837
  });
@@ -43,8 +43,13 @@ const rawHtml = await content.getPageHtml(slug);
43
43
  if (rawHtml && rawHtmlPolicy(tenant).allowed) {
44
44
  // Shared chrome: a body-only fragment gets wrapped in the tenant's chrome.html;
45
45
  // a full document serves verbatim (backward-compatible). See rawMarketingHtml.ts.
46
- const chromeWrapper = await content.getChromeHtml();
47
- return rawHtmlResponse(rawHtml, { nonce: Astro.locals.cspNonce, basePath, chromeWrapper });
46
+ const chromeWrapper = await content.getChromeHtml(`/${slug}`);
47
+ return rawHtmlResponse(rawHtml, {
48
+ nonce: Astro.locals.cspNonce,
49
+ basePath,
50
+ chromeWrapper,
51
+ plan: Astro.locals.integrationPlan,
52
+ });
48
53
  }
49
54
 
50
55
  const page = await content.getPage(slug);
@@ -59,7 +64,7 @@ const description =
59
64
  page.seoDescription ||
60
65
  (page.intro ? truncate(page.intro, 160) : undefined);
61
66
 
62
- // Structured pages (F4) drive the body from `sections`; legacy pages keep the
67
+ // Structured pages drive the body from `sections`; legacy pages keep the
63
68
  // sanitized `body_html`. Prefer structured whenever a page ships sections.
64
69
  const structured = (page.sections?.length ?? 0) > 0;
65
70
 
@@ -109,7 +114,7 @@ const updatedLabel = page.updated
109
114
  {structured && <ProseToc sections={page.sections!} />}
110
115
  </header>
111
116
 
112
- {/* Body — constrained to a readable measure. Structured pages (F4) render
117
+ {/* Body — constrained to a readable measure. Structured pages render
113
118
  framework widgets from `sections`; legacy pages keep the sanitized HTML. */}
114
119
  <div class="container-prose pb-[clamp(3rem,7vw,6rem)] pt-8">
115
120
  {structured ? (