@tokenoftrust/storefront-runner 1.4.1 → 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 (474) hide show
  1. package/apps/storefront/astro.config.mjs +17 -1
  2. package/apps/storefront/env.d.ts +54 -5
  3. package/apps/storefront/integrations/materialize-guard.mjs +54 -0
  4. package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
  5. package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
  6. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  7. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  8. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  9. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  10. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  11. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  12. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  13. package/apps/storefront/migrations-apps/meta/0002_snapshot.json +1160 -0
  14. package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -0
  15. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  16. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  17. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  18. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  19. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  20. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  21. package/apps/storefront/migrations-apps/meta/_journal.json +56 -0
  22. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  23. package/apps/storefront/public/js/dashboard-team.js +38 -2
  24. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  25. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  26. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  27. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  28. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  29. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  30. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  31. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  32. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  33. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  34. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  35. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  36. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  37. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  38. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  39. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  40. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  41. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  42. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  43. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  44. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  45. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  46. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  47. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  48. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  49. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  50. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  51. package/apps/storefront/src/components/Img.astro +1 -1
  52. package/apps/storefront/src/components/Seo.astro +65 -1
  53. package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
  54. package/apps/storefront/src/components/admin/AdminBlogTab.astro +1435 -0
  55. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
  56. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  57. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1980 -2564
  58. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  59. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
  60. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  61. package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
  62. package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
  63. package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
  64. package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
  65. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  66. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  67. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  68. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  69. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  70. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  71. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  72. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  73. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  74. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  75. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  76. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  77. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  78. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  79. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  80. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  81. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  82. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  83. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  84. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  85. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  86. package/apps/storefront/src/config/resolver.ts +4 -4
  87. package/apps/storefront/src/layouts/Layout.astro +146 -21
  88. package/apps/storefront/src/lib/activity/alerts.ts +17 -19
  89. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  90. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +89 -9
  91. package/apps/storefront/src/lib/activity/deployVersion.ts +20 -27
  92. package/apps/storefront/src/lib/activity/ingest.ts +8 -8
  93. package/apps/storefront/src/lib/activity/ingestAuth.ts +1 -1
  94. package/apps/storefront/src/lib/activity/killSwitch.ts +6 -6
  95. package/apps/storefront/src/lib/activity/query.ts +9 -9
  96. package/apps/storefront/src/lib/activity/recordActivity.ts +11 -15
  97. package/apps/storefront/src/lib/activity/store.ts +4 -6
  98. package/apps/storefront/src/lib/activity/uiActor.ts +12 -12
  99. package/apps/storefront/src/lib/activity/workerCommit.ts +6 -6
  100. package/apps/storefront/src/lib/admin/adminShell.ts +292 -0
  101. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
  102. package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
  103. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
  104. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
  105. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  106. package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
  107. package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
  108. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
  109. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
  110. package/apps/storefront/src/lib/apps/adminService.ts +14 -4
  111. package/apps/storefront/src/lib/apps/adminSession.ts +54 -45
  112. package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
  113. package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
  114. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  115. package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
  116. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  117. package/apps/storefront/src/lib/apps/credentials.ts +2 -2
  118. package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
  119. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  120. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  121. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  122. package/apps/storefront/src/lib/apps/healthAggregate.ts +5 -5
  123. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  124. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  125. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  126. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  127. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  128. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  129. package/apps/storefront/src/lib/apps/orders/attributionService.ts +2 -2
  130. package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
  131. package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
  132. package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
  133. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  134. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +9 -1
  135. package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
  136. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +7 -7
  137. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  138. package/apps/storefront/src/lib/apps/registryService.ts +270 -16
  139. package/apps/storefront/src/lib/apps/scopes.ts +2 -2
  140. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  141. package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
  142. package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
  143. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  144. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  145. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  146. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  147. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  148. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  149. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  150. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  151. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  152. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  153. package/apps/storefront/src/lib/apps/widgets/eligibility.ts +2 -2
  154. package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
  155. package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
  156. package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
  157. package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
  158. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  159. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  160. package/apps/storefront/src/lib/auth/adminEntry.ts +11 -11
  161. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  162. package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
  163. package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
  164. package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +2 -4
  165. package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
  166. package/apps/storefront/src/lib/auth/session.ts +7 -8
  167. package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
  168. package/apps/storefront/src/lib/basePath.ts +9 -2
  169. package/apps/storefront/src/lib/blog/access.ts +29 -0
  170. package/apps/storefront/src/lib/blog/collection.ts +400 -0
  171. package/apps/storefront/src/lib/blog/markdown.ts +174 -0
  172. package/apps/storefront/src/lib/blog/provider.ts +62 -3
  173. package/apps/storefront/src/lib/blog/reactions.ts +87 -0
  174. package/apps/storefront/src/lib/blog/rss.ts +92 -0
  175. package/apps/storefront/src/lib/blog/teaser.ts +83 -0
  176. package/apps/storefront/src/lib/blog/types.ts +34 -0
  177. package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
  178. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  179. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  180. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  181. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  182. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  183. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  184. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  185. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  186. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  187. package/apps/storefront/src/lib/content/index.ts +5 -5
  188. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  189. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  190. package/apps/storefront/src/lib/d1/catalog.ts +14 -2
  191. package/apps/storefront/src/lib/d1/schema-apps.ts +372 -17
  192. package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
  193. package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
  194. package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
  195. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  196. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  197. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  198. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  199. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  200. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  201. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  202. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  203. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  204. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  205. package/apps/storefront/src/lib/dev/cockpitStore.ts +1 -9
  206. package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
  207. package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
  208. package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
  209. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  210. package/apps/storefront/src/lib/edgeCache.ts +34 -5
  211. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  212. package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
  213. package/apps/storefront/src/lib/i18n.ts +25 -0
  214. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  215. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  216. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  217. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  218. package/apps/storefront/src/lib/jsonld.ts +118 -0
  219. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  220. package/apps/storefront/src/lib/membership/model.ts +1 -2
  221. package/apps/storefront/src/lib/monitoring/manifest.ts +11 -11
  222. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
  223. package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
  224. package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
  225. package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
  226. package/apps/storefront/src/lib/newsletter/send.ts +265 -0
  227. package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
  228. package/apps/storefront/src/lib/previewReload.ts +57 -1
  229. package/apps/storefront/src/lib/publish/adminPublishTab.ts +4625 -0
  230. package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
  231. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  232. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  233. package/apps/storefront/src/lib/publish/dispatchHealth.ts +5 -7
  234. package/apps/storefront/src/lib/publish/domainState.ts +22 -13
  235. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  236. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  237. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  238. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  239. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  240. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  241. package/apps/storefront/src/lib/publish/panelPolling.ts +78 -0
  242. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  243. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  244. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  245. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  246. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  247. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  248. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  249. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  250. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  251. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  252. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  253. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  254. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  255. package/apps/storefront/src/lib/publish/shipWorkspace.ts +121 -51
  256. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  257. package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -0
  258. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  259. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  260. package/apps/storefront/src/lib/rate/kvWindow.ts +53 -0
  261. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  262. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  263. package/apps/storefront/src/lib/search/index.ts +2 -2
  264. package/apps/storefront/src/lib/seo/alternates.ts +42 -0
  265. package/apps/storefront/src/lib/seo/meta.ts +65 -0
  266. package/apps/storefront/src/lib/social/golive.ts +212 -0
  267. package/apps/storefront/src/lib/social/notify.ts +32 -0
  268. package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
  269. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  270. package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
  271. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  272. package/apps/storefront/src/lib/the-build/provider.ts +32 -0
  273. package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
  274. package/apps/storefront/src/lib/the-build/toc.ts +95 -0
  275. package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
  276. package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
  277. package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
  278. package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
  279. package/apps/storefront/src/middleware/index.ts +385 -52
  280. package/apps/storefront/src/pages/[...slug].astro +9 -4
  281. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  282. package/apps/storefront/src/pages/admin/select.astro +96 -0
  283. package/apps/storefront/src/pages/admin.astro +25 -1313
  284. package/apps/storefront/src/pages/api/activity.ts +8 -8
  285. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  286. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  287. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  288. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  289. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  290. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  291. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  292. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  293. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  294. package/apps/storefront/src/pages/api/blog/[collection]/[slug]/react.ts +104 -0
  295. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  296. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  297. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  298. package/apps/storefront/src/pages/api/newsletter/[collection]/subscribe.ts +146 -0
  299. package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
  300. package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
  301. package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
  302. package/apps/storefront/src/pages/api/rum/vitals.ts +20 -2
  303. package/apps/storefront/src/pages/blog/[slug].astro +99 -19
  304. package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
  305. package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
  306. package/apps/storefront/src/pages/blog/index.astro +4 -2
  307. package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
  308. package/apps/storefront/src/pages/cockpit.astro +77 -1353
  309. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
  310. package/apps/storefront/src/pages/index.astro +10 -5
  311. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  312. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  313. package/apps/storefront/src/pages/search.astro +1 -1
  314. package/apps/storefront/src/pages/sitemap.xml.ts +21 -1
  315. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
  316. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
  317. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  318. package/apps/storefront/src/pages/the-build/[slug].astro +129 -0
  319. package/apps/storefront/src/pages/the-build/index.astro +146 -0
  320. package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
  321. package/apps/storefront/src/styles/admin.css +1106 -0
  322. package/apps/storefront/tsconfig.json +6 -1
  323. package/package.json +1 -1
  324. package/packages/public-runtime/package.json +3 -1
  325. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  326. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  327. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  328. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  329. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  330. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  331. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  332. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  333. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  334. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  335. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  336. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  337. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  338. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  339. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  340. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  341. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  342. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  343. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  344. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  345. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  346. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  347. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  348. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  349. package/packages/public-runtime/src/activity/catalog.ts +89 -33
  350. package/packages/public-runtime/src/activity/event.ts +6 -6
  351. package/packages/public-runtime/src/activity/index.ts +2 -2
  352. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  353. package/packages/public-runtime/src/candidate-index.ts +42 -8
  354. package/packages/public-runtime/src/catalog-d1.ts +1 -1
  355. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  356. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  357. package/packages/public-runtime/src/checkout-style.ts +7 -7
  358. package/packages/public-runtime/src/checkout.ts +3 -3
  359. package/packages/public-runtime/src/chrome.ts +777 -0
  360. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  361. package/packages/public-runtime/src/csp.ts +99 -0
  362. package/packages/public-runtime/src/customization-preview.ts +25 -2
  363. package/packages/public-runtime/src/customization-runtime.ts +156 -3
  364. package/packages/public-runtime/src/customization-versioning.ts +10 -0
  365. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  366. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  367. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  368. package/packages/public-runtime/src/evidence.ts +288 -0
  369. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  370. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  371. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  372. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  373. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  374. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  375. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  376. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  377. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  378. package/packages/public-runtime/src/index.ts +17 -1
  379. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  380. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  381. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  382. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  383. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  384. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  385. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  386. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  387. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  388. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  389. package/packages/public-runtime/src/integration/csp.ts +105 -0
  390. package/packages/public-runtime/src/integration/digest.ts +43 -0
  391. package/packages/public-runtime/src/integration/environment.ts +228 -0
  392. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  393. package/packages/public-runtime/src/integration/events.ts +701 -0
  394. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  395. package/packages/public-runtime/src/integration/index.ts +90 -0
  396. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  397. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  398. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  399. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  400. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  401. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  402. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  403. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  404. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  405. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  406. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  407. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  408. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  409. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  410. package/packages/public-runtime/src/integration/observability.ts +451 -0
  411. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  412. package/packages/public-runtime/src/integration/phases.ts +182 -0
  413. package/packages/public-runtime/src/integration/plan.ts +112 -0
  414. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  415. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  416. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  417. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  418. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  419. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  420. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  421. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  422. package/packages/public-runtime/src/integration/result.ts +239 -0
  423. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  424. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  425. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  426. package/packages/public-runtime/src/integration/version.ts +92 -0
  427. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  428. package/packages/public-runtime/src/membership.ts +2 -2
  429. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  430. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  431. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  432. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  433. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  434. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  435. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  436. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  437. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  438. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  439. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  440. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  441. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  442. package/packages/public-runtime/src/static-bundle.ts +357 -0
  443. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  444. package/packages/public-runtime/src/tenant.ts +81 -17
  445. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  446. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  447. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  448. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  449. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  450. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  451. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  452. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  453. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  454. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  455. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  456. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  457. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  458. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  459. package/scripts/build/copy-tenant-assets.mjs +14 -4
  460. package/scripts/dev/ai-edit.mjs +1 -1
  461. package/scripts/dev/checkout-watch.mjs +2 -2
  462. package/scripts/dev/file-browser.mjs +4 -6
  463. package/scripts/dev/file-writer.mjs +3 -3
  464. package/scripts/dev/git-status.mjs +2 -2
  465. package/scripts/dev/integration-guardrails.mjs +498 -0
  466. package/scripts/dev/locate-handler.mjs +3 -3
  467. package/scripts/dev/port-check.mjs +3 -3
  468. package/scripts/dev/publish.mjs +5 -9
  469. package/scripts/dev/safe-path.mjs +2 -2
  470. package/scripts/dev/shot.mjs +1 -1
  471. package/scripts/dev/tenant-source-root.mjs +1 -1
  472. package/scripts/dev/unified-diff.mjs +8 -3
  473. package/scripts/tot-dev.mjs +8 -11
  474. package/apps/storefront/src/lib/dev/vcBinding.ts +0 -80
@@ -11,7 +11,7 @@ import { getTenantResolver } from "@/config/resolver";
11
11
  import { referenceTheme } from "@/themes/reference";
12
12
  import { mergeTheme, themeToCssVars } from "@/themes/schema";
13
13
  import { createToTClient } from "@/lib/tot/ToTClient";
14
- import { readEnv, readKv, readD1 } from "@/lib/env";
14
+ import { readEnv, readKv, readD1, readR2 } from "@/lib/env";
15
15
  import { resolveTenantRouting } from "@/lib/tenantRouting";
16
16
  import { createContentProvider, DevDraftContentProvider } from "@/lib/storyblok/provider";
17
17
  import { DEV_DRAFT_COOKIE, devDraftKey, verifyDevDraftToken } from "@/lib/dev/devDraft";
@@ -21,6 +21,7 @@ import {
21
21
  readCacheVersion,
22
22
  getCachedPage,
23
23
  putCachedPage,
24
+ staticBundleCacheKey,
24
25
  } from "@/lib/edgeCache";
25
26
  import { STATIC_SCRIPT_HASHES, STATIC_STYLE_HASHES } from "@/lib/cspStaticHashes";
26
27
  import { applySecurityHeaders } from "@/lib/securityHeaders";
@@ -31,6 +32,10 @@ import {
31
32
  STOREFRONT_CSP_ALLOWLIST,
32
33
  checkoutCspAdditions,
33
34
  appWidgetCspAdditions,
35
+ embedCspAdditions,
36
+ foldIntegrationCsp,
37
+ embedsArtifactPath,
38
+ resolveEffectiveEmbeds,
34
39
  CSP_REPORT_GROUP,
35
40
  CSP_REPORT_PATH,
36
41
  readPublishedArtifact,
@@ -38,23 +43,40 @@ import {
38
43
  resolveTenantFeatures,
39
44
  resolveCapabilities,
40
45
  capabilitiesArtifactPath,
46
+ resolveChannelDigest,
47
+ resolveServableChannelVersion,
48
+ channelFromEnv,
49
+ serveStaticBundle,
50
+ resolveEnvironmentOnlyPlan,
51
+ resolveInterimBridgePlan,
52
+ compileIntegrationCspFromPlan,
53
+ collectDeclaredCspProfiles,
54
+ INTEGRATION_NONCE_PLACEHOLDER,
41
55
  } from "@tot/public-runtime";
42
- import type { CapabilityConfig } 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";
43
60
  import { resolveTenantCommerce } from "@/lib/checkoutCommerce";
44
61
  import { evaluateDemoGate, hostInGateList } from "@/lib/demoGate";
45
62
  import {
46
63
  evaluateLoginGate,
47
64
  parseGateMode,
48
65
  isAuthPath,
66
+ isAdminPath,
49
67
  sessionAdmitsHost,
50
68
  sessionHostCapability,
51
69
  PUBLIC_HOME_TENANT_ID,
52
70
  } from "@/lib/auth/loginGate";
71
+ import { resolveAdminLanding, isAdminSelectPath } from "@/lib/auth/adminLanding";
53
72
  import { gatedHoldingResponse } from "@/lib/auth/gatePage";
54
73
  import { readSession, KvSessionStore, SESSION_COOKIE } from "@/lib/auth/session";
55
74
  import { isMaintenanceOn, maintenanceResponse } from "@/lib/maintenance";
56
75
  import { HEALTH_PATH, healthResponse } from "@/lib/health";
57
76
  import { observeDeployVersion } from "@/lib/activity/deployVersion";
77
+ import { requestBrowserEnvironment, deriveSiteSurface, cspHeaderName } from "@/lib/integration/browserEnvironment";
78
+ import { freshNonce, stampResponseNonce } from "@/lib/integration/nonceStamp";
79
+ import { recordActivity } from "@/lib/activity/recordActivity";
58
80
  import { primeActivityKillSwitch } from "@/lib/activity/killSwitch";
59
81
  import type { DeepPartial, ThemeTokens } from "@tot/public-runtime";
60
82
 
@@ -64,19 +86,19 @@ export const onRequest = defineMiddleware(async (context, next) => {
64
86
  const host =
65
87
  request.headers.get("host") ?? url.host ?? "localhost";
66
88
 
67
- // ---- Activity telemetry kill switch (card D8) -----------------------------
89
+ // ---- Activity telemetry kill switch -----------------------------
68
90
  // Prime the ONE `ACTIVITY_TELEMETRY_DISABLED` flag from the (async) Worker env
69
91
  // once per isolate, BEFORE the first emit, so every telemetry chokepoint in this
70
92
  // isolate (deploy-provenance, recordActivity, ingest) observes it. Fail-open,
71
93
  // never throws. Off by default (unset ⇒ telemetry on).
72
94
  await primeActivityKillSwitch();
73
95
 
74
- // ---- Deploy provenance (card D2) — worker_commit / deploy.version_observed -
96
+ // ---- Deploy provenance — worker_commit / deploy.version_observed -
75
97
  // Once per worker isolate (cold start), emit `deploy.version_observed` carrying
76
- // this build's commit SHA so D5 can derive deploy skew (running build vs umbrella
77
- // tip). Idempotent per isolate, best-effort, never throws — so it's safe here at
78
- // the very front door, ahead of /health, and fires regardless of Host/tenant/gate.
79
- // 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.
80
102
  observeDeployVersion();
81
103
 
82
104
  // ---- /health — liveness + deploy-identity probe ---------------------------
@@ -164,16 +186,67 @@ export const onRequest = defineMiddleware(async (context, next) => {
164
186
  }
165
187
  const nowSeconds = Math.floor(Date.now() / 1000);
166
188
  const admitted = sessionAdmitsHost(viewerRecord, tenant.appDomain, nowSeconds);
189
+ // QA content-bypass: only when the server has configured DEMO_GATE_BYPASS_TOKEN
190
+ // (never in a prod slot) AND the request presents a matching X-Demo-Gate-Bypass
191
+ // header. Unset token ⇒ false ⇒ fully inert; a client cannot self-grant. The
192
+ // public-content-only restriction (admin/cockpit/api excluded) is enforced
193
+ // inside evaluateLoginGate via isBypassPrivilegedPath.
194
+ const bypassToken = await readEnv("DEMO_GATE_BYPASS_TOKEN");
195
+ const qaBypass =
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);
167
228
  const decision = evaluateLoginGate({
168
229
  host,
169
230
  pathname: url.pathname,
170
231
  search: url.search,
171
232
  mode: gateMode,
172
233
  gatedHosts: demoGateHosts,
173
- hasValidSession: admitted,
234
+ hasValidSession,
174
235
  tenantExempt,
236
+ qaBypass,
175
237
  });
176
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
+ }
177
250
  // Withhold content but offer sign-in (carrying the intended destination).
178
251
  return gatedHoldingResponse(decision.location);
179
252
  }
@@ -183,7 +256,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
183
256
  // in: a member sees their tenants[] role; the owner of their own tenant sees
184
257
  // the resource-scoped session's capability; a ToT-staff viewer admitted via
185
258
  // an explicit vendor selection sees the staffRoles-scoped capability. The
186
- // ship gates (u10 decideIsOwner/resolveShipPrincipal) read this capability,
259
+ // ship gates (decideIsOwner/resolveShipPrincipal) read this capability,
187
260
  // so binding it here keeps owner-resolution server-side + tenant-correct.
188
261
  const hostCapability = sessionHostCapability(viewerRecord, tenant.appDomain, nowSeconds);
189
262
  locals.viewer = {
@@ -199,13 +272,156 @@ export const onRequest = defineMiddleware(async (context, next) => {
199
272
  const isPreviewHost = host.toLowerCase().startsWith("preview.");
200
273
  const queryToken = url.searchParams.get("__preview");
201
274
  const cookieToken = context.cookies.get("tot_preview")?.value ?? null;
202
- // Per-developer AI-edit draft (decision draft-overlay-same-origin): the signed
275
+ // Per-developer AI-edit draft: the signed
203
276
  // tot_dev_draft cookie's mere PRESENCE opts the request out of the shared page
204
277
  // cache below (a draft view must never be cached publicly); it's verified —
205
278
  // signature, expiry, tenant binding — before the overlay is applied further down.
206
279
  const draftCookie = context.cookies.get(DEV_DRAFT_COOKIE)?.value ?? null;
207
280
 
208
- // ---- Edge page cache (D1): short-circuit production browse reads on a hit,
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
+
295
+ // ---- Digest-addressed static-bundle serving (ADR 0012) --------------------
296
+ // For a static-publish tenant's PRODUCTION reads only (never preview/test — the
297
+ // "Worker serves interactive preview, R2 serves prod" split), read the live
298
+ // bundle straight out of R2 instead of falling through to SSR. Deliberately
299
+ // BEFORE the edge page cache / catalog / theme / customization machinery below:
300
+ // none of that applies to bundle bytes served verbatim, and this path must
301
+ // never SSR-fallthrough once it's eligible (a missing digest/object fails
302
+ // closed, see `serveStaticBundle`), so it has to fully own the response, not
303
+ // degrade into the normal render.
304
+ // Host-resolved reads only (`basePath === ""`): bundle bytes are served
305
+ // VERBATIM with root-absolute links, so a path-prefixed view
306
+ // (/<domain>/… on the shared .store host) would escape its prefix on every
307
+ // href — those views keep SSR, which rewrites hrefs through basePath.
308
+ const staticBundleEligible =
309
+ tenant.staticPublish != null &&
310
+ tenant.staticPublish.enabled !== false &&
311
+ basePath === "" &&
312
+ request.method.toUpperCase() === "GET" &&
313
+ !isPreviewHost &&
314
+ !queryToken &&
315
+ !cookieToken &&
316
+ !draftCookie &&
317
+ managedIntegrationDeclarations.length === 0 &&
318
+ !url.pathname.startsWith("/api/");
319
+ if (staticBundleEligible) {
320
+ const bundleBucket = await readR2("STATIC_BUNDLES");
321
+ if (bundleBucket) {
322
+ const kvGet = kv ? (key: string) => kv.get(key) : async () => null;
323
+ // The R2 bundle key is the `treeDigest`, NOT the `live` channel versionId
324
+ // (the Gitea sha). Read the reconciled digest companion (`channelDigests.live`)
325
+ // — `gatedPromote`/`rollbackCandidateAtomic` write it in the same CAS flip as
326
+ // the versionId. No digest = this tenant has never staged a bundle — SSR
327
+ // serves it (a static-publish tenant must be able to bootstrap, and its
328
+ // app surfaces must never be downed by an unstaged static plane).
329
+ const digest = await resolveChannelDigest(kvGet, tenant.tenant_id, "live");
330
+ if (digest) {
331
+ const bundleCacheKey = cache
332
+ ? staticBundleCacheKey(tenant.tenant_id, digest, url.pathname, url.search)
333
+ : null;
334
+ if (bundleCacheKey && cache) {
335
+ const hit = await getCachedPage(cache, bundleCacheKey);
336
+ if (hit) return hit;
337
+ }
338
+ const bundleResponse = await serveStaticBundle(
339
+ {
340
+ bucket: bundleBucket as unknown as StaticBundleR2BucketLike,
341
+ digest,
342
+ onFailClosed: (detail) => {
343
+ console.error(
344
+ `[static-bundle] fail-closed for ${tenant.tenant_id} ${url.pathname}: ${detail}`,
345
+ );
346
+ },
347
+ },
348
+ url.pathname,
349
+ );
350
+ // `null` = the manifest proves this path is outside the bundle's static
351
+ // surface (an app surface like /admin) — SSR owns it below.
352
+ if (bundleResponse) {
353
+ if (bundleCacheKey && cache) {
354
+ await putCachedPage(cache, bundleCacheKey, bundleResponse);
355
+ }
356
+ return bundleResponse;
357
+ }
358
+ } else {
359
+ console.error(
360
+ `[static-bundle] no live digest for ${tenant.tenant_id} — SSR until a bundle is staged`,
361
+ );
362
+ }
363
+ }
364
+ // No STATIC_BUNDLES binding (plain `astro dev`, or unconfigured) —
365
+ // degrading to SSR here rather than a hard failure keeps `astro dev`/tests
366
+ // usable without provisioning R2.
367
+ }
368
+
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,
209
425
  // BEFORE building the client / theme / rendering. Misses store the final
210
426
  // response at the end of this middleware.
211
427
  //
@@ -224,12 +440,27 @@ export const onRequest = defineMiddleware(async (context, next) => {
224
440
  let pageKey: Request | null = null;
225
441
  if (pageCacheEligible && cache && kv) {
226
442
  const version = await readCacheVersion(kv, tenant.tenant_id);
227
- 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
+ );
228
456
  const hit = await getCachedPage(cache, pageKey);
229
- 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());
230
461
  }
231
462
 
232
- // Catalog source selection (F17 graduation path — docs/architecture/product-catalog.md).
463
+ // Catalog source selection (docs/architecture/product-catalog.md).
233
464
  // A store's product source graduates over its life: JSON fixtures (dev default) ->
234
465
  // D1 edge replica (staging) -> hosted ToT catalog (go-live). Here we pick the read
235
466
  // backend: USE_D1_CATALOG=1 (+ a CATALOG_DB binding) reads the D1 edge replica;
@@ -249,7 +480,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
249
480
  ? `${proto}://${host}${basePath}`
250
481
  : `https://${tenant.canonicalDomain}`;
251
482
 
252
- // Live vs test customization (D4). Production hosts always serve "live" and
483
+ // Live vs test customization. Production hosts always serve "live" and
253
484
  // ignore any token; a "preview." host is auth-gated by a signed, tenant-bound,
254
485
  // short-TTL preview token (?__preview= on first hit, then the tot_preview
255
486
  // cookie). Test is never indexable (X-Robots-Tag below).
@@ -262,6 +493,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
262
493
  token: previewToken,
263
494
  secret: previewSecret,
264
495
  nowSeconds,
496
+ pathname: url.pathname,
265
497
  });
266
498
  if (customization.reject) {
267
499
  return new Response(customization.reject.message, {
@@ -290,6 +522,28 @@ export const onRequest = defineMiddleware(async (context, next) => {
290
522
  // pointer fall back to the legacy per-path selection — no behavior change.
291
523
  const kvGet = kv ? (key: string) => kv.get(key) : async () => null;
292
524
 
525
+ // Self-heal a DANGLING channel pointer: the pointer for this env's channel may
526
+ // name a version that was never built (or whose manifest was evicted).
527
+ // `readVersionedArtifact` already serves last-built content instead of emptying
528
+ // the surface; here we do the once-per-request detection to ENQUEUE a rebuild of
529
+ // the dangling version. Fail-open: a KV hiccup never blocks the render (the reads
530
+ // below still run).
531
+ if (kv) {
532
+ const channel = channelFromEnv(customization.env);
533
+ await resolveServableChannelVersion(kvGet, tenant.tenant_id, channel, ({ versionId }) => {
534
+ recordActivity({
535
+ action: "preview.build.requested",
536
+ actor: { kind: "system", id: "storefront" },
537
+ source: "server",
538
+ outcome: { status: "succeeded" },
539
+ scope: { tenantId: tenant.tenant_id },
540
+ payload: { args: { changeId: versionId } },
541
+ });
542
+ }).catch(() => {
543
+ /* fail-open: self-heal detection never breaks the request */
544
+ });
545
+ }
546
+
293
547
  // Verify the developer-draft cookie (signature + expiry + tenant binding) — the
294
548
  // overlay below only engages for the developer who minted the draft, on THIS
295
549
  // tenant. Same secret + trust model as the preview token; invalid/expired/foreign
@@ -348,9 +602,34 @@ export const onRequest = defineMiddleware(async (context, next) => {
348
602
  }
349
603
  const capabilities = resolveCapabilities(tenant, publishedCaps);
350
604
 
351
- // Per-request CSP nonce (D2): goes on the platform's own inline tags in
352
- // Layout.astro so a strict CSP can allow them without 'unsafe-inline'.
353
- 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;
354
633
 
355
634
  locals.tenant = tenant;
356
635
  // Effective capability flags for this tenant (commerce defaults unless the
@@ -393,6 +672,31 @@ export const onRequest = defineMiddleware(async (context, next) => {
393
672
  // tenant-agnostic; the Layout uses this to keep root-absolute links inside
394
673
  // the tenant prefix on the client.
395
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);
396
700
 
397
701
  // /dev was renamed to /cockpit (2026-07-20). 302-redirect any old /dev link —
398
702
  // host-routed (/dev) or path-routed (/<vendor>/dev) — to the new path, preserving
@@ -421,7 +725,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
421
725
  nonce: cspNonce,
422
726
  });
423
727
 
424
- // Baseline security headers (S1) on every response — set before the page-cache
728
+ // Baseline security headers on every response — set before the page-cache
425
729
  // store below so cached copies carry them too.
426
730
  applySecurityHeaders(response.headers);
427
731
 
@@ -445,9 +749,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
445
749
  // site unstyled. The BUILD is CSP-clean (styles are externalized via
446
750
  // `inlineStylesheets:"never"`), so we enforce there and only REPORT in dev —
447
751
  // violations are still surfaced, but styling/HMR work locally.
448
- const cspHeader = import.meta.env.DEV
449
- ? "Content-Security-Policy-Report-Only"
450
- : "Content-Security-Policy";
752
+ const cspHeader = cspHeaderName(import.meta.env.DEV);
451
753
  response.headers.set(
452
754
  "Reporting-Endpoints",
453
755
  `${CSP_REPORT_GROUP}="${CSP_REPORT_PATH}"`,
@@ -456,40 +758,66 @@ export const onRequest = defineMiddleware(async (context, next) => {
456
758
  // strict policy only when this tenant has a store — so add-to-cart, the
457
759
  // sidecart iframe/XHR, and the cross-origin form POST all pass.
458
760
  const checkoutCsp = commerce ? checkoutCspAdditions(commerce.origin) : null;
459
- // Private-app widget origins (D5) — read back AFTER next() so this reflects
761
+ // Private-app widget origins — read back AFTER next() so this reflects
460
762
  // exactly the placements that rendered on THIS page, never a wildcard.
461
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.
462
815
  response.headers.set(
463
816
  cspHeader,
464
- buildCsp({
465
- nonce: cspNonce,
466
- ...STOREFRONT_CSP_ALLOWLIST,
467
- // Pin Astro's static inline hydration scripts + island style by hash
468
- // (they're identical every request); my dynamic inline tags use the nonce.
469
- scriptSrc: [
470
- ...STOREFRONT_CSP_ALLOWLIST.scriptSrc,
471
- ...STATIC_SCRIPT_HASHES,
472
- ...(checkoutCsp?.scriptSrc ?? []),
473
- ],
474
- styleSrc: [
475
- ...STOREFRONT_CSP_ALLOWLIST.styleSrc,
476
- ...STATIC_STYLE_HASHES,
477
- ...(checkoutCsp?.styleSrc ?? []),
478
- ],
479
- fontSrc: [...STOREFRONT_CSP_ALLOWLIST.fontSrc, ...(checkoutCsp?.fontSrc ?? [])],
480
- connectSrc: [
481
- ...STOREFRONT_CSP_ALLOWLIST.connectSrc,
482
- ...(checkoutCsp?.connectSrc ?? []),
483
- ],
484
- imgSrc: [...STOREFRONT_CSP_ALLOWLIST.imgSrc, ...(checkoutCsp?.imgSrc ?? [])],
485
- frameSrc: [...(checkoutCsp?.frameSrc ?? []), ...widgetCsp.frameSrc],
486
- formAction: checkoutCsp?.formAction ?? [],
487
- reportGroup: CSP_REPORT_GROUP,
488
- reportUri: CSP_REPORT_PATH,
489
- }),
817
+ buildCsp(foldIntegrationCsp(cspOptions, locals.integrationCsp?.additions ?? {})),
490
818
  );
491
819
 
492
- // 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
493
821
  // production (no preview token reached here) cacheable GETs have a pageKey;
494
822
  // putCachedPage additionally skips non-200, non-HTML, and cookie-bearing
495
823
  // responses. Also expose Cache-Control to any fronting CDN.
@@ -498,7 +826,12 @@ export const onRequest = defineMiddleware(async (context, next) => {
498
826
  "Cache-Control",
499
827
  "public, s-maxage=300, stale-while-revalidate=86400",
500
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.
501
831
  await putCachedPage(cache, pageKey, response);
502
832
  }
503
- 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());
504
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 ? (