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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/env.d.ts +54 -5
  3. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  4. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  5. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  6. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  7. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  8. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  9. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  10. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  11. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  12. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  13. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  14. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  15. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  16. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  17. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  18. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  19. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  20. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  22. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  23. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  24. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  25. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  26. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  27. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  28. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  29. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  30. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  32. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  33. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  34. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  37. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  38. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  41. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  42. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  43. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  44. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  45. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  46. package/apps/storefront/src/components/Img.astro +1 -1
  47. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  48. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  49. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  50. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  51. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  52. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  53. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  54. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  55. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  56. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  57. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  59. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  60. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  61. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  62. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  63. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  64. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  65. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  66. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  67. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  68. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  69. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  70. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  71. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  72. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  73. package/apps/storefront/src/config/resolver.ts +4 -4
  74. package/apps/storefront/src/layouts/Layout.astro +100 -20
  75. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  76. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  77. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  78. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  79. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  80. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  81. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  82. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  83. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  84. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  87. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  89. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  90. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  91. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  92. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  93. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  94. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  95. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  96. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  97. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  98. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  99. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  100. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  104. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  105. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  106. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  109. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  110. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  111. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  112. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  113. package/apps/storefront/src/lib/blog/collection.ts +97 -10
  114. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  115. package/apps/storefront/src/lib/blog/provider.ts +57 -3
  116. package/apps/storefront/src/lib/blog/types.ts +9 -9
  117. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  119. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  120. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  121. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  122. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  123. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  124. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  126. package/apps/storefront/src/lib/content/index.ts +5 -5
  127. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  128. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  129. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  130. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  131. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  132. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  133. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  134. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  135. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  136. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  137. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  138. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  139. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  140. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  141. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  142. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  143. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  144. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  145. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  146. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  147. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  148. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  149. package/apps/storefront/src/lib/membership/model.ts +1 -2
  150. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  151. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  153. package/apps/storefront/src/lib/previewReload.ts +57 -1
  154. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  155. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  156. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  157. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  158. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  159. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  160. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  161. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  162. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  163. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  164. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  165. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  166. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  173. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  176. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  177. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  178. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  179. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  180. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  181. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  182. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  183. package/apps/storefront/src/lib/social/notify.ts +4 -4
  184. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  185. package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
  186. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  187. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  188. package/apps/storefront/src/middleware/index.ts +273 -51
  189. package/apps/storefront/src/pages/[...slug].astro +9 -4
  190. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  191. package/apps/storefront/src/pages/admin/select.astro +96 -0
  192. package/apps/storefront/src/pages/admin.astro +16 -2
  193. package/apps/storefront/src/pages/api/activity.ts +8 -8
  194. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  195. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  196. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  197. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  199. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  201. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  202. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  203. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  204. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  205. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  206. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  207. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  208. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  209. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  210. package/apps/storefront/src/pages/blog/index.astro +2 -2
  211. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  212. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  213. package/apps/storefront/src/pages/index.astro +10 -5
  214. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  215. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  216. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  217. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
  218. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
  219. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  220. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  221. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  222. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  223. package/apps/storefront/src/styles/admin.css +27 -1
  224. package/apps/storefront/tsconfig.json +1 -1
  225. package/package.json +1 -1
  226. package/packages/public-runtime/package.json +2 -1
  227. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  228. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  229. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  230. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  231. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  232. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  250. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  251. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  252. package/packages/public-runtime/src/activity/event.ts +6 -6
  253. package/packages/public-runtime/src/activity/index.ts +2 -2
  254. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  255. package/packages/public-runtime/src/candidate-index.ts +42 -8
  256. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  257. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  258. package/packages/public-runtime/src/checkout-style.ts +7 -7
  259. package/packages/public-runtime/src/checkout.ts +3 -3
  260. package/packages/public-runtime/src/chrome.ts +777 -0
  261. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  262. package/packages/public-runtime/src/csp.ts +99 -0
  263. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  264. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  265. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  266. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  267. package/packages/public-runtime/src/evidence.ts +288 -0
  268. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  269. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  270. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  271. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  272. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  273. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  274. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  275. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  276. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  277. package/packages/public-runtime/src/index.ts +16 -1
  278. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  279. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  280. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  281. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  282. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  283. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  284. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  285. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  286. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  287. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  288. package/packages/public-runtime/src/integration/csp.ts +105 -0
  289. package/packages/public-runtime/src/integration/digest.ts +43 -0
  290. package/packages/public-runtime/src/integration/environment.ts +228 -0
  291. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  292. package/packages/public-runtime/src/integration/events.ts +701 -0
  293. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  294. package/packages/public-runtime/src/integration/index.ts +90 -0
  295. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  296. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  297. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  298. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  299. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  300. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  301. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  302. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  303. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  305. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  306. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  307. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  308. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  309. package/packages/public-runtime/src/integration/observability.ts +451 -0
  310. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  311. package/packages/public-runtime/src/integration/phases.ts +182 -0
  312. package/packages/public-runtime/src/integration/plan.ts +112 -0
  313. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  314. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  315. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  316. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  317. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  318. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  319. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  320. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  321. package/packages/public-runtime/src/integration/result.ts +239 -0
  322. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  323. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  324. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  325. package/packages/public-runtime/src/integration/version.ts +92 -0
  326. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  327. package/packages/public-runtime/src/membership.ts +2 -2
  328. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  329. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  330. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  331. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  332. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  333. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  334. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  335. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  336. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  337. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  338. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  339. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  340. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  341. package/packages/public-runtime/src/static-bundle.ts +5 -5
  342. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  343. package/packages/public-runtime/src/tenant.ts +45 -9
  344. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  345. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  346. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  347. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  348. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  349. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  350. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  351. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  352. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  353. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  354. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  355. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  356. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  357. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  358. package/scripts/dev/file-browser.mjs +2 -2
  359. package/scripts/dev/file-writer.mjs +2 -2
  360. package/scripts/dev/integration-guardrails.mjs +498 -0
  361. package/scripts/dev/safe-path.mjs +2 -2
  362. package/scripts/dev/tenant-source-root.mjs +1 -1
  363. package/scripts/tot-dev.mjs +2 -2
@@ -15,6 +15,11 @@
15
15
  * injection is a deliberate string replace — robust for first-party documents, not a
16
16
  * sanitizer. Untrusted vendor HTML must be sandboxed/sanitized instead.
17
17
  */
18
+ import {
19
+ renderIntegrationPhases,
20
+ type EffectivePlan,
21
+ } from "@tot/public-runtime";
22
+
18
23
  export { rawHtmlPolicy } from "@tot/public-runtime";
19
24
 
20
25
  /**
@@ -64,6 +69,50 @@ export function wrapFragmentInChrome(fragment: string, chromeWrapper: string): s
64
69
  return out;
65
70
  }
66
71
 
72
+ /**
73
+ * Insert `snippet` at the very start of the document's `<head>` (or, absent a
74
+ * head, right after `<html>`, else at the document start) — so the renderer-owned
75
+ * head phases precede any tenant/app `<script>`/`<style>` in the served document.
76
+ * A body-only fragment with no wrapper has neither tag, so it is prepended.
77
+ */
78
+ function injectHeadStart(html: string, snippet: string): string {
79
+ // `(?:\s[^>]*)?>` matches `<head>` / `<head lang=…>` but NOT `<header>`.
80
+ const head = /<head(?:\s[^>]*)?>/i.exec(html);
81
+ if (head) {
82
+ const at = head.index + head[0].length;
83
+ return html.slice(0, at) + snippet + html.slice(at);
84
+ }
85
+ const htmlTag = /<html(?:\s[^>]*)?>/i.exec(html);
86
+ if (htmlTag) {
87
+ const at = htmlTag.index + htmlTag[0].length;
88
+ return html.slice(0, at) + snippet + html.slice(at);
89
+ }
90
+ return snippet + html;
91
+ }
92
+
93
+ /**
94
+ * Insert `snippet` at the very start of `<body>` (the body.start boundary), or —
95
+ * for a body-only fragment with no `<body>` — at the document start.
96
+ */
97
+ function injectBodyStart(html: string, snippet: string): string {
98
+ const body = /<body(?:\s[^>]*)?>/i.exec(html);
99
+ if (body) {
100
+ const at = body.index + body[0].length;
101
+ return html.slice(0, at) + snippet + html.slice(at);
102
+ }
103
+ return snippet + html;
104
+ }
105
+
106
+ /**
107
+ * Insert `snippet` immediately before `</body>` (the document.deferred boundary),
108
+ * or — for a body-only fragment with no `</body>` — at the document end.
109
+ */
110
+ function injectBodyEnd(html: string, snippet: string): string {
111
+ const at = html.toLowerCase().lastIndexOf("</body>");
112
+ if (at >= 0) return html.slice(0, at) + snippet + html.slice(at);
113
+ return html + snippet;
114
+ }
115
+
67
116
  /** Stamp `nonce` onto every inline `<style>`/`<script>` so a strict CSP admits them. */
68
117
  export function nonceInlineTags(html: string, nonce: string): string {
69
118
  return html
@@ -79,7 +128,7 @@ export function nonceInlineTags(html: string, nonce: string): string {
79
128
  * must NOT get the tenant prefix:
80
129
  * - `/tenants/…` — per-tenant static assets (logo, favicon, theme/mkt css);
81
130
  * - `/shared/…` — platform-shipped shared assets (e.g. the shared commerce-chrome
82
- * stylesheet, unit F2) that every tenant links.
131
+ * stylesheet) that every tenant links.
83
132
  * Asset `src="…"` isn't matched at all (href-only).
84
133
  */
85
134
  export function prefixTenantLinks(html: string, basePath: string): string {
@@ -100,7 +149,19 @@ export function prefixTenantLinks(html: string, basePath: string): string {
100
149
  */
101
150
  export function rawHtmlResponse(
102
151
  html: string,
103
- opts: { nonce: string; basePath: string; chromeWrapper?: string | null },
152
+ opts: {
153
+ nonce: string;
154
+ basePath: string;
155
+ chromeWrapper?: string | null;
156
+ /**
157
+ * The resolved effective plan for this request — the raw-mount half of the
158
+ * "one engine, two mounts" contract (Layout.astro is the other). The SAME
159
+ * render engine emits the renderer-owned phases into this document, so its
160
+ * phase markup is byte-identical to Layout's. Required: no render path may
161
+ * silently omit it. Nonces are stamped per response, never serialized (ADR 0018).
162
+ */
163
+ plan: EffectivePlan;
164
+ },
104
165
  ): Response {
105
166
  let composed =
106
167
  isFullDocument(html) || !opts.chromeWrapper
@@ -130,7 +191,17 @@ export function rawHtmlResponse(
130
191
  ? composed.replace("</body>", `${widget}</body>`)
131
192
  : `${composed}${widget}`;
132
193
  }
133
- const doc = prefixTenantLinks(nonceInlineTags(composed, opts.nonce), opts.basePath);
194
+ // Stamp the page nonce onto the TENANT's inline tags first, then splice the render
195
+ // engine's phase markup — which already carries the nonce — so the engine bytes are
196
+ // never double-stamped and match Layout.astro's mount exactly. `opts.nonce` is the
197
+ // per-response nonce placeholder; the middleware substitutes a fresh nonce at serve
198
+ // time (ADR 0018), so the same engine output feeds both mounts identically.
199
+ composed = nonceInlineTags(composed, opts.nonce);
200
+ const phases = renderIntegrationPhases(opts.plan, opts.nonce);
201
+ composed = injectHeadStart(composed, phases.head);
202
+ composed = injectBodyStart(composed, phases.bodyStart);
203
+ composed = injectBodyEnd(composed, phases.bodyDeferred);
204
+ const doc = prefixTenantLinks(composed, opts.basePath);
134
205
  return new Response(doc, {
135
206
  headers: { "content-type": "text/html; charset=utf-8" },
136
207
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * hreflang alternates for a translated article (I2). Two pure helpers so the
2
+ * hreflang alternates for a translated article. Two pure helpers so the
3
3
  * article route and <Seo> stay thin and the "singleton emits nothing" guard is
4
4
  * unit-testable without an Astro render harness.
5
5
  */
@@ -10,13 +10,13 @@ function logDiag(fields: Record<string, unknown>): void {
10
10
  }
11
11
 
12
12
  /**
13
- * The S3/S4 SEAM — the single call the go-live hook makes when a blog post
13
+ * The seam — the single call the go-live hook makes when a blog post
14
14
  * transitions INTO a live-visible state (see {@link notifyPostsWentLive}). This
15
- * unit (S1) ships the HOOK + the at-most-once idempotency key, NOT the network
15
+ * ships the HOOK + the at-most-once idempotency key, NOT the network
16
16
  * last-mile: v1 is a no-op announcement (logs only).
17
17
  *
18
- * S3 (the `SocialPublisher` interface + X adapter) and S4 (per-`(tenant,network)`
19
- * config + encrypted token store) replace THIS body to compose a per-network
18
+ * A `SocialPublisher` interface + X adapter, and per-`(tenant,network)`
19
+ * config + encrypted token store, replace THIS body to compose a per-network
20
20
  * payload — title, canonical URL, excerpt, featured image, `categories[0]` →
21
21
  * hashtags — and POST it to each configured network's publish API. The signature
22
22
  * is deliberately per-POST; per-network fan-out and the per-network idempotency
@@ -64,7 +64,7 @@ export interface FeaturedProductsBlock {
64
64
 
65
65
  /**
66
66
  * Membership "band" on the commerce home — the Guest / Member / Autopilot tier
67
- * comparison, driven by the platform <TierCards> (F4) widget from tenant data.
67
+ * comparison, driven by the platform <TierCards> widget from tenant data.
68
68
  * The widget owns the illustrative-figures affordance (a tier is an EXAMPLE
69
69
  * unless it asserts `real: true`), so a tenant surfaces its membership model on
70
70
  * the home page without copying tier-card HTML. The block carries the same
@@ -271,11 +271,11 @@ export interface EditorialPage {
271
271
  /**
272
272
  * Sanitized HTML body (legacy / raw path). Optional: a page authored with
273
273
  * structured {@link ProseSection}s omits it and drives the body from `sections`
274
- * instead (spec §F4). The renderer prefers `sections` when present.
274
+ * instead. The renderer prefers `sections` when present.
275
275
  */
276
276
  body_html?: string;
277
277
  /**
278
- * Structured policy/prose body (spec §F4) — headings, paragraphs, lists,
278
+ * Structured policy/prose body — headings, paragraphs, lists,
279
279
  * callouts, a contact block, needs-review flags. When present, the page route
280
280
  * renders <ProseSections> from this instead of `set:html(body_html)`, so a
281
281
  * tenant drives a full policy page from JSON with no HTML/CSS to copy.
@@ -290,33 +290,7 @@ export interface EditorialPage {
290
290
  isPolicy?: boolean;
291
291
  }
292
292
 
293
- export interface SiteNavItem {
294
- label: string;
295
- href: string;
296
- /** Mega-menu children, e.g. collection links grouped under a category. */
297
- children?: { label: string; href: string }[];
298
- }
299
-
300
- export interface SiteChrome {
301
- nav: SiteNavItem[];
302
- /**
303
- * Header conversion buttons (marketing tenants). Rendered in the header's
304
- * right cluster in place of the commerce search/saved controls. Commerce
305
- * tenants omit this and keep the search + saved affordances.
306
- */
307
- headerCtas?: MarketingCta[];
308
- footer: {
309
- columns: { title: string; links: { label: string; href: string }[] }[];
310
- social: { label: string; href: string }[];
311
- tagline?: string;
312
- /** Bottom-bar legal/compliance line (marketing tenants). Plain text. */
313
- legal?: string;
314
- };
315
- announcement?: string;
316
- /** Default legal/safety disclaimer HTML (fallback for unknown categories). */
317
- disclaimer?: string;
318
- /** Per-product-category disclaimer HTML, keyed by product_type (or
319
- * compliance.product_category) — the source store renders a different legal
320
- * block per type (sub-ohm on salts, battery on devices). */
321
- disclaimers?: Record<string, string>;
322
- }
293
+ // Site chrome (header/footer) is the ONE consolidated schema in
294
+ // lib/chrome/model.ts (`ChromeConfig`) — see that file's header comment for
295
+ // why the old `SiteChrome`/`SiteNavItem` pair that used to live here was
296
+ // retired.
@@ -17,12 +17,24 @@
17
17
  import type {
18
18
  EditorialPage,
19
19
  HomeContent,
20
- SiteChrome,
21
20
  } from "./content-model.js";
21
+ import type { ChromeConfig } from "../chrome/model.js";
22
22
  import type { CustomizationEnv, KvGet, TenantConfig } from "@tot/public-runtime";
23
- import { contentArtifactPath, readPublishedArtifact } from "@tot/public-runtime";
23
+ import {
24
+ contentArtifactPath,
25
+ listPublishedArtifactPaths,
26
+ readPublishedArtifact,
27
+ } from "@tot/public-runtime";
24
28
  import { renderRawChrome } from "../rawChrome.js";
25
29
  import { parseChromeConfig } from "../chrome/parseConfig.js";
30
+ import { parseChromeAssignments } from "../chrome/model.js";
31
+
32
+ /** Safe, always-valid fallback when neither the tenant's own chrome.json nor
33
+ * the default scope's validates — the storefront must always render. */
34
+ const EMPTY_CHROME_CONFIG: ChromeConfig = {
35
+ header: { variant: "primary", brand: { label: "" }, nav: [] },
36
+ footer: { variant: "default", columns: [] },
37
+ };
26
38
 
27
39
  export interface ContentProvider {
28
40
  getHome(): Promise<HomeContent>;
@@ -48,11 +60,21 @@ export interface ContentProvider {
48
60
  * spliced in (see lib/rawMarketingHtml.ts). Lets raw pages stop duplicating
49
61
  * chrome: chrome lives once here, pages become fragments. File:
50
62
  * `content/<tenant>/chrome.html`. Tenant-owned (no cross-tenant fallback).
63
+ *
64
+ * `currentPath`, when given, is the tenant-relative request path (e.g. "/"
65
+ * or "/collections/all") in the same unprefixed form the chrome config's
66
+ * own nav `href`s are authored in — drives `aria-current="page"` on the
67
+ * matching primary-nav link when the wrapper is rendered from chrome.json
68
+ * (rawChrome.ts). Omitted ⇒ no item is marked current.
51
69
  */
52
- getChromeHtml(): Promise<string | null>;
53
- getChrome(): Promise<SiteChrome>;
70
+ getChromeHtml(currentPath?: string): Promise<string | null>;
71
+ getChrome(): Promise<ChromeConfig>;
54
72
  getPage(slug: string): Promise<EditorialPage | null>;
55
73
  listPages(): Promise<EditorialPage[]>;
74
+ /** Raw tenant-content artifact, relative to `content/`. */
75
+ readArtifact(relative: string): Promise<string | null>;
76
+ /** Artifact paths relative to `content/`; null means use a legacy fallback. */
77
+ listArtifactPaths(prefix?: string): Promise<string[] | null>;
56
78
  }
57
79
 
58
80
  // ---------------------------------------------------------------------------
@@ -67,9 +89,16 @@ export interface ContentProvider {
67
89
  const homeGlobs = import.meta.glob<HomeContent>("../../../../../tenants/*/content/home.json", {
68
90
  import: "default",
69
91
  });
70
- const chromeGlobs = import.meta.glob<SiteChrome>("../../../../../tenants/*/content/chrome.json", {
92
+ const chromeGlobs = import.meta.glob<unknown>("../../../../../tenants/*/content/chrome.json", {
71
93
  import: "default",
72
94
  });
95
+ // Per-page header/footer variant overrides for the raw-HTML chrome path (see
96
+ // lib/chrome/assignments.ts). Tenant-owned, no cross-tenant fallback — same
97
+ // convention as chromeGlobs above.
98
+ const chromeAssignmentGlobs = import.meta.glob<unknown>(
99
+ "../../../../../tenants/*/content/chrome-assignments.json",
100
+ { import: "default" },
101
+ );
73
102
  const pageGlobs = import.meta.glob<EditorialPage>("../../../../../tenants/*/content/pages/*.json", {
74
103
  import: "default",
75
104
  });
@@ -91,6 +120,10 @@ const chromeHtmlGlobs = import.meta.glob<string>("../../../../../tenants/*/conte
91
120
  query: "?raw",
92
121
  import: "default",
93
122
  });
123
+ const artifactGlobs = import.meta.glob<string>("../../../../../tenants/*/content/**/*", {
124
+ query: "?raw",
125
+ import: "default",
126
+ });
94
127
 
95
128
  const DEFAULT_SCOPE = "home";
96
129
 
@@ -196,18 +229,22 @@ export class LocalContentProvider implements ContentProvider {
196
229
  .sort((a, b) => a.localeCompare(b));
197
230
  }
198
231
 
199
- async getChromeHtml(): Promise<string | null> {
232
+ async getChromeHtml(currentPath?: string): Promise<string | null> {
200
233
  // (1) A hand-authored chrome.html wins — keeps a not-yet-adopted tenant's chrome
201
234
  // byte-identical (dev: fresh from disk so an edit previews on save).
202
235
  const dev = await readRawDevFile(this.scope, "chrome.html");
203
236
  if (dev !== null) return dev;
204
237
  const loader = pickContent(chromeHtmlGlobs, this.scope);
205
238
  if (loader) return loader();
206
- // (2) No chrome.html: render the CANONICAL shared chrome (unit F3) from the
207
- // tenant's chrome.json — but only when it opted in (sharedChrome: true).
208
- // Tenant-owned only, like the raw HTML above (no cross-tenant fallback).
239
+ // (2) No chrome.html: render the CANONICAL shared chrome from the
240
+ // tenant's chrome.json — but only when it validates whole against the
241
+ // consolidated ChromeConfig schema (lib/chrome/parseConfig.ts). Tenant-
242
+ // owned only, like the raw HTML above (no cross-tenant fallback).
209
243
  const config = await this.loadChromeConfig();
210
- return config ? renderRawChrome(config) : null;
244
+ if (!config) return null;
245
+ // Per-page variant override — a page not listed in chrome-assignments.json
246
+ // keeps chrome.json's default variant. renderRawChrome resolves it.
247
+ return renderRawChrome(config, currentPath, await this.loadChromeAssignments());
211
248
  }
212
249
 
213
250
  /** The tenant's parsed raw-chrome config (chrome.json), or null if not adopted. */
@@ -225,31 +262,33 @@ export class LocalContentProvider implements ContentProvider {
225
262
  return loader ? parseChromeConfig(await loader()) : null;
226
263
  }
227
264
 
228
- async getChrome(): Promise<SiteChrome> {
265
+ /** The tenant's parsed page-assignment manifest (chrome-assignments.json),
266
+ * or null if absent/invalid — same dev-hot-reload + tenant-owned-only
267
+ * discipline as loadChromeConfig. */
268
+ private async loadChromeAssignments(): Promise<ReturnType<typeof parseChromeAssignments>> {
269
+ const devRaw = await readRawDevFile(this.scope, "chrome-assignments.json");
270
+ if (devRaw !== null) {
271
+ try {
272
+ return parseChromeAssignments(JSON.parse(devRaw));
273
+ } catch {
274
+ return null;
275
+ }
276
+ }
277
+ const loader = pickContent(chromeAssignmentGlobs, this.scope);
278
+ return loader ? parseChromeAssignments(await loader()) : null;
279
+ }
280
+
281
+ async getChrome(): Promise<ChromeConfig> {
282
+ // The full config is validated as ONE document now (header/footer are
283
+ // both required) — there is no more partial-field merge across scopes; a
284
+ // tenant's own chrome.json either validates whole, or the default scope's
285
+ // does, or the storefront falls back to an empty-but-valid config.
286
+ const own = await this.loadChromeConfig();
287
+ if (own) return own;
288
+ if (this.scope === DEFAULT_SCOPE) return EMPTY_CHROME_CONFIG;
229
289
  const defL = pickContent(chromeGlobs, DEFAULT_SCOPE);
230
- const def: SiteChrome = defL
231
- ? await defL()
232
- : { nav: [], footer: { columns: [], social: [] } };
233
- const ownL = pickContent(chromeGlobs, this.scope);
234
- if (!ownL) return def;
235
- const c = await ownL();
236
- if (this.scope === DEFAULT_SCOPE) return c;
237
- // Per-field fallback fills only STRUCTURAL scaffolding (nav/footer) so a
238
- // partially-adopted tenant still renders. Promotional/identity/legal fields
239
- // (announcement, headerCtas, disclaimers) are tenant-OWNED and never inherit
240
- // from the default scope — otherwise e.g. a marketing tenant would show the
241
- // default store's shipping banner or nicotine disclaimers.
242
- return {
243
- nav: c.nav?.length ? c.nav : def.nav,
244
- footer:
245
- c.footer && (c.footer.columns?.length || c.footer.social?.length)
246
- ? c.footer
247
- : def.footer,
248
- headerCtas: c.headerCtas,
249
- announcement: c.announcement,
250
- disclaimer: c.disclaimer,
251
- disclaimers: c.disclaimers,
252
- };
290
+ const def = defL ? parseChromeConfig(await defL()) : null;
291
+ return def ?? EMPTY_CHROME_CONFIG;
253
292
  }
254
293
 
255
294
  async getPage(slug: string): Promise<EditorialPage | null> {
@@ -264,6 +303,24 @@ export class LocalContentProvider implements ContentProvider {
264
303
  const loaders = pickPages(this.scope);
265
304
  return Promise.all(loaders.map((l) => l()));
266
305
  }
306
+
307
+ async readArtifact(relative: string): Promise<string | null> {
308
+ const normalized = relative.replace(/^\/+/, "");
309
+ const marker = `/tenants/${this.scope}/content/${normalized}`;
310
+ const key = Object.keys(artifactGlobs).find((candidate) => candidate.endsWith(marker));
311
+ return key ? artifactGlobs[key]!() : null;
312
+ }
313
+
314
+ async listArtifactPaths(prefix = ""): Promise<string[]> {
315
+ const marker = `/tenants/${this.scope}/content/`;
316
+ return Object.keys(artifactGlobs)
317
+ .flatMap((key) => {
318
+ const at = key.indexOf(marker);
319
+ return at < 0 ? [] : [key.slice(at + marker.length)];
320
+ })
321
+ .filter((path) => path.startsWith(prefix))
322
+ .sort((a, b) => a.localeCompare(b));
323
+ }
267
324
  }
268
325
 
269
326
  // ---------------------------------------------------------------------------
@@ -405,26 +462,24 @@ export class StoryblokContentProvider implements ContentProvider {
405
462
  return this.fallback.listPageHtmlSlugs();
406
463
  }
407
464
 
408
- getChromeHtml(): Promise<string | null> {
465
+ getChromeHtml(currentPath?: string): Promise<string | null> {
409
466
  // The chrome wrapper lives in tenant content files, not Storyblok — delegate.
410
- return this.fallback.getChromeHtml();
467
+ return this.fallback.getChromeHtml(currentPath);
411
468
  }
412
469
 
413
- async getChrome(): Promise<SiteChrome> {
414
- // Editorial chrome (nav/footer/tagline) is authored in Storyblok; the
415
- // announcement + legal disclaimers are MIGRATION-sourced (written to local
416
- // chrome.json), so overlay those from the fallback regardless.
470
+ async getChrome(): Promise<ChromeConfig> {
471
+ // Editorial chrome (nav/footer) is authored in Storyblok; brand/variant
472
+ // and everything else stay MIGRATION-sourced (the local chrome.json), so
473
+ // overlay only nav + footer from Storyblok when present.
417
474
  const local = await this.fallback.getChrome();
418
475
  try {
419
476
  const story = await fetchStory(this.token, "chrome");
420
477
  const c = story?.content;
421
478
  if (c && Array.isArray(c.nav)) {
422
479
  return {
423
- nav: c.nav,
424
- footer: c.footer ?? local.footer,
425
- announcement: local.announcement ?? c.announcement,
426
- disclaimer: local.disclaimer,
427
- disclaimers: local.disclaimers,
480
+ ...local,
481
+ header: { ...local.header, nav: c.nav },
482
+ footer: c.footer ? { ...local.footer, ...c.footer } : local.footer,
428
483
  };
429
484
  }
430
485
  } catch {
@@ -461,6 +516,14 @@ export class StoryblokContentProvider implements ContentProvider {
461
516
  }
462
517
  return this.fallback.listPages();
463
518
  }
519
+
520
+ readArtifact(relative: string): Promise<string | null> {
521
+ return this.fallback.readArtifact(relative);
522
+ }
523
+
524
+ listArtifactPaths(prefix?: string): Promise<string[] | null> {
525
+ return this.fallback.listArtifactPaths(prefix);
526
+ }
464
527
  }
465
528
 
466
529
  // ---------------------------------------------------------------------------
@@ -468,7 +531,8 @@ export class StoryblokContentProvider implements ContentProvider {
468
531
  // resolved environment (store->app wiring). For each content artifact it checks
469
532
  // the customization store ({live,test} pointer -> versioned JSON); if present it
470
533
  // serves that, otherwise it delegates to the base provider (Storyblok/local).
471
- // listPages() stays from the base (per-page overrides flow through getPage).
534
+ // Enumeration also follows the selected immutable manifest, so sitemap/blog/page
535
+ // indexes do not need a build-time tenant checkout.
472
536
  // ---------------------------------------------------------------------------
473
537
 
474
538
  export interface ContentCustomization {
@@ -517,34 +581,72 @@ export class PublishedContentProvider implements ContentProvider {
517
581
  return (await this.publishedRaw(rel)) ?? this.base.getPageHtml(slug);
518
582
  }
519
583
  listPageHtmlSlugs(): Promise<string[]> {
520
- return this.base.listPageHtmlSlugs();
584
+ return this.listArtifactPaths("pages-html/").then((paths) =>
585
+ paths === null
586
+ ? this.base.listPageHtmlSlugs()
587
+ : paths
588
+ .filter((path) => path.endsWith(".html"))
589
+ .map((path) => path.slice("pages-html/".length, -".html".length)),
590
+ );
521
591
  }
522
- async getChromeHtml(): Promise<string | null> {
592
+ async getChromeHtml(currentPath?: string): Promise<string | null> {
523
593
  // A published raw chrome.html override (parallels the home.html/pages-html
524
594
  // publish path) wins, so a vendor edit previews / goes live with no rebuild.
525
595
  const publishedHtml = await this.publishedRaw("chrome.html");
526
596
  if (publishedHtml) return publishedHtml;
527
- // Else, if the PUBLISHED chrome.json opts into shared chrome (unit F3), render
597
+ // Else, if the PUBLISHED chrome.json opts into shared chrome, render
528
598
  // the canonical wrapper from it — so a config edit previews without a rebuild.
529
599
  const config = parseChromeConfig(await this.published<unknown>("chrome.json"));
530
- if (config) return renderRawChrome(config);
600
+ if (config) {
601
+ // Per-page variant override, resolved against the PUBLISHED assignment
602
+ // manifest (mirrors chrome.json itself — each source resolves fully
603
+ // within itself, no mixing published/base artifacts).
604
+ const assignments = parseChromeAssignments(await this.published<unknown>("chrome-assignments.json"));
605
+ return renderRawChrome(config, currentPath, assignments);
606
+ }
531
607
  // Otherwise fall back to the base chain (bundled chrome.html or config render).
532
- return this.base.getChromeHtml();
608
+ return this.base.getChromeHtml(currentPath);
533
609
  }
534
- async getChrome(): Promise<SiteChrome> {
535
- return (await this.published<SiteChrome>("chrome.json")) ?? this.base.getChrome();
610
+ async getChrome(): Promise<ChromeConfig> {
611
+ // Runs through the SAME fail-closed schema validator as getChromeHtml's
612
+ // chrome.json branch (lib/chrome/parseConfig.ts) — a published artifact is
613
+ // no longer trusted blind (was: JSON.parse + a raw cast to SiteChrome).
614
+ const config = parseChromeConfig(await this.published<unknown>("chrome.json"));
615
+ return config ?? this.base.getChrome();
536
616
  }
537
617
  async getPage(slug: string): Promise<EditorialPage | null> {
538
618
  return (await this.published<EditorialPage>(`pages/${slug}.json`)) ?? this.base.getPage(slug);
539
619
  }
540
- listPages(): Promise<EditorialPage[]> {
541
- return this.base.listPages();
620
+ async listPages(): Promise<EditorialPage[]> {
621
+ const paths = await this.listArtifactPaths("pages/");
622
+ if (paths === null) return this.base.listPages();
623
+ const pages = await Promise.all(
624
+ paths
625
+ .filter((path) => path.endsWith(".json"))
626
+ .map(async (path) => this.published<EditorialPage>(path)),
627
+ );
628
+ return pages.filter((page): page is EditorialPage => page !== null);
629
+ }
630
+
631
+ readArtifact(relative: string): Promise<string | null> {
632
+ return this.publishedRaw(relative);
633
+ }
634
+
635
+ async listArtifactPaths(prefix = ""): Promise<string[] | null> {
636
+ const root = contentArtifactPath(this.tenantId, "");
637
+ const paths = await listPublishedArtifactPaths(
638
+ this.cust.get,
639
+ this.tenantId,
640
+ this.cust.env,
641
+ `${root}${prefix}`,
642
+ );
643
+ return paths?.map((path) => path.slice(root.length)) ?? null;
542
644
  }
543
645
  }
544
646
 
545
647
  // ---------------------------------------------------------------------------
546
- // DevDraftContentProvider — the per-developer DRAFT overlay (decision
547
- // draft-overlay-same-origin). Wraps the resolved provider chain and serves a
648
+ // DevDraftContentProvider — the per-developer DRAFT overlay. Wraps the
649
+ // resolved provider chain and serves a
548
650
  // draft artifact (written by /api/content-edit/ai-edit into TENANT_CACHE under
549
651
  // `dev-draft:<tenantId>:<draftId>:<path>`) when one exists, else delegates.
550
652
  // The middleware constructs this ONLY for a request whose signed tot_dev_draft
@@ -581,10 +683,10 @@ export class DevDraftContentProvider implements ContentProvider {
581
683
  listPageHtmlSlugs(): Promise<string[]> {
582
684
  return this.base.listPageHtmlSlugs();
583
685
  }
584
- getChromeHtml(): Promise<string | null> {
585
- return this.base.getChromeHtml();
686
+ getChromeHtml(currentPath?: string): Promise<string | null> {
687
+ return this.base.getChromeHtml(currentPath);
586
688
  }
587
- getChrome(): Promise<SiteChrome> {
689
+ getChrome(): Promise<ChromeConfig> {
588
690
  return this.base.getChrome();
589
691
  }
590
692
  getPage(slug: string): Promise<EditorialPage | null> {
@@ -593,6 +695,12 @@ export class DevDraftContentProvider implements ContentProvider {
593
695
  listPages(): Promise<EditorialPage[]> {
594
696
  return this.base.listPages();
595
697
  }
698
+ readArtifact(relative: string): Promise<string | null> {
699
+ return this.base.readArtifact(relative);
700
+ }
701
+ listArtifactPaths(prefix?: string): Promise<string[] | null> {
702
+ return this.base.listArtifactPaths(prefix);
703
+ }
596
704
  }
597
705
 
598
706
  const PLACEHOLDER_TOKEN_RE = /PLACEHOLDER|^$/i;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Because a savings headline (a "%", a "$" amount) is a claim, {@link
10
10
  * SubscriptionFigure} is treated as ILLUSTRATIVE by default — same discipline
11
- * as the membership tier figures (decision compliance-widgets-platform-owned):
11
+ * as the membership tier figures:
12
12
  * unless a figure explicitly asserts `real: true`, the widget marks it as an
13
13
  * example so it is never presented as a live offer.
14
14
  */
@@ -44,8 +44,8 @@ export interface SubscriptionFigure {
44
44
  }
45
45
 
46
46
  /**
47
- * The account-management actions a subscriber can take on an Autopilot order
48
- * (F10). The storefront never EXECUTES these — each is a handoff into the
47
+ * The account-management actions a subscriber can take on an Autopilot order.
48
+ * The storefront never EXECUTES these — each is a handoff into the
49
49
  * tenant's product-owned manage-subscription portal (see {@link
50
50
  * SubscriptionManagePortal}); this enum just gives the entry points stable,
51
51
  * typed identities so the copy, ordering, and analytics hooks are consistent.
@@ -151,7 +151,7 @@ function defaultActionHref(action: SubscriptionManageAction, baseHref: string):
151
151
  }
152
152
 
153
153
  /**
154
- * Build the manage-subscription entry points for a tenant's portal — the F10
154
+ * Build the manage-subscription entry points for a tenant's portal — the
155
155
  * skip / pause / cancel / reschedule / quantity (+ resume / swap / address)
156
156
  * handoffs. Each entry is a plain link into `portal.baseHref`; the storefront
157
157
  * hands off, the provider executes.
@@ -4,12 +4,13 @@
4
4
  // collection-bound helpers.
5
5
  import { getPost, listPosts, resolveRedirect, type Collection } from "../blog/collection.js";
6
6
  import type { BlogPost, BlogPostSummary } from "../blog/types.js";
7
+ import type { ContentProvider } from "../storyblok/provider.js";
7
8
 
8
9
  export const THE_BUILD: Collection = { id: "the-build", base: "/the-build" };
9
10
 
10
11
  export function listBuildPosts(
11
12
  tenantId: string,
12
- opts?: { includeUnpublished?: boolean; now?: string },
13
+ opts?: { includeUnpublished?: boolean; now?: string; content?: ContentProvider },
13
14
  ): Promise<BlogPostSummary[]> {
14
15
  return listPosts(tenantId, THE_BUILD.id, opts);
15
16
  }
@@ -17,11 +18,15 @@ export function listBuildPosts(
17
18
  export function getBuildPost(
18
19
  tenantId: string,
19
20
  slug: string,
20
- opts?: { includeUnpublished?: boolean; now?: string },
21
+ opts?: { includeUnpublished?: boolean; now?: string; content?: ContentProvider },
21
22
  ): Promise<BlogPost | null> {
22
23
  return getPost(tenantId, THE_BUILD.id, slug, opts);
23
24
  }
24
25
 
25
- export function resolveBuildRedirect(tenantId: string, slug: string): Promise<string | null> {
26
- return resolveRedirect(tenantId, THE_BUILD.id, slug);
26
+ export function resolveBuildRedirect(
27
+ tenantId: string,
28
+ slug: string,
29
+ content?: ContentProvider,
30
+ ): Promise<string | null> {
31
+ return resolveRedirect(tenantId, THE_BUILD.id, slug, content);
27
32
  }