@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
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Digest-addressed static-bundle serving (ADR 0012 —
3
+ * `docs/architecture/decisions/0012-instant-static-rollback-pointer-substrate.md`),
4
+ * dedup-aware (per-file content-addressed asset dedup,
5
+ * `docs/epics/proposed/storefront-instant-static-rollback.md` Addendum §A).
6
+ *
7
+ * Reads an already-materialized, already-staged static bundle
8
+ * (`scripts/publish/materialize-tenant.mjs` output, staged by
9
+ * `scripts/publish/stage-bundle.mjs` to `versions/<treeDigest>/…` PLUS the
10
+ * shared `assets/<sha256>` pool) straight out of R2 and serves the bytes
11
+ * VERBATIM — no re-render, no per-request CSP-nonce middleware (the bundle
12
+ * carries one pinned nonce for its whole tree; see the ADR's "Consequences"
13
+ * section). This is the read half: the write path (what PUTs a bundle into this
14
+ * R2 bucket) is `scripts/publish/lib/r2-runner.mjs`, which stages every
15
+ * `versions/<digest>/` bundle at candidate-prebuild time. Every static-publish
16
+ * tenant's production reads are served through here.
17
+ *
18
+ * DIGEST CONTRACT (RECONCILED, T14 — ADR 0012): the caller resolves "what to
19
+ * serve" via `resolveChannelDigest(get, tenantId, "live")` — the `channelDigests`
20
+ * companion to the `live` channel pointer, NOT `resolveChannelVersion` (which
21
+ * returns the versionId / Gitea sha, a DIFFERENT namespace than the treeDigest).
22
+ * `gatedPromote` (go-live) and `rollbackCandidateAtomic` (rollback) write the
23
+ * staged bundle's real `treeDigest` to `channelDigests.live` in the SAME CAS flip
24
+ * that moves the versionId, so this module gets the exact R2 key prefix
25
+ * `versions/<treeDigest>/`. A static tenant whose `live` has a versionId but no
26
+ * digest companion yet resolves to `null` here and fails closed (see below)
27
+ * rather than serving the wrong bytes under the versionId.
28
+ *
29
+ * T6 KEY RESOLUTION (`kind: "route"` vs `kind: "asset"`, `scripts/publish/lib/
30
+ * asset-pool.mjs`'s classification, carried verbatim in `manifest.files`): the
31
+ * manifest is now read on EVERY request (not just HTML, as before T6) because
32
+ * it's the ONLY source of truth for which R2 key a bundle-relative path
33
+ * resolves to — `kind: "route"` files still live at `versions/<digest>/<rel>`
34
+ * (rendered fresh per version), `kind: "asset"` files live at the SHARED,
35
+ * content-addressed `assets/<sha256><ext>` pool (`staticBundleAssetPoolKey`,
36
+ * kept in lockstep with `scripts/publish/lib/asset-pool.mjs`'s `assetPoolKey`
37
+ * — that module can't be imported here, this is the Worker bundle, that one
38
+ * runs under plain node). A `rel` absent from `manifest.files` (a genuinely
39
+ * unexpected path, or a pre-T6 manifest with no `files` classification) falls
40
+ * back to the OLD `versions/<digest>/<rel>` lookup — fail-closed on a miss
41
+ * either way, never a guess.
42
+ */
43
+ import { assetContentType } from "./tenant-assets.js";
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // R2 seam — same structural subset tenant-assets.ts uses (no workers-types dep)
47
+ // ---------------------------------------------------------------------------
48
+
49
+ export interface StaticBundleR2ObjectLike {
50
+ body: ReadableStream<Uint8Array> | null;
51
+ arrayBuffer(): Promise<ArrayBuffer>;
52
+ size?: number;
53
+ }
54
+
55
+ export interface StaticBundleR2BucketLike {
56
+ get(key: string): Promise<StaticBundleR2ObjectLike | null>;
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Path mapping — request pathname -> bundle-relative object path
61
+ // ---------------------------------------------------------------------------
62
+
63
+ /**
64
+ * Reject empty, absolute-outside-bundle, or traversal-bearing relative paths
65
+ * (mirrors `tenant-assets.ts`'s `isSafeRel`, duplicated here to keep this
66
+ * module import-independent of the assets pointer model).
67
+ */
68
+ function isSafeRel(rel: string): boolean {
69
+ if (!rel) return false;
70
+ return !rel.split("/").some((seg) => seg === ".." || seg === ".");
71
+ }
72
+
73
+ /**
74
+ * Map a request pathname to the bundle-relative file the materializer wrote
75
+ * for it — the SAME transform `scripts/publish/lib/materialize.mjs`'s
76
+ * `bundlePathForRoute` (routes) and `assetFilePath` (assets) apply, ported
77
+ * here as one pure function since `scripts/` is not importable from the
78
+ * Worker bundle:
79
+ * "/" -> "index.html"
80
+ * "/about" -> "about/index.html" (directory-index for pretty URLs)
81
+ * "/a/b/" -> "a/b/index.html"
82
+ * "/sitemap.xml" -> "sitemap.xml" (a real file extension: as-is)
83
+ * "/_astro/x.js" -> "_astro/x.js" (asset: as-is)
84
+ * Returns null for an unsafe path (traversal, decode failure).
85
+ */
86
+ export function staticBundleRelPath(pathname: string): string | null {
87
+ let decoded: string;
88
+ try {
89
+ decoded = decodeURIComponent(pathname);
90
+ } catch {
91
+ return null;
92
+ }
93
+ const clean = decoded.replace(/^\/+/, "").replace(/\/+$/, "");
94
+ if (clean === "") return "index.html";
95
+ if (!isSafeRel(clean)) return null;
96
+ const last = clean.split("/").pop() || "";
97
+ if (/\.[a-z0-9]+$/i.test(last)) return clean;
98
+ return `${clean}/index.html`;
99
+ }
100
+
101
+ /** Full R2 object key for a `route`-kind bundle-relative path at a given digest. */
102
+ export function staticBundleObjectKey(digest: string, rel: string): string {
103
+ return `versions/${digest}/${rel}`;
104
+ }
105
+
106
+ /** R2 key for a digest's bundle manifest (materialize-tenant.mjs's `manifest.json`). */
107
+ export function staticBundleManifestKey(digest: string): string {
108
+ return `versions/${digest}/manifest.json`;
109
+ }
110
+
111
+ /**
112
+ * T6: the shared content-addressed pool key for one asset's hash — MUST stay
113
+ * in lockstep with `scripts/publish/lib/asset-pool.mjs`'s `assetPoolKey`
114
+ * (same extension-preserving `assets/<sha256hex><ext>` shape; that module
115
+ * can't be imported here since this is the Worker bundle and that one runs
116
+ * under plain node — both sides' tests assert the exact key shape).
117
+ */
118
+ export function staticBundleAssetPoolKey(hash: string, rel: string): string {
119
+ const dot = rel.lastIndexOf(".");
120
+ const slash = rel.lastIndexOf("/");
121
+ const ext = dot > slash ? rel.slice(dot) : "";
122
+ return `assets/${hash}${ext}`;
123
+ }
124
+
125
+ /**
126
+ * Resolve the R2 key to fetch for a `rel` bundle path, using the manifest's
127
+ * T6 file classification: `kind: "asset"` -> the shared pool key (by content
128
+ * hash, NOT the version); anything else (`kind: "route"`, or `rel` absent
129
+ * from `files` entirely — a pre-T6 manifest, or a genuinely unlisted path) ->
130
+ * the per-version `versions/<digest>/<rel>` key, same as before T6.
131
+ */
132
+ export function resolveStaticBundleObjectKey(
133
+ digest: string,
134
+ rel: string,
135
+ files: StaticBundleManifest["files"] | undefined,
136
+ ): string {
137
+ const entry = files?.[rel];
138
+ if (entry?.kind === "asset" && entry.hash) {
139
+ return staticBundleAssetPoolKey(entry.hash, rel);
140
+ }
141
+ return staticBundleObjectKey(digest, rel);
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Manifest (CSP pinned-nonce header) — read once per hit for an HTML response
146
+ // ---------------------------------------------------------------------------
147
+
148
+ /** One `manifest.files` entry (T6, `scripts/publish/lib/asset-pool.mjs`'s `classifyBundleFiles` shape). */
149
+ export interface StaticBundleFileEntry {
150
+ hash: string;
151
+ kind: "route" | "asset";
152
+ }
153
+
154
+ /** The slice of `materialize-tenant.mjs`'s manifest this read path needs. */
155
+ export interface StaticBundleManifest {
156
+ treeDigest: string;
157
+ pinnedNonce: string;
158
+ csp: { headerName: string; value: string } | null;
159
+ /** T6 classification: relPath -> {hash, kind}. Absent on a pre-T6 manifest — every lookup then falls back to the old versions/<digest>/<rel> key (see `resolveStaticBundleObjectKey`). */
160
+ files?: Record<string, StaticBundleFileEntry>;
161
+ }
162
+
163
+ function parseManifest(raw: string): StaticBundleManifest | null {
164
+ try {
165
+ const m = JSON.parse(raw) as Partial<StaticBundleManifest>;
166
+ if (typeof m.treeDigest !== "string" || typeof m.pinnedNonce !== "string") return null;
167
+ const csp =
168
+ m.csp &&
169
+ typeof (m.csp as { headerName?: unknown }).headerName === "string" &&
170
+ typeof (m.csp as { value?: unknown }).value === "string"
171
+ ? (m.csp as { headerName: string; value: string })
172
+ : null;
173
+ let files: StaticBundleManifest["files"];
174
+ if (m.files && typeof m.files === "object") {
175
+ files = {};
176
+ for (const [rel, entry] of Object.entries(m.files as Record<string, unknown>)) {
177
+ const e = entry as Partial<StaticBundleFileEntry> | null;
178
+ if (
179
+ e &&
180
+ typeof e.hash === "string" &&
181
+ (e.kind === "route" || e.kind === "asset")
182
+ ) {
183
+ files[rel] = { hash: e.hash, kind: e.kind };
184
+ }
185
+ }
186
+ }
187
+ return { treeDigest: m.treeDigest, pinnedNonce: m.pinnedNonce, csp, files };
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+
193
+ /** Read+parse a digest's `manifest.json` from R2. Null on any miss/malformed data. */
194
+ export async function readStaticBundleManifest(
195
+ bucket: StaticBundleR2BucketLike,
196
+ digest: string,
197
+ ): Promise<StaticBundleManifest | null> {
198
+ const object = await bucket.get(staticBundleManifestKey(digest));
199
+ if (!object) return null;
200
+ const text = new TextDecoder().decode(await object.arrayBuffer());
201
+ return parseManifest(text);
202
+ }
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Serve — resolve digest, stream the object from R2, fail closed on a miss
206
+ // ---------------------------------------------------------------------------
207
+
208
+ export interface ServeStaticBundleDeps {
209
+ bucket: StaticBundleR2BucketLike;
210
+ /**
211
+ * The digest to serve, already resolved by the caller (typically
212
+ * `resolveChannelVersion(get, tenantId, "live")` — see the module
213
+ * docstring's DIGEST CONTRACT). `null` means "pointer-serving is on for
214
+ * this tenant but nothing is live yet" — fails closed, same as a missing
215
+ * object, per the ADR: this path is only ever reached for a confirmed
216
+ * pointer-serving tenant, so an unresolved digest is itself a fault, not a
217
+ * "fall through to SSR" signal.
218
+ */
219
+ digest: string | null;
220
+ /** Best-effort diagnostic sink (never throws, never blocks the response). */
221
+ onFailClosed?: (detail: string) => void;
222
+ }
223
+
224
+ /** Structured detail for the one fail-closed shape this module can produce. */
225
+ export class StaticBundleUnavailableError extends Error {
226
+ readonly code = "static-bundle-unavailable" as const;
227
+ constructor(detail: string) {
228
+ super(detail);
229
+ this.name = "StaticBundleUnavailableError";
230
+ }
231
+ }
232
+
233
+ function failClosed(deps: ServeStaticBundleDeps, detail: string): Response {
234
+ try {
235
+ deps.onFailClosed?.(detail);
236
+ } catch {
237
+ /* diagnostic sink must never break the response path */
238
+ }
239
+ // 503, not a 200 masquerading as content and not a 404 (which would read as
240
+ // "this route doesn't exist" — it does, the bundle is just unreachable) and
241
+ // never an SSR fallthrough: this route is ONLY reached for a tenant
242
+ // confirmed to be on the pointer-serving model, so an unreadable bundle is
243
+ // an infra fault to surface (verify/monitoring), not a content gap.
244
+ return new Response("Static bundle unavailable", {
245
+ status: 503,
246
+ headers: {
247
+ "cache-control": "no-store",
248
+ "x-tot-static-bundle": "fail-closed",
249
+ },
250
+ });
251
+ }
252
+
253
+ /** Cache-Control for a `route`-kind file (HTML/xml/txt — rendered fresh per version, stable non-digest url). */
254
+ const ROUTE_CACHE_CONTROL = "public, max-age=0, s-maxage=300, stale-while-revalidate=86400";
255
+ /** Cache-Control for an `asset`-kind file — the pool key IS the content hash, so caching forever is always correct (epic §A). */
256
+ const ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
257
+
258
+ /**
259
+ * Serve `pathname` from the digest-addressed static bundle in R2.
260
+ *
261
+ * Returns `null` — the ONE "fall through to SSR" signal — when the manifest's
262
+ * `files` map proves `pathname` is not part of this bundle's static surface:
263
+ * app surfaces (/admin, /cockpit, checkout, …) share the tenant's host but are
264
+ * never materialized into a bundle, so the caller must render them. Only a
265
+ * manifest WITH `files` can prove non-membership; a pre-T6 manifest keeps the
266
+ * fetch-then-fail-closed path.
267
+ *
268
+ * Every OTHER outcome (hit, unsafe path, unresolved digest, missing manifest,
269
+ * missing object for a LISTED file) terminates the request here rather than
270
+ * falling through — a staged digest's listed route must never silently degrade
271
+ * into SSR (wrong/divergent bytes), per the ADR's fail-closed guidance.
272
+ *
273
+ * T6: the manifest is now read FIRST, on every request (not just HTML, as
274
+ * before T6) — it's the only source of truth for whether `rel` resolves to
275
+ * the per-version `versions/<digest>/<rel>` key or the shared content-addressed
276
+ * `assets/<sha256>` pool key (`resolveStaticBundleObjectKey`). A missing
277
+ * manifest fails closed the same way a missing object always has: the
278
+ * manifest is deliberately the LAST thing `stage-bundle.mjs` writes for a
279
+ * digest (see `s3-publish.mjs`'s `buildManifestPromoteStep`), so its absence
280
+ * means this digest was never fully staged — serving from it would be wrong
281
+ * before T6 too, this just makes the fail-closed check unconditional.
282
+ *
283
+ * On a hit, serves the R2 bytes VERBATIM with a Cache-Control keyed by the
284
+ * manifest's file `kind` (long-lived immutable for the shared `asset` pool —
285
+ * safe because the key itself is the content hash; short/revalidating for a
286
+ * `route` doc, rendered fresh per version at a STABLE, non-digest url) and,
287
+ * for an HTML response, the bundle's PINNED CSP header read back from the
288
+ * same manifest read — never the Worker's per-request nonce middleware, per
289
+ * the ADR's CSP-consequences note.
290
+ */
291
+ export async function serveStaticBundle(
292
+ deps: ServeStaticBundleDeps,
293
+ pathname: string,
294
+ ): Promise<Response | null> {
295
+ const { bucket, digest } = deps;
296
+ if (!digest) {
297
+ return failClosed(deps, "pointer-serving tenant has no resolved live digest");
298
+ }
299
+
300
+ const rel = staticBundleRelPath(pathname);
301
+ if (!rel) {
302
+ return failClosed(deps, `unsafe/unparseable path: ${JSON.stringify(pathname)}`);
303
+ }
304
+
305
+ const manifest = await readStaticBundleManifest(bucket, digest);
306
+ if (!manifest) {
307
+ return failClosed(deps, `missing/unreadable manifest for digest ${digest}`);
308
+ }
309
+
310
+ const entry = manifest.files?.[rel];
311
+ if (manifest.files && !entry) {
312
+ // Provably not in this bundle's static surface — an app surface (/admin,
313
+ // /cockpit, …) or a route this version never materialized. SSR owns it.
314
+ return null;
315
+ }
316
+ const objectKey = resolveStaticBundleObjectKey(digest, rel, manifest.files);
317
+ const object = await bucket.get(objectKey);
318
+ if (!object) {
319
+ return failClosed(deps, `missing R2 object ${objectKey}`);
320
+ }
321
+
322
+ const isHtml = rel.endsWith(".html");
323
+ // Cache tier: use the manifest's `kind` classification when present.
324
+ // A manifest with no `files` at all falls back to the ORIGINAL
325
+ // isHtml-based heuristic — every non-html path used to be treated as a
326
+ // long-cacheable asset, which this preserves exactly for an old bundle
327
+ // this Worker version might still encounter mid-rollout.
328
+ const cacheControl = entry
329
+ ? entry.kind === "asset"
330
+ ? ASSET_CACHE_CONTROL
331
+ : ROUTE_CACHE_CONTROL
332
+ : isHtml
333
+ ? ROUTE_CACHE_CONTROL
334
+ : ASSET_CACHE_CONTROL;
335
+ const headers: Record<string, string> = {
336
+ "content-type": assetContentType(rel),
337
+ // Cache-key-by-digest lives in the caller's edge-cache layer for `route`
338
+ // keys (versions/<digest>/<rel> is digest-scoped — a pointer flip is a
339
+ // clean cache miss on the NEW digest's keys, never a stale serve of the
340
+ // old one); `asset` keys are ADDITIONALLY digest-independent (content-
341
+ // addressed by hash), so they're safe to cache forever downstream too.
342
+ // This header governs downstream/browser caching of the STABLE URL:
343
+ "cache-control": cacheControl,
344
+ "x-tot-static-bundle": "hit",
345
+ "x-tot-static-bundle-digest": digest,
346
+ };
347
+
348
+ if (isHtml && manifest.csp) {
349
+ headers[manifest.csp.headerName] = manifest.csp.value;
350
+ }
351
+
352
+ if (typeof object.size === "number") {
353
+ headers["content-length"] = String(object.size);
354
+ }
355
+ const body: BodyInit = object.body ?? (await object.arrayBuffer());
356
+ return new Response(body, { status: 200, headers });
357
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Versioned tenant static-asset store (decision `assets-r2-versioned`).
2
+ * Versioned tenant static-asset store.
3
3
  *
4
4
  * Tenant static assets (`tenants/<id>/public/**` — logo, CSS, ~36 binaries) are
5
5
  * served from **R2, version-scoped**, replacing the build-time
@@ -275,6 +275,46 @@ export interface IngestResult {
275
275
  count: number;
276
276
  }
277
277
 
278
+ const R2_PUT_MAX_ATTEMPTS = 3;
279
+ const R2_PUT_RETRY_DELAYS_MS = [100, 400] as const;
280
+
281
+ function errorField(error: unknown, field: string): unknown {
282
+ if (typeof error !== "object" || error === null || !(field in error)) return undefined;
283
+ return (error as Record<string, unknown>)[field];
284
+ }
285
+
286
+ function isTransientR2PutError(error: unknown): boolean {
287
+ const code = String(errorField(error, "code") ?? "");
288
+ const status = Number(errorField(error, "status") ?? errorField(error, "statusCode"));
289
+ const message = error instanceof Error ? error.message : String(error);
290
+ return (
291
+ code === "10001" ||
292
+ code === "InternalError" ||
293
+ (Number.isFinite(status) && status >= 500 && status <= 599) ||
294
+ /\b10001\b|internal error|temporarily unavailable|service unavailable|timed? out/i.test(message)
295
+ );
296
+ }
297
+
298
+ function waitForR2Retry(ms: number): Promise<void> {
299
+ return new Promise((resolve) => setTimeout(resolve, ms));
300
+ }
301
+
302
+ async function putAssetWithRetry(
303
+ bucket: R2BucketLike,
304
+ key: string,
305
+ bytes: Uint8Array,
306
+ ): Promise<void> {
307
+ for (let attempt = 1; attempt <= R2_PUT_MAX_ATTEMPTS; attempt++) {
308
+ try {
309
+ await bucket.put(key, bytes);
310
+ return;
311
+ } catch (error) {
312
+ if (attempt === R2_PUT_MAX_ATTEMPTS || !isTransientR2PutError(error)) throw error;
313
+ await waitForR2Retry(R2_PUT_RETRY_DELAYS_MS[attempt - 1]!);
314
+ }
315
+ }
316
+ }
317
+
278
318
  /**
279
319
  * Write a version's public assets to R2 under `tenants/<id>/<versionId>/<rel>`.
280
320
  * Content-addressed by the (caller-supplied or computed) snapshot id → immutable
@@ -294,7 +334,9 @@ export async function ingestAssetVersion(
294
334
  const keys: string[] = [];
295
335
  for (const a of assets) {
296
336
  const key = tenantAssetKey(tenantId, vid, a.rel);
297
- await bucket.put(key, a.bytes);
337
+ // The key and bytes are immutable for this version, so retrying an ambiguous
338
+ // transient R2 failure is idempotent even if the first write landed server-side.
339
+ await putAssetWithRetry(bucket, key, a.bytes);
298
340
  keys.push(key);
299
341
  }
300
342
  return { versionId: vid, keys, count: keys.length };
@@ -411,6 +453,16 @@ export interface ServeTenantAssetDeps {
411
453
  kvGet: (key: string) => Promise<string | null>;
412
454
  /** Channel to serve; default "live". Pass `locals.customizationEnv`. */
413
455
  channel?: CustomizationEnv;
456
+ /**
457
+ * Explicit snapshot versionId that OVERRIDES the channel→version pointer. Set for
458
+ * a CANDIDATE preview session: a candidate's assets are ingested to R2 keyed by
459
+ * the candidate versionId but NEVER pointed to by the shared test/live channel
460
+ * (the no-channel-flip invariant), so without this the candidate's NEW assets are
461
+ * materialized-but-unreachable. The caller must have authorized the viewer for
462
+ * THIS tenant (the versionId is a content hash, tenant-scoped by the R2 key) —
463
+ * see the `/tenants/<id>` route's preview-token check.
464
+ */
465
+ versionId?: string;
414
466
  /** Request `If-None-Match` header, for conditional GET (304). */
415
467
  ifNoneMatch?: string | null;
416
468
  }
@@ -434,8 +486,13 @@ export async function serveTenantAsset(
434
486
  if (!parsed) return null;
435
487
  const { tenantId, rel } = parsed;
436
488
 
437
- const pointer = await readAssetsPointer({ get: deps.kvGet }, tenantId);
438
- const versionId = resolveAssetVersion(pointer, deps.channel ?? "live");
489
+ // A candidate-preview override pins the snapshot directly; otherwise resolve the
490
+ // channel→version pointer (live/test) as usual.
491
+ let versionId = deps.versionId ?? null;
492
+ if (!versionId) {
493
+ const pointer = await readAssetsPointer({ get: deps.kvGet }, tenantId);
494
+ versionId = resolveAssetVersion(pointer, deps.channel ?? "live");
495
+ }
439
496
  if (!versionId) return null; // non-versioned tenant → fall through
440
497
 
441
498
  const etag = `"${versionId}"`;
@@ -477,3 +534,85 @@ function matchesEtag(ifNoneMatch: string, etag: string): boolean {
477
534
  .split(",")
478
535
  .some((t) => t.trim() === "*" || norm(t) === target);
479
536
  }
537
+
538
+ // ---------------------------------------------------------------------------
539
+ // Verify — fetch-confirm a version's app-asset subtree actually materialized
540
+ // ---------------------------------------------------------------------------
541
+
542
+ /** Outcome of {@link verifyMaterializedAssets}. */
543
+ export interface AssetVerificationResult {
544
+ /** App-asset objects expected to serve at this version. */
545
+ expected: number;
546
+ /** How many were confirmed present + non-empty in R2. */
547
+ verified: number;
548
+ /** The served relative paths (or artifact paths) that did NOT materialize. */
549
+ missing: string[];
550
+ /** Set when the verification itself could not run (fail-closed, not a pass). */
551
+ error?: string;
552
+ }
553
+
554
+ /** How many R2 existence probes run at once — matches the runtime's ~6/host reality. */
555
+ const VERIFY_PROBE_POOL = 6;
556
+
557
+ /**
558
+ * Confirm every app-asset artifact path is present + non-empty in R2 at
559
+ * `versionId` — the EXACT object `serveTenantAsset` streams for `/tenants/<id>/<rel>`.
560
+ * Positive, fail-closed evidence for the version-scoped asset lane: an asset whose
561
+ * path can't normalize to a served rel, or is absent/empty in R2, is reported
562
+ * `missing`; a systemic fault (the bucket throwing on every probe) reports the whole
563
+ * subtree missing with an `error` rather than a vacuous pass. Checking the object at
564
+ * the published `versionId` (not an HTTP self-fetch) is channel-independent: the
565
+ * served route may resolve a different channel→version pointer per request, but a
566
+ * version's bytes are fixed. `bucket` needs only `get`; the body is never read.
567
+ */
568
+ export async function verifyMaterializedAssets(
569
+ bucket: Pick<R2BucketLike, "get">,
570
+ tenantId: string,
571
+ versionId: string,
572
+ appAssetArtifactPaths: readonly string[],
573
+ ): Promise<AssetVerificationResult> {
574
+ const expected = appAssetArtifactPaths.length;
575
+ if (expected === 0) return { expected: 0, verified: 0, missing: [] };
576
+
577
+ try {
578
+ const missing: string[] = [];
579
+ let verified = 0;
580
+ let next = 0;
581
+ const worker = async (): Promise<void> => {
582
+ while (next < appAssetArtifactPaths.length) {
583
+ const artifactPath = appAssetArtifactPaths[next++]!;
584
+ const rel = toPublicRel(tenantId, artifactPath);
585
+ // An app asset whose path doesn't normalize to a served rel can't serve —
586
+ // count it missing (fail-closed), labelled by its artifact path.
587
+ if (!rel) {
588
+ missing.push(artifactPath);
589
+ continue;
590
+ }
591
+ let present = false;
592
+ try {
593
+ const obj = await bucket.get(tenantAssetKey(tenantId, versionId, rel));
594
+ // Present when the object exists and (when the store reports size) is non-empty.
595
+ present = obj != null && (obj.size == null || obj.size > 0);
596
+ } catch {
597
+ present = false;
598
+ }
599
+ if (present) verified++;
600
+ else missing.push(rel);
601
+ }
602
+ };
603
+ const width = Math.max(1, Math.min(VERIFY_PROBE_POOL, appAssetArtifactPaths.length));
604
+ await Promise.all(Array.from({ length: width }, worker));
605
+ // Stable order aids diffing/readability — pool completion order is nondeterministic.
606
+ missing.sort();
607
+ return { expected, verified, missing };
608
+ } catch (err) {
609
+ // Systemic failure (e.g. the bucket seam throwing on every call): fail closed —
610
+ // report the whole subtree as unverified rather than claiming a pass.
611
+ return {
612
+ expected,
613
+ verified: 0,
614
+ missing: [...appAssetArtifactPaths],
615
+ error: err instanceof Error ? err.message : String(err),
616
+ };
617
+ }
618
+ }