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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +8 -3
  3. package/apps/storefront/env.d.ts +54 -5
  4. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  5. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  6. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  7. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  8. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  9. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  10. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  11. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  12. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  13. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  14. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  15. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  16. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  17. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  18. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  19. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  20. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  22. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  23. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  24. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  25. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  26. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  27. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  28. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  29. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  30. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  32. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  33. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  34. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  37. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  38. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  41. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  42. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  43. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  44. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  45. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  46. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  47. package/apps/storefront/src/components/Img.astro +1 -1
  48. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  49. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  50. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  51. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  52. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  53. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  54. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  55. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  59. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  60. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  61. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  62. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  63. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  64. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  65. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  66. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  67. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  68. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  69. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  70. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  71. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  72. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  73. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  74. package/apps/storefront/src/config/resolver.ts +18 -10
  75. package/apps/storefront/src/layouts/Layout.astro +100 -20
  76. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  77. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  78. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  79. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  80. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  81. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  82. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  83. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  84. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  87. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  89. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  90. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  91. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  92. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  93. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  94. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  95. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  96. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  97. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  98. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  99. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  100. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  104. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  105. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  106. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  109. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  110. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  111. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  112. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  113. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  114. package/apps/storefront/src/lib/blog/collection.ts +114 -17
  115. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  116. package/apps/storefront/src/lib/blog/provider.ts +76 -9
  117. package/apps/storefront/src/lib/blog/types.ts +9 -9
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  119. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  120. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  121. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  122. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  123. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  124. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  126. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  127. package/apps/storefront/src/lib/content/index.ts +5 -5
  128. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  129. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  130. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  131. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  132. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  133. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  134. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  135. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  136. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  137. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  138. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  139. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  140. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  141. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  142. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  143. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  144. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  145. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  146. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  147. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  148. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  149. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  150. package/apps/storefront/src/lib/membership/model.ts +1 -2
  151. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  153. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  154. package/apps/storefront/src/lib/previewReload.ts +57 -1
  155. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  156. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  157. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  158. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  159. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  160. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  161. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  162. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  163. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  164. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  165. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  166. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  173. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  176. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  177. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  178. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  179. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  180. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  181. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  182. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  183. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  184. package/apps/storefront/src/lib/social/notify.ts +4 -4
  185. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  186. package/apps/storefront/src/lib/storyblok/provider.ts +203 -69
  187. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  188. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  189. package/apps/storefront/src/middleware/index.ts +273 -51
  190. package/apps/storefront/src/pages/[...slug].astro +9 -4
  191. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  192. package/apps/storefront/src/pages/admin/select.astro +96 -0
  193. package/apps/storefront/src/pages/admin.astro +16 -2
  194. package/apps/storefront/src/pages/api/activity.ts +8 -8
  195. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  196. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  197. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  199. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  201. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  202. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  203. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  204. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  205. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  206. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  207. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  208. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  209. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  210. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  211. package/apps/storefront/src/pages/blog/index.astro +2 -2
  212. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  213. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  214. package/apps/storefront/src/pages/index.astro +10 -5
  215. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  216. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  217. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  218. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +42 -31
  219. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +8 -3
  220. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +6 -2
  221. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +6 -2
  222. package/apps/storefront/src/pages/style-guide/index.astro +19 -5
  223. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  224. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  225. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  226. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  227. package/apps/storefront/src/styles/admin.css +27 -1
  228. package/apps/storefront/src/themes/tenants/index.ts +19 -7
  229. package/apps/storefront/tsconfig.json +1 -1
  230. package/package.json +1 -1
  231. package/packages/public-runtime/package.json +2 -1
  232. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  250. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  251. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  252. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  253. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  254. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  255. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  256. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  257. package/packages/public-runtime/src/activity/event.ts +6 -6
  258. package/packages/public-runtime/src/activity/index.ts +2 -2
  259. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  260. package/packages/public-runtime/src/candidate-index.ts +42 -8
  261. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  262. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  263. package/packages/public-runtime/src/checkout-style.ts +7 -7
  264. package/packages/public-runtime/src/checkout.ts +3 -3
  265. package/packages/public-runtime/src/chrome.ts +777 -0
  266. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  267. package/packages/public-runtime/src/csp.ts +99 -0
  268. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  269. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  270. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  271. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  272. package/packages/public-runtime/src/evidence.ts +288 -0
  273. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  274. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  275. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  276. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  277. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  278. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  279. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  280. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  281. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  282. package/packages/public-runtime/src/index.ts +16 -1
  283. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  284. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  285. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  286. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  287. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  288. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  289. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  290. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  291. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  292. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  293. package/packages/public-runtime/src/integration/csp.ts +105 -0
  294. package/packages/public-runtime/src/integration/digest.ts +43 -0
  295. package/packages/public-runtime/src/integration/environment.ts +228 -0
  296. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  297. package/packages/public-runtime/src/integration/events.ts +701 -0
  298. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  299. package/packages/public-runtime/src/integration/index.ts +90 -0
  300. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  301. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  302. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  303. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  305. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  306. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  307. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  308. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  309. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  310. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  311. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  312. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  313. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  314. package/packages/public-runtime/src/integration/observability.ts +451 -0
  315. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  316. package/packages/public-runtime/src/integration/phases.ts +182 -0
  317. package/packages/public-runtime/src/integration/plan.ts +112 -0
  318. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  319. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  320. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  321. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  322. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  323. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  324. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  325. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  326. package/packages/public-runtime/src/integration/result.ts +239 -0
  327. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  328. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  329. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  330. package/packages/public-runtime/src/integration/version.ts +92 -0
  331. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  332. package/packages/public-runtime/src/membership.ts +2 -2
  333. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  334. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  335. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  336. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  337. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  338. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  339. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  340. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  341. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  342. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  343. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  344. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  345. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  346. package/packages/public-runtime/src/static-bundle.ts +5 -5
  347. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  348. package/packages/public-runtime/src/tenant.ts +136 -9
  349. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  350. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  351. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  352. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  353. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  354. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  355. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  356. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  357. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  358. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  359. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  360. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  361. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  362. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  363. package/scripts/build/copy-tenant-assets.mjs +12 -5
  364. package/scripts/dev/ai-edit.mjs +2 -1
  365. package/scripts/dev/file-browser.mjs +2 -2
  366. package/scripts/dev/file-writer.mjs +2 -2
  367. package/scripts/dev/integration-guardrails.mjs +498 -0
  368. package/scripts/dev/safe-path.mjs +2 -2
  369. package/scripts/dev/tenant-source-root.mjs +3 -2
  370. package/scripts/tot-dev.mjs +5 -4
@@ -57,8 +57,13 @@ if (rawHtml && rawHtmlPolicy(tenant).allowed) {
57
57
  // Shared chrome: if the tenant ships a chrome.html wrapper AND this home is a
58
58
  // body-only fragment, rawHtmlResponse splices it in; a full document serves
59
59
  // verbatim (backward-compatible).
60
- const chromeWrapper = await content.getChromeHtml();
61
- return rawHtmlResponse(rawHtml, { nonce: Astro.locals.cspNonce, basePath, chromeWrapper });
60
+ const chromeWrapper = await content.getChromeHtml("/");
61
+ return rawHtmlResponse(rawHtml, {
62
+ nonce: Astro.locals.cspNonce,
63
+ basePath,
64
+ chromeWrapper,
65
+ plan: Astro.locals.integrationPlan,
66
+ });
62
67
  }
63
68
 
64
69
  const home = await content.getHome();
@@ -143,9 +148,9 @@ for (const block of marketing ? [] : home.blocks) {
143
148
  }
144
149
  }
145
150
 
146
- // Private-app widgets (D5) — home.section slot. Commerce-home only; marketing-
151
+ // Private-app widgets — home.section slot. Commerce-home only; marketing-
147
152
  // tenant homes (raw HTML, above, and the marketing-block home) are out of
148
- // scope for D5 (deferred). No dark-mode preference infra exists yet — "light"
153
+ // scope (deferred). No dark-mode preference infra exists yet — "light"
149
154
  // is the only theme hint until that lands.
150
155
  const homeSectionWidgets = marketing
151
156
  ? []
@@ -326,7 +331,7 @@ const homeSectionWidgets = marketing
326
331
  return null;
327
332
  })
328
333
  }
329
- {/* Private-app widgets (D5, home.section) — sandboxed per install. */}
334
+ {/* Private-app widgets (home.section) — sandboxed per install. */}
330
335
  {homeSectionWidgets.map((w) => (
331
336
  <Section>
332
337
  <AppWidgetFrame
@@ -34,7 +34,7 @@ export async function GET(context: APIContext): Promise<Response> {
34
34
  content.listPages(),
35
35
  tot.getFeatured(12),
36
36
  content.getHome().catch(() => null),
37
- listBlogPosts(tenant.tenant_id),
37
+ listBlogPosts(tenant.tenant_id, content),
38
38
  ]);
39
39
  // Empty synthesized collections (no active members) never make the guide —
40
40
  // an LLM shouldn't be pointed at a URL that resolves to zero products.
@@ -60,7 +60,7 @@ if (!product || product.status === "archived") {
60
60
  }
61
61
  const isDraft = product.status !== "active";
62
62
 
63
- // Private-app widgets (D5) — product.aside slot, one iframe per live install.
63
+ // Private-app widgets — product.aside slot, one iframe per live install.
64
64
  // No dark-mode preference is resolved anywhere in the storefront yet; "light"
65
65
  // is the only theme hint until that infra exists.
66
66
  const productAsideWidgets = await renderWidgetSlot(
@@ -111,7 +111,7 @@ const mf = (ns: string, key: string) =>
111
111
  product.metafields.find((m) => m.namespace === ns && m.key === key)?.value;
112
112
 
113
113
  // Reviews — REAL aggregate from the store's reviews app, mirrored to metafields.
114
- // readReviewAggregate is the single, real-or-absent reader (§F9): it parses both
114
+ // readReviewAggregate is the single, real-or-absent reader: it parses both
115
115
  // the bare and JSON-blob rating forms and the real count key, and returns {0,0}
116
116
  // unless BOTH numbers are real positives — so no fabricated rating can render.
117
117
  const { average: reviewAvg, count: reviewCount } = readReviewAggregate(product);
@@ -181,7 +181,7 @@ const available = isAvailable(product);
181
181
  // side so the island can render a signed, tamper-proof form. In `signed` mode a
182
182
  // missing secret means we render NO cart (never a tamperable form); the
183
183
  // `unsigned-demo` mode needs no secret. Drafts still get a cart (preview links).
184
- // Membership + Autopilot mechanics (F10). Member pricing renders only once a
184
+ // Membership + Autopilot mechanics. Member pricing renders only once a
185
185
  // real eligibility signal flows through resolveMemberContext (Phase 2); Phase 1
186
186
  // is anonymous. Autopilot (subscription options) is hidden when the tenant has
187
187
  // set commerce.autopilot.enabled = false (hide-at-launch).
@@ -198,7 +198,7 @@ const pricing: {
198
198
  ? { membershipRule, stacking: membershipProgram?.stacking ?? DEFAULT_STACKING_POLICY }
199
199
  : undefined;
200
200
  const autopilotEnabled = isAutopilotEnabled(tenant.commerce?.autopilot);
201
- // Server-side pricing context (F11b). The SIGNED price is the member-adjusted
201
+ // Server-side pricing context. The SIGNED price is the member-adjusted
202
202
  // effective price for THIS request's resolved eligibility — never the list price,
203
203
  // and never re-derived client-side. Phase 1 resolves anonymous, so this yields the
204
204
  // list/subscription price; a Phase-2 member request signs their member price.
@@ -366,8 +366,9 @@ const jsonLd = [productJsonLd, breadcrumbLd(crumbs, canonicalBase)];
366
366
  productTitle={product.title}
367
367
  checkoutCart={checkoutCart}
368
368
  pricing={pricing}
369
+ handoffUrl={withBase(basePath, "/api/commerce/checkout-handoff")}
369
370
  />
370
- {/* Regulated-tenant checkout ENFORCEMENT gate (F12/T5). Intercepts the
371
+ {/* Regulated-tenant checkout ENFORCEMENT gate. Intercepts the
371
372
  managed-cart handoff to clear age + ship-to compliance server-side
372
373
  before the external checkout. Only mounts when a store is wired AND
373
374
  the tenant is regulated. */}
@@ -392,7 +393,7 @@ const jsonLd = [productJsonLd, breadcrumbLd(crumbs, canonicalBase)];
392
393
  </button>
393
394
  </div>
394
395
 
395
- {/* Private-app widgets (D5, product.aside) — sandboxed per install. */}
396
+ {/* Private-app widgets (product.aside) — sandboxed per install. */}
396
397
  {productAsideWidgets.map((w) => (
397
398
  <div class="mt-7">
398
399
  <AppWidgetFrame
@@ -67,7 +67,7 @@ export async function GET(context: APIContext): Promise<Response> {
67
67
  urls.push({ loc: `${base}/${page.slug}` });
68
68
  }
69
69
 
70
- const blogPosts = await listBlogPosts(tenant.tenant_id);
70
+ const blogPosts = await listBlogPosts(tenant.tenant_id, content);
71
71
  if (blogPosts.length > 0) {
72
72
  urls.push({ loc: `${base}/blog/` });
73
73
  for (const post of blogPosts) {
@@ -85,7 +85,7 @@ export async function GET(context: APIContext): Promise<Response> {
85
85
  }
86
86
  }
87
87
 
88
- const buildPosts = await listBuildPosts(tenant.tenant_id);
88
+ const buildPosts = await listBuildPosts(tenant.tenant_id, { content });
89
89
  if (buildPosts.length > 0) {
90
90
  urls.push({ loc: `${base}/the-build/` });
91
91
  for (const post of buildPosts) {
@@ -20,6 +20,7 @@ export const prerender = false;
20
20
  // utilities + reference theme-token fallbacks so the (token-styled) components
21
21
  // render here without the app Layout. The `.sg-commerce` wrapper pins the
22
22
  // reference palette so they look coherent regardless of the marketing theme CSS.
23
+ import { tenantDirSegment } from "@tot/public-runtime";
23
24
  import "@/styles/global.css";
24
25
  import PriceDisplay from "@/components/commerce/PriceDisplay.astro";
25
26
  import RatingStars from "@/components/commerce/RatingStars.astro";
@@ -88,26 +89,27 @@ const cspNonce = Astro.locals.cspNonce;
88
89
 
89
90
  // --- Sample chrome data (preview-only; no live tenant content) ----------------
90
91
  const sampleNav: ChromeNavItem[] = [
91
- { label: "New", href: "#", flag: true, badge: "NEW" },
92
+ { id: "nav-new", label: "New", href: "#", flag: true, badge: "NEW" },
92
93
  {
94
+ id: "nav-shop",
93
95
  label: "Shop",
94
96
  href: "#",
95
97
  columns: [
96
- { title: "Category", links: [{ label: "Disposables", href: "#" }, { label: "E-Liquid", href: "#" }, { label: "Devices", href: "#" }] },
97
- { title: "Featured", links: [{ label: "Best Sellers", href: "#" }, { label: "On Sale", href: "#", badge: "%" }] },
98
- { title: "Brands", links: [{ label: "Northwind", href: "#" }, { label: "Summit Co.", href: "#" }] },
98
+ { title: "Category", links: [{ id: "nav-shop-disposables", label: "Disposables", href: "#" }, { id: "nav-shop-eliquid", label: "E-Liquid", href: "#" }, { id: "nav-shop-devices", label: "Devices", href: "#" }] },
99
+ { title: "Featured", links: [{ id: "nav-shop-bestsellers", label: "Best Sellers", href: "#" }, { id: "nav-shop-onsale", label: "On Sale", href: "#", badge: "%" }] },
100
+ { title: "Brands", links: [{ id: "nav-shop-northwind", label: "Northwind", href: "#" }, { id: "nav-shop-summit", label: "Summit Co.", href: "#" }] },
99
101
  ],
100
102
  },
101
- { label: "Coils & Parts", href: "#", children: [{ label: "Coils", href: "#" }, { label: "Pods", href: "#" }, { label: "Batteries", href: "#" }] },
102
- { label: "On Sale", href: "#" },
103
+ { id: "nav-coils", label: "Coils & Parts", href: "#", children: [{ id: "nav-coils-coils", label: "Coils", href: "#" }, { id: "nav-coils-pods", label: "Pods", href: "#" }, { id: "nav-coils-batteries", label: "Batteries", href: "#" }] },
104
+ { id: "nav-sale", label: "On Sale", href: "#" },
103
105
  ];
104
106
  const sampleSocial = [
105
107
  { label: "X", href: "#", iconPath: "M18.9 2H22l-7.1 8.1L23 22h-6.6l-5-6.6L5.7 22H2.6l7.6-8.7L1.5 2h6.8l4.5 6 5.1-6z" },
106
108
  ];
107
109
  const sampleFooterCols = [
108
- { title: "Shop", links: [{ label: "Disposables", href: "#" }, { label: "E-Liquid", href: "#" }, { label: "Devices", href: "#" }] },
109
- { title: "Support", links: [{ label: "Contact", href: "#" }, { label: "Shipping", href: "#" }, { label: "Returns", href: "#" }] },
110
- { title: "Company", links: [{ label: "About", href: "#" }, { label: "Blog", href: "#" }] },
110
+ { title: "Shop", links: [{ id: "ftr-shop-disposables", label: "Disposables", href: "#" }, { id: "ftr-shop-eliquid", label: "E-Liquid", href: "#" }, { id: "ftr-shop-devices", label: "Devices", href: "#" }] },
111
+ { title: "Support", links: [{ id: "ftr-support-contact", label: "Contact", href: "#" }, { id: "ftr-support-shipping", label: "Shipping", href: "#" }, { id: "ftr-support-returns", label: "Returns", href: "#" }] },
112
+ { title: "Company", links: [{ id: "ftr-company-about", label: "About", href: "#" }, { id: "ftr-company-blog", label: "Blog", href: "#" }] },
111
113
  ];
112
114
  // BrandGrid samples: text-wordmark tiles and logo-image tiles (Img renders
113
115
  // deterministic placeholders offline so the image code path is exercised
@@ -125,7 +127,7 @@ const sampleBrandsLogo: Brand[] = [
125
127
  { label: "Pod King", href: "#", logoSrc: "#", logoWidth: 200, logoHeight: 100 },
126
128
  { label: "Flavor Beast", href: "#", logoSrc: "#", logoWidth: 200, logoHeight: 100 },
127
129
  ];
128
- // Review/UGC samples (P3 reviews-ugc-widgets): illustrative fixture reviews
130
+ // Review/UGC samples: illustrative fixture reviews
129
131
  // ONLY, for previewing ReviewSummary/ReviewList token styling in isolation —
130
132
  // never sourced from or rendered on a live product page (see
131
133
  // docs/architecture/review-markup-policy.md; real callers pass tot.getReviews()
@@ -135,7 +137,7 @@ const sampleReviews: Review[] = [
135
137
  { author: "Priya K.", rating: 4, body: "Good overall, battery life could be a bit longer.", date: "2026-05-18", verifiedBuyer: true },
136
138
  { author: "T. Nguyen", rating: 3, title: "Decent, not amazing", body: "Does the job but nothing stands out versus similar devices.", date: "2026-04-30" },
137
139
  ];
138
- // Blog/article samples (W4): featured-image cards (Img renders deterministic
140
+ // Blog/article samples: featured-image cards (Img renders deterministic
139
141
  // placeholders offline so the image path is exercised) + one image-less card
140
142
  // (token-tinted placeholder band). Meta is illustrative example copy only.
141
143
  const sampleArticles: Article[] = [
@@ -236,7 +238,7 @@ const sampleComparisonRows: ComparisonRow[] = [
236
238
  { label: "Cancel anytime", cells: [null, true, true] },
237
239
  ];
238
240
 
239
- // --- Sample structured content (F4) — the DATA a policy page is driven by,
241
+ // --- Sample structured content — the DATA a policy page is driven by,
240
242
  // replacing a hand-authored HTML/CSS fragment. Exercises headings (TOC),
241
243
  // rich-text links, lists, a definition glossary, tone callouts, a contact
242
244
  // block, and a needs-review flag (reviewer-only). ---------------------------
@@ -293,13 +295,13 @@ const sampleProseSections: ProseSection[] = [
293
295
  },
294
296
  ];
295
297
 
296
- // Trust strip (F4, f09-guarded): mixes real claims + a real rating with
298
+ // Trust strip: mixes real claims + a real rating with
297
299
  // fabrication vectors that MUST be dropped — the guide proves real-or-absent.
298
300
  const sampleTrustStrip: TrustStripItem[] = [
299
301
  { kind: "claim", label: "Ships from USA" },
300
302
  { kind: "claim", label: "Lab-tested" },
301
303
  { kind: "rating", average: 4.9, count: 369, label: "avg rating" },
302
- { kind: "claim", label: "Based on 369 reviews" }, // fabricated → dropped by f09
304
+ { kind: "claim", label: "Based on 369 reviews" }, // fabricated → dropped
303
305
  { kind: "rating", average: 5, count: 0 }, // half-populated → dropped
304
306
  ];
305
307
 
@@ -458,10 +460,13 @@ const sampleMarketingBlocks: MarketingBlock[] = [
458
460
  // Colocated repo-root layout: from src/pages/style-guide/[tenant]/ the repo
459
461
  // root is six levels up.
460
462
  const themeModules = import.meta.glob<Record<string, any>>(
461
- "../../../../../../tenants/*/content/themes/*.json",
463
+ [
464
+ "../../../../../../tenants/*/content/themes/*.json",
465
+ "../../../../../../tenants/e2e/*/content/themes/*.json",
466
+ ],
462
467
  { eager: true, import: "default" },
463
468
  );
464
- const seg = `/tenants/${tenant}/content/themes/`;
469
+ const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
465
470
  const t = Object.entries(themeModules)
466
471
  .filter(([path]) => path.includes(seg))
467
472
  .map(([, mod]) => mod)
@@ -469,17 +474,23 @@ const t = Object.entries(themeModules)
469
474
 
470
475
  if (!t) return new Response(null, { status: 404 });
471
476
 
472
- // Has this tenant adopted the canonical shared chrome (unit F3)? If so it no
473
- // longer ships its own mkt.css (retired at integration, I1/I2/I3) — link the
477
+ // Has this tenant adopted the canonical shared chrome? If so it no
478
+ // longer ships its own mkt.css (retired at integration) — link the
474
479
  // platform /shared/commerce-chrome.css instead so the marketing-component
475
480
  // samples below still render in-brand.
476
481
  const chromeModules = import.meta.glob<Record<string, any>>(
477
- "../../../../../../tenants/*/content/chrome.json",
482
+ [
483
+ "../../../../../../tenants/*/content/chrome.json",
484
+ "../../../../../../tenants/e2e/*/content/chrome.json",
485
+ ],
478
486
  { eager: true, import: "default" },
479
487
  );
480
- const chromeSeg = `/tenants/${tenant}/content/chrome.json`;
488
+ const chromeSeg = `${tenantDirSegment(tenant ?? "")}content/chrome.json`;
481
489
  const chromeConfig = Object.entries(chromeModules).find(([path]) => path.includes(chromeSeg))?.[1];
482
- const usesSharedChrome = chromeConfig?.sharedChrome === true;
490
+ // Adoption is now "does chrome.json carry the consolidated header/footer
491
+ // shape" (lib/chrome/model.ts ChromeConfig) — the old `sharedChrome: true`
492
+ // opt-in flag is retired; a successfully-shaped config IS the adopted signal.
493
+ const usesSharedChrome = !!chromeConfig?.header && !!chromeConfig?.footer;
483
494
 
484
495
  const tk = t.tokens ?? {};
485
496
  const storyboard = t.storyboard ?? null;
@@ -492,7 +503,7 @@ const REFERENCE_COMPONENT_VARS: Record<string, string> = {
492
503
  "--color-muted": "#5c6663", "--color-primary": "#1f3a34", "--color-primary-contrast": "#f4f1ea",
493
504
  "--color-accent": "#b5612f", "--color-border": "#dcd6c9", "--color-success": "#3f6b4f",
494
505
  "--color-sale": "#9e3b2e",
495
- // F1 semantic + F6 quality-bar interaction tokens — reference (Northwind)
506
+ // Semantic + quality-bar interaction tokens — reference (Northwind)
496
507
  // values so the text/icon-token samples render concretely and re-skin when a
497
508
  // descriptor overrides them via preview.componentVars.
498
509
  "--color-warning": "#8a5717", "--color-error": "#a22014", "--color-info": "#2c5b73",
@@ -610,7 +621,7 @@ const componentVarsInline = Object.entries(componentVars)
610
621
  .sg-commerce .sg-h { color: var(--color-primary); }
611
622
  .sg-commerce .sg-block { border-top-color: var(--color-border); }
612
623
 
613
- /* Semantic text & icon token samples (v1.1 · unit F6). Each swatch reads
624
+ /* Semantic text & icon token samples (v1.1). Each swatch reads
614
625
  ONE contract token so the guide proves text/icon roles resolve + re-skin
615
626
  from tokens alone — never a hard-coded color. */
616
627
  .sg-tok-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
@@ -952,7 +963,7 @@ const componentVarsInline = Object.entries(componentVars)
952
963
  </div>
953
964
  </section>
954
965
 
955
- {/* ── Semantic text & icon tokens (v1.1 · unit F6) ────────────────────
966
+ {/* ── Semantic text & icon tokens (v1.1) ──────────────────────────────
956
967
  The quality-bar coverage sample: every text role, icon tint, and
957
968
  interaction state renders from a NAMED contract token — nothing here
958
969
  hard-codes a color or borrows `accent`. Swap the theme (or a
@@ -1138,7 +1149,7 @@ const componentVarsInline = Object.entries(componentVars)
1138
1149
  <section class="sg-block">
1139
1150
  <p class="sg-h">Chrome — UtilityNav</p>
1140
1151
  <UtilityNav
1141
- links={[{ label: "News", href: "#" }, { label: "About", href: "#" }, { label: "Contact", href: "#" }]}
1152
+ links={[{ id: "util-news", label: "News", href: "#" }, { id: "util-about", label: "About", href: "#" }, { id: "util-contact", label: "Contact", href: "#" }]}
1142
1153
  locale="English"
1143
1154
  currency="United States (USD $)"
1144
1155
  social={sampleSocial}
@@ -1283,8 +1294,8 @@ const componentVarsInline = Object.entries(componentVars)
1283
1294
  — no pricing/checkout/subscription mechanics. Figures are ILLUSTRATIVE
1284
1295
  by default: unless a tier asserts real:true the widget renders a
1285
1296
  first-class "Illustrative figures" affordance (badge + "e.g." + note)
1286
- so an example number is never shown as a live offer (decision
1287
- compliance-widgets-platform-owned). Fed sample tiers here; a tenant
1297
+ so an example number is never shown as a live offer. Fed sample
1298
+ tiers here; a tenant
1288
1299
  supplies its own on a membership page. */}
1289
1300
  <section class="sg-block">
1290
1301
  <p class="sg-h">Membership tiers — <code>components/membership/</code></p>
@@ -1307,13 +1318,13 @@ const componentVarsInline = Object.entries(componentVars)
1307
1318
  />
1308
1319
  </section>
1309
1320
 
1310
- {/* ── Structured content widgets (components/content/, spec §F4) ───────
1321
+ {/* ── Structured content widgets (components/content/) ───────
1311
1322
  The DATA-driven promotion of repeated raw-page patterns. A tenant
1312
1323
  authors a policy/about/compliance page as structured `sections`
1313
1324
  (JSON) instead of copying an HTML/CSS fragment; these components
1314
1325
  render it with the a11y (headings, <dl>, <address>, roles, tap
1315
- targets) baked in and every color from f06 tokens. The trust strip is
1316
- f09-guarded — real-or-absent, fabricated proof dropped. */}
1326
+ targets) baked in and every color from tokens. The trust strip is
1327
+ guarded — real-or-absent, fabricated proof dropped. */}
1317
1328
  <section class="sg-block">
1318
1329
  <p class="sg-h">Structured content — <code>components/content/</code></p>
1319
1330
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Data-driven framework widgets (F4): a policy page rendered from structured <code>sections</code> with no HTML/CSS to copy. Accessibility lives in the components; tenant content is pure data.</p>
@@ -1374,7 +1385,7 @@ const componentVarsInline = Object.entries(componentVars)
1374
1385
  {/* ── Interactive islands (components/islands/) ─────────────────────
1375
1386
  Client-hydrated Preact islands. Each is tenant-agnostic and reads
1376
1387
  only fixture/localStorage data here — no live catalog, cart, or ToT
1377
- calls (unit style-guide-full-coverage). */}
1388
+ calls. */}
1378
1389
  <section class="sg-block">
1379
1390
  <p class="sg-h">Islands — <code>components/islands/</code></p>
1380
1391
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Client-hydrated Preact islands exercised with fixture data — no live catalog, cart, or ToT calls.</p>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  /**
3
3
  * Preview/dev-only SHARED RAW-CHROME STYLE GUIDE —
4
- * `/_style-guide/<tenant>/chrome/<theme-id>/` (unit F4).
4
+ * `/_style-guide/<tenant>/chrome/<theme-id>/`.
5
5
  *
6
6
  * Renders the platform-shipped raw chrome set — the `/shared/commerce-chrome.css`
7
7
  * class vocabulary (announcement + utility bar, header + search + actions, primary
@@ -30,11 +30,16 @@ const cspNonce = Astro.locals.cspNonce;
30
30
 
31
31
  // Tenant-local theme descriptors. From src/pages/style-guide/[tenant]/chrome/ the
32
32
  // repo root is seven levels up.
33
+ import { tenantDirSegment } from "@tot/public-runtime";
34
+
33
35
  const themeModules = import.meta.glob<Record<string, any>>(
34
- "../../../../../../../tenants/*/content/themes/*.json",
36
+ [
37
+ "../../../../../../../tenants/*/content/themes/*.json",
38
+ "../../../../../../../tenants/e2e/*/content/themes/*.json",
39
+ ],
35
40
  { eager: true, import: "default" },
36
41
  );
37
- const seg = `/tenants/${tenant}/content/themes/`;
42
+ const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
38
43
  const t = Object.entries(themeModules)
39
44
  .filter(([path]) => path.includes(seg))
40
45
  .map(([, mod]) => mod)
@@ -17,6 +17,7 @@
17
17
  */
18
18
  export const prerender = false;
19
19
 
20
+ import { tenantDirSegment } from "@tot/public-runtime";
20
21
  import "@/styles/global.css";
21
22
  import type { MarkdownInstance } from "astro";
22
23
  import StyleGuideNav from "@/components/style-guide/StyleGuideNav.astro";
@@ -27,10 +28,13 @@ const cspNonce = Astro.locals.cspNonce;
27
28
  // Tenant-local theme descriptors. From src/pages/style-guide/[tenant]/guide/ the
28
29
  // repo root is seven levels up (same depth as the chrome guide).
29
30
  const themeModules = import.meta.glob<Record<string, any>>(
30
- "../../../../../../../tenants/*/content/themes/*.json",
31
+ [
32
+ "../../../../../../../tenants/*/content/themes/*.json",
33
+ "../../../../../../../tenants/e2e/*/content/themes/*.json",
34
+ ],
31
35
  { eager: true, import: "default" },
32
36
  );
33
- const seg = `/tenants/${tenant}/content/themes/`;
37
+ const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
34
38
  const t = Object.entries(themeModules)
35
39
  .filter(([path]) => path.includes(seg))
36
40
  .map(([, mod]) => mod)
@@ -16,6 +16,7 @@
16
16
  */
17
17
  export const prerender = false;
18
18
 
19
+ import { tenantDirSegment } from "@tot/public-runtime";
19
20
  import StyleGuideNav from "@/components/style-guide/StyleGuideNav.astro";
20
21
 
21
22
  const { tenant } = Astro.params;
@@ -24,10 +25,13 @@ const cspNonce = Astro.locals.cspNonce;
24
25
  // Tenant-local theme descriptors. Colocated repo-root layout: from
25
26
  // src/pages/style-guide/[tenant]/ the repo root is six levels up.
26
27
  const themeModules = import.meta.glob<Record<string, any>>(
27
- "../../../../../../tenants/*/content/themes/*.json",
28
+ [
29
+ "../../../../../../tenants/*/content/themes/*.json",
30
+ "../../../../../../tenants/e2e/*/content/themes/*.json",
31
+ ],
28
32
  { eager: true, import: "default" },
29
33
  );
30
- const seg = `/tenants/${tenant}/content/themes/`;
34
+ const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
31
35
  const themes = Object.entries(themeModules)
32
36
  .filter(([path]) => path.includes(seg))
33
37
  .map(([, mod]) => mod);
@@ -2,8 +2,11 @@
2
2
  /**
3
3
  * Preview/dev-only STYLE-GUIDE ROOT INDEX — `/_style-guide/`.
4
4
  *
5
- * Lists EVERY tenant and each of its theme guides, so all clients' guides are
6
- * discoverable from one place. Review surface only: noindex, never in the
5
+ * Lists every REAL tenant and each of its theme guides, so all clients' guides
6
+ * are discoverable from one place. Platform test tenants (the `.e2e.test` class,
7
+ * colocated under tenants/e2e/) are NOT listed — a generalized listing shows
8
+ * clients, not fixtures. `?includeTestTenants=1` opts them back in for someone
9
+ * who came here to look at one. Review surface only: noindex, never in the
7
10
  * sitemap, never part of production marketing publishing. Tenant-agnostic —
8
11
  * theme data is tenant-local (tenants/<tenant>/content/themes/*.json).
9
12
  *
@@ -17,25 +20,33 @@
17
20
  */
18
21
  export const prerender = false;
19
22
 
23
+ import { listedTenantIds } from "@tot/public-runtime";
24
+
20
25
  const cspNonce = Astro.locals.cspNonce;
26
+ const includeTestTenants = Astro.url.searchParams.get("includeTestTenants") === "1";
21
27
 
22
28
  // All tenant-local theme descriptors, grouped by tenant. Colocated repo-root
23
29
  // layout: from src/pages/style-guide/ the repo root is five levels up.
24
30
  const themeModules = import.meta.glob<Record<string, any>>(
25
- "../../../../../tenants/*/content/themes/*.json",
31
+ [
32
+ "../../../../../tenants/*/content/themes/*.json",
33
+ "../../../../../tenants/e2e/*/content/themes/*.json",
34
+ ],
26
35
  { eager: true, import: "default" },
27
36
  );
28
37
 
29
38
  const ROLE_ORDER: Record<string, number> = { current: 0, candidate: 1, legacy: 2 };
30
39
  const byTenant = new Map<string, any[]>();
31
40
  for (const [path, mod] of Object.entries(themeModules)) {
32
- const m = /\/tenants\/([^/]+)\/content\/themes\//.exec(path);
41
+ const m = /\/tenants\/(?:e2e\/)?([^/]+)\/content\/themes\//.exec(path);
33
42
  const tenant = m?.[1];
34
43
  if (!tenant) continue;
35
44
  if (!byTenant.has(tenant)) byTenant.set(tenant, []);
36
45
  byTenant.get(tenant)!.push(mod);
37
46
  }
47
+ const listedIds = new Set(listedTenantIds([...byTenant.keys()], { includeTestTenants }));
38
48
  const tenants = [...byTenant.entries()]
49
+ .filter(([tenant]) => listedIds.has(tenant))
39
50
  .map(([tenant, themes]) => ({
40
51
  tenant,
41
52
  themes: themes.sort((a, b) => (ROLE_ORDER[a.role] ?? 9) - (ROLE_ORDER[b.role] ?? 9)),
@@ -77,7 +88,10 @@ const tenants = [...byTenant.entries()]
77
88
  <div class="wrap">
78
89
  <span class="flag">Preview · dev only · noindex</span>
79
90
  <h1>Client style guides</h1>
80
- <p class="sub">Every tenant and its theme guides. Open a guide to preview the standard components and page sections re-skinned under that theme. Nothing here re-skins a live site.</p>
91
+ <p class="sub">Every client tenant and its theme guides. Open a guide to preview the standard components and page sections re-skinned under that theme. Nothing here re-skins a live site.</p>
92
+ <p class="sub">{includeTestTenants
93
+ ? <span>Showing platform test tenants too — <a href="/_style-guide/">hide them</a>.</span>
94
+ : <span>Platform test tenants are hidden — <a href="/_style-guide/?includeTestTenants=1">show them</a>.</span>}</p>
81
95
 
82
96
  {tenants.map(({ tenant, themes }) => (
83
97
  <section class="tenant">
@@ -2,7 +2,7 @@
2
2
  * GET /tenants/<id>/<path> — serve a tenant's versioned static asset from R2.
3
3
  *
4
4
  * The version-scoped replacement for the build-time `copy-tenant-assets.mjs`
5
- * baking (decision `assets-r2-versioned`). Resolves the tenant's active
5
+ * baking. Resolves the tenant's active
6
6
  * channel→snapshot versionId from the `cust:assets-pointer:<id>` KV record and
7
7
  * streams `tenants/<id>/<versionId>/<rel>` from the TENANT_ASSETS R2 bucket with
8
8
  * the right content-type + a revalidating cache header + a versionId ETag.
@@ -17,12 +17,36 @@
17
17
  import type { APIContext } from "astro";
18
18
  import {
19
19
  serveTenantAsset,
20
+ verifyPreviewToken,
20
21
  type R2BucketLike,
21
22
  } from "@tot/public-runtime";
22
- import { readR2, readKv } from "@/lib/env";
23
+ import { readR2, readKv, readEnv } from "@/lib/env";
23
24
 
24
25
  export const prerender = false;
25
26
 
27
+ /** A versionId is a content sha — restrict to a safe R2 key segment (no traversal). */
28
+ const SAFE_VERSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
29
+
30
+ /**
31
+ * Resolve a CANDIDATE-preview version override for this asset request, or null.
32
+ * A candidate/revision page sets `tot_preview_version` (its versionId) + the
33
+ * `tot_preview` capability token; when the token verifies for THIS tenant, we serve
34
+ * that exact snapshot (`tenants/<id>/<versionId>/…`) instead of the channel pointer,
35
+ * so a candidate's NEW assets — unreachable via the never-flipped channel — resolve.
36
+ * Token-gated + tenant-matched so it can never serve another tenant's snapshot.
37
+ */
38
+ async function resolvePreviewVersion(context: APIContext, urlTenant: string): Promise<string | null> {
39
+ const versionId = context.cookies.get("tot_preview_version")?.value ?? null;
40
+ const token = context.cookies.get("tot_preview")?.value ?? null;
41
+ if (!versionId || !token || !urlTenant) return null;
42
+ if (!SAFE_VERSION_ID.test(versionId) || versionId.includes("..")) return null;
43
+ const secret = (await readEnv("CUSTOMIZATION_PREVIEW_SECRET")) ?? null;
44
+ if (!secret) return null;
45
+ const verified = await verifyPreviewToken(secret, token, Math.floor(Date.now() / 1000));
46
+ if (!verified.ok || verified.claims.tenantId !== urlTenant) return null;
47
+ return versionId;
48
+ }
49
+
26
50
  export async function GET(context: APIContext): Promise<Response> {
27
51
  const [bucket, kv] = await Promise.all([
28
52
  readR2("TENANT_ASSETS"),
@@ -36,17 +60,29 @@ export async function GET(context: APIContext): Promise<Response> {
36
60
  });
37
61
  }
38
62
 
63
+ const previewVersionId = await resolvePreviewVersion(context, context.params.id ?? "");
64
+
39
65
  const res = await serveTenantAsset(
40
66
  {
41
67
  // Runtime binding structurally satisfies the shared-runtime R2 seam.
42
68
  bucket: bucket as unknown as R2BucketLike,
43
69
  kvGet: (key) => kv.get(key),
44
70
  channel: context.locals.customizationEnv ?? "live",
71
+ ...(previewVersionId ? { versionId: previewVersionId } : {}),
45
72
  ifNoneMatch: context.request.headers.get("if-none-match"),
46
73
  },
47
74
  new URL(context.request.url).pathname,
48
75
  );
49
76
 
77
+ // A candidate-preview asset must NEVER be edge/CDN-cached under its tokenless URL
78
+ // (that would poison the public asset with candidate bytes, and Vary doesn't cover
79
+ // the auth cookie). Force no-store for the preview-version path; the normal
80
+ // channel-served asset keeps its revalidating cache header.
81
+ if (res && previewVersionId) {
82
+ res.headers.set("cache-control", "no-store");
83
+ res.headers.set("x-robots-tag", "noindex, nofollow");
84
+ }
85
+
50
86
  return (
51
87
  res ??
52
88
  new Response("Not found", {
@@ -11,11 +11,11 @@ import { getTranslations } from "@/lib/blog/collection.js";
11
11
  import { buildAlternates } from "@/lib/seo/alternates.js";
12
12
  import { breadcrumbLd } from "@/lib/jsonld.js";
13
13
 
14
- const { tenant, canonicalBase, basePath } = Astro.locals;
14
+ const { tenant, canonicalBase, basePath, content } = Astro.locals;
15
15
  const slug = Astro.params.slug ?? "";
16
- const post = await getBuildPost(tenant.tenant_id, slug);
16
+ const post = await getBuildPost(tenant.tenant_id, slug, { content });
17
17
  if (!post) {
18
- const redirect = await resolveBuildRedirect(tenant.tenant_id, slug);
18
+ const redirect = await resolveBuildRedirect(tenant.tenant_id, slug, content);
19
19
  if (redirect) return Astro.redirect(`${basePath}/the-build/${redirect}/`, 301);
20
20
  return new Response(null, { status: 404 });
21
21
  }
@@ -45,7 +45,7 @@ const buildHref = `${basePath}/the-build/`;
45
45
  // the live-visible variant set (self + siblings); `buildAlternates` yields [] for
46
46
  // a singleton, so a non-translated post gets no alternates and no `lang` override
47
47
  // (byte-identical to today).
48
- const translations = await getTranslations(tenant.tenant_id, THE_BUILD.id, post.slug);
48
+ const translations = await getTranslations(tenant.tenant_id, THE_BUILD.id, post.slug, { content });
49
49
  const alternates = buildAlternates(translations, (s) =>
50
50
  new URL(`/the-build/${s}/`, canonicalBase + "/").toString(),
51
51
  );
@@ -8,8 +8,8 @@ import Layout from "@/layouts/Layout.astro";
8
8
  import PostAccessBadge from "@/components/blog/PostAccessBadge.astro";
9
9
  import { listBuildPosts } from "@/lib/the-build/provider.js";
10
10
 
11
- const { tenant, basePath, canonicalBase } = Astro.locals;
12
- const posts = await listBuildPosts(tenant.tenant_id);
11
+ const { tenant, basePath, canonicalBase, content } = Astro.locals;
12
+ const posts = await listBuildPosts(tenant.tenant_id, { content });
13
13
  if (posts.length === 0) return new Response(null, { status: 404 });
14
14
 
15
15
  const href = (path: string) => `${basePath}${path}` || "/";
@@ -11,8 +11,8 @@ import { listBuildPosts, THE_BUILD } from "@/lib/the-build/provider.js";
11
11
  import { renderRssFeed } from "@/lib/blog/rss.js";
12
12
 
13
13
  export async function GET(context: APIContext): Promise<Response> {
14
- const { tenant, canonicalBase } = context.locals;
15
- const posts = await listBuildPosts(tenant.tenant_id);
14
+ const { tenant, canonicalBase, content } = context.locals;
15
+ const posts = await listBuildPosts(tenant.tenant_id, { content });
16
16
  if (posts.length === 0) return new Response(null, { status: 404 });
17
17
 
18
18
  const body = renderRssFeed({