@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
@@ -1,33 +1,32 @@
1
1
  /**
2
- * Ship operations workspace (unit dg7) — the PURE composer behind the admin
2
+ * Ship operations workspace — the PURE composer behind the admin
3
3
  * Publish tab's operations surface that sits ALONGSIDE the go-live core
4
4
  * (AdminPublishTab.astro's change queue + accept/reject + dispatch status).
5
5
  *
6
- * dg7 is DELTA-scoped: the go-live session already delivered the Publish-tab
7
- * core (the change queue from GET /api/changes, Go-live/Reject, the live-version
8
- * + dispatch-status panel, and the owner-only Domain section). This module adds
9
- * the operations workspace BEYOND that core — the three read/act surfaces woven
10
- * into the same tab:
6
+ * This module composes the operations workspace ALONGSIDE the Publish-tab core
7
+ * (the change queue from GET /api/changes, Go-live/Reject, the live-version +
8
+ * dispatch-status panel, and the owner-only Domain section) the three read/act
9
+ * surfaces woven into the same tab:
11
10
  *
12
11
  * 1. DIFF VIEW — for one pending candidate, the artifact-level diff of what
13
12
  * changed (its rendered immutable version vs the current live version),
14
- * tied to the exact tenant Gitea head SHA, with the u5 immutable
15
- * `/preview/<tenant>/rev/<sha>/` link and the dg3 evidence AUTHORITY
13
+ * tied to the exact tenant Gitea head SHA, with the immutable
14
+ * `/<tenant>/preview/rev/<sha>/` link and the evidence AUTHORITY
16
15
  * (authoritative / stale / missing) — never a diff a reviewer would mistake
17
16
  * for the head when a last-good version actually renders.
18
17
  * 2. VERSIONS — the tenant's published-version / promotion history
19
- * (`listPromotions`, u2/u3 promotion_status), each row carrying the tenant
20
- * Gitea SHA (`gitea-only-tenant-content-authority`), its promotion time, a
18
+ * (`listPromotions`, promotion_status), each row carrying the tenant
19
+ * Gitea SHA, its promotion time, a
21
20
  * `/rev/<sha>/` link, and — marking the current live one.
22
21
  * 3. ROLLBACK — pick a prior version and roll back THROUGH the one orchestrator
23
- * (`/api/changes/rollback` → `rollbackCandidateAtomic` / u8's
22
+ * (`/api/changes/rollback` → `rollbackCandidateAtomic` /
24
23
  * `staticRollbackOrchestrator`), rendering only the HONEST terminal state
25
24
  * (`rolled_back` when VERIFIED; `publish_pending` in flight; `publish_failed`
26
25
  * / `blocked` fail-closed) — never a false "rolled back".
27
26
  *
28
27
  * COMPOSES, never rebuilds:
29
- * - the u5 immutable link is `reviewDashboardRevisionHref` verbatim;
30
- * - the dg3 authority is `candidateEvidenceAuthority` /
28
+ * - the immutable link is `reviewDashboardRevisionHref` verbatim;
29
+ * - the authority is `candidateEvidenceAuthority` /
31
30
  * `classifyCandidateForRender` verbatim;
32
31
  * - the versions come from the SAME `KVVersionStore.listPromotions` the
33
32
  * "one step back" rollback resolution reads;
@@ -38,22 +37,16 @@
38
37
  * decision is unit-tested without a Worker/KV — mirroring the reviewDashboard.ts /
39
38
  * candidatePreviewState.ts split.
40
39
  *
41
- * HONEST BOUNDARY (surfaced, never faked): a FULLY-STATIC tenant serves its public
42
- * site from an external `versions/<digest>/` archive whose per-version treeDigests
43
- * live only in S3 enumerable by the Node-only `listRollbackTargets`
44
- * (`scripts/publish/lib/static-rollback.mjs`), NOT by the Worker. So the console
45
- * can list a static tenant's promotion history + `/rev` links + the CURRENT live
46
- * digest, but it cannot resolve an ARBITRARY prior version's archive digest. It
47
- * therefore rolls a static tenant back by an HONEST ONE-DISPATCH (rux13):
48
- * `/api/changes/rollback` accepts the `targetVersionId` alone, restores the pointer,
49
- * and dispatches a CI job that resolves the digest from the archive by sourceSha
50
- * (`rollback-s3.mjs --source-sha`) and re-publishes — reporting `publish_pending`
51
- * until the re-publish is verified live (never a false "rolled back"). `rollbackMode`
52
- * names the two paths: a `control-plane` tenant rolls back one-click via the pointer
53
- * flip (instant); a `static-archive` tenant's rollback DISPATCHES via CI and
54
- * completes asynchronously — the surface must say so, never imply an instant flip.
40
+ * `rollbackMode` names the path a rollback is honestly driven by: a `control-plane`
41
+ * (hosted) tenant and a `pointer` (static-publish) tenant BOTH roll back one-click
42
+ * via the instant KV `live` pointer flip the Worker serves whatever bundle the
43
+ * pointer's digest names (from R2, for a static tenant), so restoring the pointer IS
44
+ * the rollback. The `publish_pending`/`publish_failed`/`blocked` honesty model from
45
+ * `interpretRollbackResponse` below applies identically in both modes only the
46
+ * digest column (surfaced for a static tenant) differs.
55
47
  */
56
48
  import { reviewDashboardRevisionHref } from "@/lib/preview/reviewDashboard";
49
+ import { assignVersionNumbers } from "@/lib/publish/versionNumber";
57
50
  import {
58
51
  candidateEvidenceAuthority,
59
52
  classifyCandidateForRender,
@@ -64,11 +57,45 @@ import type { ReviewEnvironment, PromotionEvent } from "@tot/public-runtime";
64
57
 
65
58
  // ── Versions / promotion history ───────────────────────────────────────────
66
59
 
67
- /** One published version in the tenant's promotion history (u2/u3 promotion_status). */
60
+ /** The plain-language "what shipped in this version" model (same shape the staged
61
+ * release summary uses — a name + a lead sentence + one highlight per included change). */
62
+ export interface VersionReleaseSummary {
63
+ /** The human-readable release name (see `deriveReleaseName`), or null. */
64
+ name: string | null;
65
+ lead: string;
66
+ highlights: { title: string; detail: string }[];
67
+ }
68
+
69
+ /** One change that shipped in a published version — its PR summary, for the
70
+ * version's openable "what shipped" list. Fields degrade to null/`PR #N` when
71
+ * candidate_summary didn't cover the PR (best-effort enrichment). */
72
+ export interface WorkspaceVersionChange {
73
+ changeId: string;
74
+ prNumber: number | null;
75
+ headSha: string;
76
+ title: string | null;
77
+ description: string | null;
78
+ fileCount: number | null;
79
+ linesAdded: number | null;
80
+ linesRemoved: number | null;
81
+ }
82
+
83
+ /** Per-version release enrichment, keyed by versionId (== the promoted aggregate sha). */
84
+ export interface VersionReleaseInfo {
85
+ releaseSummary: VersionReleaseSummary | null;
86
+ changes: WorkspaceVersionChange[];
87
+ }
88
+
89
+ /** One published version in the tenant's promotion history (promotion_status). */
68
90
  export interface WorkspaceVersion {
69
- /** The tenant Gitea SHA the version serves — the content identity
70
- * (`gitea-only-tenant-content-authority`). */
91
+ /** The tenant Gitea SHA the version serves — the content identity. */
71
92
  versionId: string;
93
+ /**
94
+ * The merchant-facing monotonic version number ("v42") — derived from first
95
+ * promotion order (see `versionNumber.ts`), shared with the "Live version"
96
+ * metric. The human label; the SHA is demoted to a technical disclosure.
97
+ */
98
+ number: number | null;
72
99
  /** How the version was published (a Gitea commit, or a content hash). */
73
100
  source: "commit" | "hash" | null;
74
101
  /** ISO time the version was PROMOTED onto `live` (the promotion event). */
@@ -84,30 +111,46 @@ export interface WorkspaceVersion {
84
111
  * digest lives only in the S3 archive the Worker can't enumerate. Never fabricated.
85
112
  */
86
113
  digest: string | null;
87
- /** The u5 immutable `/preview/<tenant>/rev/<sha>/` deep-link, or `null` without a SHA. */
114
+ /** The immutable `/<tenant>/preview/rev/<sha>/` deep-link, or `null` without a SHA. */
88
115
  revHref: string | null;
89
116
  /** The version currently serving live (the one you never roll back TO). */
90
117
  isCurrent: boolean;
118
+ /**
119
+ * The plain-language "what shipped in this version" summary, composed from the
120
+ * release receipt's included PRs + their candidate_summary. `null` when no
121
+ * receipt links this version to a PR set, or none carried summary prose.
122
+ */
123
+ releaseSummary: VersionReleaseSummary | null;
124
+ /**
125
+ * The PRs that shipped in this version (from its release receipt), each with its
126
+ * human summary — the version's openable "what shipped" list. Empty when no
127
+ * receipt links this version to a PR set.
128
+ */
129
+ changes: WorkspaceVersionChange[];
130
+ /** How many changes shipped in this version, or `null` when unknown (no receipt). */
131
+ changeCount: number | null;
91
132
  }
92
133
 
93
- /** How a rollback is honestly driven for this tenant (see the module HONEST BOUNDARY note). */
94
- export type RollbackMode = "control-plane" | "static-archive";
134
+ /**
135
+ * How a rollback is honestly driven for this tenant. `control-plane` (hosted) and
136
+ * `pointer` (static-publish) both roll back one-click via the instant KV `live`
137
+ * pointer flip; they render identical instant "Roll back" copy, differing only in
138
+ * whether the static digest column is shown.
139
+ */
140
+ export type RollbackMode = "control-plane" | "pointer";
95
141
 
96
142
  /** The versions-panel view model. */
97
143
  export interface VersionsView {
98
144
  /** The current live version + (static) the digest `live/` actually serves. */
99
- current: { versionId: string | null; digest: string | null };
145
+ current: { versionId: string | null; number: number | null; digest: string | null };
100
146
  /** Promotion history, newest first, current marked. */
101
147
  versions: WorkspaceVersion[];
102
148
  /** Whether the tenant publishes its public site to an external static target. */
103
149
  staticPublish: boolean;
104
150
  /**
105
- * `control-plane` a prior version rolls back one-click via `targetVersionId`
106
- * (instant pointer flip).
107
- * `static-archive` the console rolls back by an honest ONE-DISPATCH: it sends
108
- * `targetVersionId` alone and CI resolves the archive digest + re-publishes
109
- * asynchronously (`publish_pending` until verified live) — the surface must say
110
- * "completes via CI", never imply an instant flip.
151
+ * `control-plane` (hosted) and `pointer` (static-publish) both roll back
152
+ * one-click via `targetVersionId` (instant KV pointer flip) and render identical
153
+ * instant "Roll back" copy.
111
154
  */
112
155
  rollbackMode: RollbackMode;
113
156
  }
@@ -129,6 +172,12 @@ export interface BuildVersionsViewInput {
129
172
  currentDigest: string | null;
130
173
  /** Per-version manifest facts, keyed by versionId (absent ⇒ unknown facts). */
131
174
  manifests: Map<string, VersionManifestFacts>;
175
+ /**
176
+ * Per-version release enrichment, keyed by versionId (== the promoted aggregate
177
+ * sha on the release receipt). Absent ⇒ a version with no linked PR set (an
178
+ * honest empty release summary + no changes list — never fabricated).
179
+ */
180
+ releases?: Map<string, VersionReleaseInfo>;
132
181
  staticPublish: boolean;
133
182
  }
134
183
 
@@ -136,16 +185,31 @@ export interface BuildVersionsViewInput {
136
185
  * Assemble the versions panel from the promotion history + per-version manifest
137
186
  * facts + the current-live pointer. Pure. The current live row is marked; the
138
187
  * digest is populated ONLY for the current live of a static tenant (never faked
139
- * for priors). `revHref` is the u5 immutable link.
188
+ * for priors). `revHref` is the immutable link.
140
189
  */
141
190
  export function buildVersionsView(input: BuildVersionsViewInput): VersionsView {
142
- const { tenantId, promotions, currentVersionId, currentDigest, manifests, staticPublish } = input;
191
+ const {
192
+ tenantId,
193
+ promotions,
194
+ currentVersionId,
195
+ currentDigest,
196
+ manifests,
197
+ releases,
198
+ staticPublish,
199
+ } = input;
200
+
201
+ // The merchant-facing "v42" counter, derived from first-promotion order — the
202
+ // SAME derivation `resolveLiveVersion` feeds the "Live version" metric, so the
203
+ // panel and the metric always agree on a version's number.
204
+ const numbers = assignVersionNumbers(promotions);
143
205
 
144
206
  const versions: WorkspaceVersion[] = promotions.map((p) => {
145
207
  const facts = manifests.get(p.versionId);
208
+ const release = releases?.get(p.versionId);
146
209
  const isCurrent = currentVersionId != null && p.versionId === currentVersionId;
147
210
  return {
148
211
  versionId: p.versionId,
212
+ number: numbers.get(p.versionId) ?? null,
149
213
  source: facts?.source ?? null,
150
214
  promotedAt: p.at,
151
215
  createdAt: facts?.createdAt ?? null,
@@ -154,14 +218,21 @@ export function buildVersionsView(input: BuildVersionsViewInput): VersionsView {
154
218
  digest: isCurrent && staticPublish ? currentDigest : null,
155
219
  revHref: reviewDashboardRevisionHref(tenantId, p.versionId),
156
220
  isCurrent,
221
+ releaseSummary: release?.releaseSummary ?? null,
222
+ changes: release?.changes ?? [],
223
+ changeCount: release ? release.changes.length : null,
157
224
  };
158
225
  });
159
226
 
160
227
  return {
161
- current: { versionId: currentVersionId, digest: staticPublish ? currentDigest : null },
228
+ current: {
229
+ versionId: currentVersionId,
230
+ number: currentVersionId != null ? numbers.get(currentVersionId) ?? null : null,
231
+ digest: staticPublish ? currentDigest : null,
232
+ },
162
233
  versions,
163
234
  staticPublish,
164
- rollbackMode: staticPublish ? "static-archive" : "control-plane",
235
+ rollbackMode: staticPublish ? "pointer" : "control-plane",
165
236
  };
166
237
  }
167
238
 
@@ -182,13 +253,13 @@ export interface CandidateDiffView {
182
253
  headSha: string | null;
183
254
  /** The immutable version actually rendered + diffed — the reconcile ACTUAL. */
184
255
  versionId: string | null;
185
- /** The u5 immutable link to the exact reviewed SHA, or `null` without a head SHA. */
256
+ /** The immutable link to the exact reviewed SHA, or `null` without a head SHA. */
186
257
  revHref: string | null;
187
- /** dg3 evidence authority: authoritative (tied to head) / stale (last-good) / missing. */
258
+ /** Evidence authority: authoritative (tied to head) / stale (last-good) / missing. */
188
259
  authority: CandidateEvidenceAuthority;
189
260
  /** The artifact-level diff (candidate rendered version vs current live), sorted by path. */
190
261
  files: DiffEntry[];
191
- /** True when the candidate has a renderable version to diff (dg3 `ready`). */
262
+ /** True when the candidate has a renderable version to diff (the `ready` classification). */
192
263
  renderable: boolean;
193
264
  /** When NOT renderable, the honest reason (names why) — never a fake empty diff. */
194
265
  blockedReason: string | null;
@@ -206,7 +277,7 @@ export interface BuildCandidateDiffInput {
206
277
  /**
207
278
  * Diff a candidate's rendered version against the current live version, at the
208
279
  * artifact level (each path carries a content hash in its site-version manifest).
209
- * Composes dg3's `classifyCandidateForRender` (only a `ready` candidate has a
280
+ * Composes `classifyCandidateForRender` (only a `ready` candidate has a
210
281
  * version to diff — a blocked/queued one yields an HONEST `blockedReason`, never a
211
282
  * fabricated empty diff) and `candidateEvidenceAuthority` (so a diff of a last-good
212
283
  * version that LAGS the head is flagged `stale`, not passed off as the head).
@@ -289,10 +360,9 @@ export interface RollbackOutcome {
289
360
  * for BOTH planes without the surface having to know which it hit:
290
361
  * - a HOSTED (control-plane) rollback returns `{ rolledBack: true, toVersionId }`
291
362
  * on success (the pointer flip IS the rollback) or `{ error, reason }` on refusal;
292
- * - a STATIC rollback returns the orchestrator's honest `{ state, ... }` (u8).
363
+ * - a STATIC rollback returns the orchestrator's honest `{ state, ... }`.
293
364
  * The rule the surface depends on: `rolledBack` is true ONLY for a verified
294
- * `rolled_back` — a `publish_pending` is NEVER rendered as rolled back
295
- * (`storefront-atomic-accept-is-ship-orchestrator` / `static-publish-fails-closed`).
365
+ * `rolled_back` — a `publish_pending` is NEVER rendered as rolled back.
296
366
  * Pure.
297
367
  */
298
368
  export function interpretRollbackResponse(
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The shared "buffer-or-stream on `Accept`" plumbing for a long-running admin
3
+ * route. ONE emit-driven work function serves BOTH transports:
4
+ *
5
+ * - a non-streaming caller (the CLI, any `Accept: application/json` client) gets
6
+ * the buffered terminal JSON body + status the work resolved — `emit` is a no-op;
7
+ * - a streaming caller (`Accept: text/event-stream`, the admin Publish tab) gets
8
+ * each `emit(event, data)` as an SSE frame AS IT HAPPENS, then a terminal
9
+ * `result` event carrying the SAME payload the JSON body would have, then close.
10
+ *
11
+ * The work runs ONCE either way; the terminal payload + status are IDENTICAL. Only
12
+ * the delivery differs. This is the generalized form of the pattern
13
+ * `POST /api/preview/build` first shipped inline — extracted here so every
14
+ * long-running route (build / integrate / rebuild / ship) streams the same way
15
+ * without re-implementing the ReadableStream/encoder/close dance.
16
+ */
17
+
18
+ /**
19
+ * A buffered JSON Response — the non-streaming terminal body. Inlined (not imported
20
+ * from the change/grants controlplane) so this generic transport helper stays in the
21
+ * `lib/publish` render layer, respecting the renderer-no-controlplane boundary.
22
+ */
23
+ function jsonResponse(body: unknown, status = 200): Response {
24
+ return new Response(JSON.stringify(body), {
25
+ status,
26
+ headers: { "content-type": "application/json", "cache-control": "no-store" },
27
+ });
28
+ }
29
+
30
+ /** Emit one SSE event. A no-op in buffered (JSON) mode. */
31
+ export type SseEmit = (event: string, data: Record<string, unknown>) => void;
32
+
33
+ /** The terminal outcome of the work: the HTTP status + the JSON/`result` payload. */
34
+ export interface StreamWorkResult {
35
+ status: number;
36
+ payload: Record<string, unknown>;
37
+ }
38
+
39
+ /** True when the caller asked for an SSE stream (`Accept: text/event-stream`). */
40
+ export function wantsEventStream(request: Request): boolean {
41
+ return (request.headers.get("accept") || "").includes("text/event-stream");
42
+ }
43
+
44
+ /**
45
+ * Run `work` and deliver its terminal payload as either a buffered JSON Response
46
+ * (default) or a streamed SSE Response (when the caller sent
47
+ * `Accept: text/event-stream`). In stream mode each `emit` becomes an
48
+ * `event: <name>\ndata: <json>\n\n` frame; the terminal payload is sent as a
49
+ * `result` event and the stream closes. `onError` maps an UNEXPECTED throw (the
50
+ * work is expected to be fail-safe and return its own error payload) to a terminal
51
+ * result — in JSON mode it becomes the response body+status, in SSE mode the
52
+ * `result` event.
53
+ */
54
+ export async function respondBufferedOrStreamed(
55
+ request: Request,
56
+ work: (emit: SseEmit) => Promise<StreamWorkResult>,
57
+ opts?: { onError?: (error: unknown) => StreamWorkResult },
58
+ ): Promise<Response> {
59
+ const onError =
60
+ opts?.onError ??
61
+ ((error: unknown): StreamWorkResult => ({
62
+ status: 500,
63
+ payload: {
64
+ ok: false,
65
+ outcome: "failed",
66
+ errors: [`operation failed: ${error instanceof Error ? error.message : String(error)}`],
67
+ },
68
+ }));
69
+
70
+ // Buffered JSON mode — the CLI / any non-streaming caller. `emit` is a no-op; the
71
+ // work's own status + payload are returned verbatim (same contract as before).
72
+ if (!wantsEventStream(request)) {
73
+ try {
74
+ const { status, payload } = await work(() => {});
75
+ return jsonResponse(payload, status);
76
+ } catch (error) {
77
+ const { status, payload } = onError(error);
78
+ return jsonResponse(payload, status);
79
+ }
80
+ }
81
+
82
+ // SSE mode — stream each phase as it happens, then a terminal `result` event
83
+ // carrying the SAME payload the JSON body would have, then close. Workers stream a
84
+ // ReadableStream body natively — no infra.
85
+ const encoder = new TextEncoder();
86
+ const stream = new ReadableStream<Uint8Array>({
87
+ start(controller) {
88
+ const send: SseEmit = (event, data) => {
89
+ try {
90
+ controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
91
+ } catch {
92
+ /* controller already closed (client disconnected) — nothing to do */
93
+ }
94
+ };
95
+ void (async () => {
96
+ try {
97
+ const { payload } = await work(send);
98
+ send("result", payload);
99
+ } catch (error) {
100
+ send("result", onError(error).payload);
101
+ } finally {
102
+ try {
103
+ controller.close();
104
+ } catch {
105
+ /* already closed */
106
+ }
107
+ }
108
+ })();
109
+ },
110
+ });
111
+ return new Response(stream, {
112
+ status: 200,
113
+ headers: {
114
+ "content-type": "text/event-stream; charset=utf-8",
115
+ "cache-control": "no-cache, no-transform",
116
+ "x-accel-buffering": "no",
117
+ },
118
+ });
119
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Authenticated static-bundle STAGING upload — the Worker-side write surface that
3
+ * lets CI stage a materialized bundle's objects into the `STATIC_BUNDLES` R2
4
+ * binding WITHOUT a static R2 S3-compatible key pair (`R2_ACCESS_KEY_ID`/
5
+ * `R2_SECRET_ACCESS_KEY`). CI proves identity with the SAME OIDC→AWS→SSM→
6
+ * `private_key_jwt` chain it already uses to authenticate to MCP
7
+ * (`scripts/publish/ci-mcp-read-remote.mjs`): it mints a short-lived RS256
8
+ * `client_assertion` under its dedicated confidential client
9
+ * (`storefront-ci.tokenoftrust.com` / kid `storefront-ci-publish-1`, the key held
10
+ * only in SSM), audience-bound to THIS upload endpoint, and the Worker verifies
11
+ * the signature against that client's PUBLISHED public JWKS.
12
+ *
13
+ * This is a WRITE path to a production-serving bucket, so the auth boundary is
14
+ * deliberately as strong as the MCP leg:
15
+ * - RS256 signature over the CI client's JWKS (no shared static secret).
16
+ * - `iss == sub == <expected CI client id>` (RFC 7523 §3).
17
+ * - `aud == <this exact upload endpoint>` (RFC 8707 resource binding) — an
18
+ * assertion minted for MCP's token endpoint can never be replayed here, and
19
+ * vice versa.
20
+ * - `exp`/`iat` freshness (jose enforces `exp`; assertions live ~60s).
21
+ * - `jti` one-time-use replay rejection (KV-backed), mirroring MCP's replay
22
+ * store — so a captured assertion can't be replayed inside its TTL window.
23
+ * - Object key confined to `versions/<digest>/…`: a valid CI credential can
24
+ * only write immutable version archives, never the `live/` pointer or any
25
+ * other bucket key.
26
+ *
27
+ * The KEY SCHEME (`versions/<digest>/<rel>`) MUST byte-match the read side
28
+ * (`@tot/public-runtime` `staticBundleObjectKey`/`staticBundleManifestKey`) and
29
+ * the CI writer (`scripts/publish/lib/r2-publish.mjs` `r2ObjectKey`) — the same
30
+ * lockstep those two already keep. MANIFEST-LAST ordering (the manifest is the
31
+ * "fully staged" signal) is the CALLER's responsibility (CI writes it last, the
32
+ * same as `writeBundleToR2`); this surface writes exactly the one object it is
33
+ * handed per request.
34
+ */
35
+ import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from "jose";
36
+
37
+ /** The only signature algorithm accepted (matches the CI minter). */
38
+ export const STAGE_BUNDLE_UPLOAD_ALG = "RS256";
39
+
40
+ /** The route this surface is mounted at — the canonical `aud`/resource identifier. */
41
+ export const STAGE_BUNDLE_UPLOAD_PATH = "/api/publish/stage-bundle";
42
+
43
+ /** Default CI confidential-client id (`iss == sub`), overridable per env. */
44
+ export const DEFAULT_STAGE_BUNDLE_UPLOAD_ISSUER = "storefront-ci.tokenoftrust.com";
45
+
46
+ /** How long a used `jti` is remembered — the assertion TTL (~60s) plus clock skew. */
47
+ export const STAGE_BUNDLE_JTI_TTL_SECONDS = 120;
48
+
49
+ /** Clock skew tolerated on `exp`/`iat`, in seconds. */
50
+ const CLOCK_TOLERANCE_SECONDS = 5;
51
+
52
+ /**
53
+ * The `versions/<digest>/<rel>` keys a CI credential is allowed to write. The
54
+ * digest is a lowercase hex tree digest; `rel` is a bundle-relative POSIX path
55
+ * with no traversal. Anything else (a bare key, `live/…`, an absolute path, a
56
+ * `..` segment) is refused with 400 — the write authority is bounded to
57
+ * immutable version archives regardless of a valid signature.
58
+ */
59
+ const VERSION_KEY_RE = /^versions\/[a-f0-9]{8,}\/.+$/;
60
+
61
+ /** Minimal R2 surface this module needs — structural, so tests pass a fake. */
62
+ export interface StageBundleUploadBucket {
63
+ head(key: string): Promise<{ key: string } | null>;
64
+ put(
65
+ key: string,
66
+ value: ArrayBuffer | ArrayBufferView,
67
+ options?: { httpMetadata?: { contentType?: string } },
68
+ ): Promise<unknown>;
69
+ }
70
+
71
+ /**
72
+ * One-time-use `jti` store. `checkAndRemember` returns `true` when the `jti` was
73
+ * unseen (and records it), `false` when it was already used (a replay). Backed by
74
+ * KV in the deployed Worker; omitted in dev/tests where no KV is bound (replay
75
+ * protection then relies on the ~60s `aud`-bound `exp` window alone).
76
+ */
77
+ export interface JtiReplayStore {
78
+ checkAndRemember(jti: string, ttlSeconds: number): Promise<boolean>;
79
+ }
80
+
81
+ export interface StageBundleUploadConfig {
82
+ /** The CI client's published PUBLIC JWKS (verifies the assertion signature). */
83
+ jwks: JSONWebKeySet;
84
+ /** Expected `iss == sub` (the CI confidential-client id). */
85
+ issuer: string;
86
+ /** Expected `aud` — this exact upload endpoint URL (RFC 8707 resource). */
87
+ audience: string;
88
+ }
89
+
90
+ /** A KV-namespace-shaped store (`get`/`put`) — the deployed jti replay backing. */
91
+ export interface JtiKvLike {
92
+ get(key: string): Promise<string | null>;
93
+ put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>;
94
+ }
95
+
96
+ /** Build a KV-backed {@link JtiReplayStore}. Keyed under a dedicated prefix. */
97
+ export function kvJtiReplayStore(kv: JtiKvLike): JtiReplayStore {
98
+ return {
99
+ async checkAndRemember(jti, ttlSeconds) {
100
+ const key = `stage-bundle-jti:${jti}`;
101
+ if (await kv.get(key)) return false;
102
+ // KV's minimum expirationTtl is 60s; the TTL default already exceeds it.
103
+ await kv.put(key, "1", { expirationTtl: Math.max(60, ttlSeconds) });
104
+ return true;
105
+ },
106
+ };
107
+ }
108
+
109
+ function json(status: number, body: Record<string, unknown>): Response {
110
+ return new Response(JSON.stringify(body), {
111
+ status,
112
+ headers: { "content-type": "application/json" },
113
+ });
114
+ }
115
+
116
+ /** Extract the bearer assertion from the Authorization header, or null. */
117
+ function bearer(request: Request): string | null {
118
+ const h = request.headers.get("authorization");
119
+ if (!h) return null;
120
+ const m = /^Bearer (.+)$/.exec(h.trim());
121
+ return m ? m[1]!.trim() : null;
122
+ }
123
+
124
+ /**
125
+ * Verify the CI `client_assertion`: RS256 signature over the configured JWKS,
126
+ * `iss`/`sub`/`aud` exact match, `exp` freshness, and one-time `jti`. Returns the
127
+ * verified payload, or a `Response` (401) to return verbatim on any failure — no
128
+ * failure detail beyond a stable machine reason is leaked.
129
+ */
130
+ export async function verifyUploadAssertion(
131
+ token: string,
132
+ config: StageBundleUploadConfig,
133
+ jtiStore?: JtiReplayStore,
134
+ ): Promise<{ payload: JWTPayload } | { reject: Response }> {
135
+ let payload: JWTPayload;
136
+ try {
137
+ const keys = createLocalJWKSet(config.jwks);
138
+ ({ payload } = await jwtVerify(token, keys, {
139
+ algorithms: [STAGE_BUNDLE_UPLOAD_ALG],
140
+ issuer: config.issuer,
141
+ audience: config.audience,
142
+ clockTolerance: CLOCK_TOLERANCE_SECONDS,
143
+ }));
144
+ } catch {
145
+ // Signature, iss/aud mismatch, or expiry — one opaque 401 (never which).
146
+ return { reject: json(401, { error: "invalid_assertion" }) };
147
+ }
148
+ // RFC 7523 §3: sub must equal the client id (iss). jose verifies iss but not
149
+ // that sub matches it, so assert it explicitly.
150
+ if (payload.sub !== config.issuer) {
151
+ return { reject: json(401, { error: "invalid_assertion" }) };
152
+ }
153
+ const jti = typeof payload.jti === "string" ? payload.jti : "";
154
+ if (!jti) {
155
+ return { reject: json(401, { error: "invalid_assertion" }) };
156
+ }
157
+ if (jtiStore) {
158
+ const fresh = await jtiStore.checkAndRemember(jti, STAGE_BUNDLE_JTI_TTL_SECONDS);
159
+ if (!fresh) return { reject: json(401, { error: "assertion_replayed" }) };
160
+ }
161
+ return { payload };
162
+ }
163
+
164
+ /** True iff `key` is a well-formed, traversal-free `versions/<digest>/<rel>` key. */
165
+ export function isSafeVersionKey(key: string): boolean {
166
+ if (!VERSION_KEY_RE.test(key)) return false;
167
+ return !key.split("/").some((seg) => seg === ".." || seg === "." || seg === "");
168
+ }
169
+
170
+ /**
171
+ * Handle one authenticated staging request against `bucket`:
172
+ * - `PUT ?key=versions/<digest>/<rel>` — write the request body as that object.
173
+ * - `HEAD ?key=versions/<digest>/<rel>` — 200 if present, 404 if not (the CI
174
+ * idempotency probe, mirroring `r2ObjectExists`).
175
+ * Both require a valid CI assertion; the key is confined to `versions/<digest>/`.
176
+ */
177
+ export async function handleStageBundleUpload(opts: {
178
+ request: Request;
179
+ bucket: StageBundleUploadBucket;
180
+ config: StageBundleUploadConfig;
181
+ jtiStore?: JtiReplayStore;
182
+ }): Promise<Response> {
183
+ const { request, bucket, config, jtiStore } = opts;
184
+ const method = request.method.toUpperCase();
185
+ if (method !== "PUT" && method !== "HEAD") {
186
+ return json(405, { error: "method_not_allowed" });
187
+ }
188
+
189
+ const token = bearer(request);
190
+ if (!token) return json(401, { error: "missing_bearer" });
191
+
192
+ // HEAD is a read/probe and must NOT consume the one-time jti (a PUT retry after
193
+ // a probe would then falsely 401 as a replay). Only PUT records the jti.
194
+ const verified = await verifyUploadAssertion(
195
+ token,
196
+ config,
197
+ method === "PUT" ? jtiStore : undefined,
198
+ );
199
+ if ("reject" in verified) return verified.reject;
200
+
201
+ const key = new URL(request.url).searchParams.get("key") ?? "";
202
+ if (!isSafeVersionKey(key)) {
203
+ return json(400, { error: "invalid_key" });
204
+ }
205
+
206
+ if (method === "HEAD") {
207
+ const obj = await bucket.head(key);
208
+ return new Response(null, { status: obj ? 200 : 404 });
209
+ }
210
+
211
+ const body = await request.arrayBuffer();
212
+ const contentType = request.headers.get("content-type") ?? undefined;
213
+ await bucket.put(key, body, contentType ? { httpMetadata: { contentType } } : undefined);
214
+ return json(200, { key, staged: true });
215
+ }