@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
@@ -1,4 +1,16 @@
1
+ import { tenantDirSegment } from "@tot/public-runtime";
1
2
  import type { AuthorBio, BlogPost, BlogPostSummary, PostStatus } from "./types.js";
3
+ import type { ContentProvider } from "../storyblok/provider.js";
4
+
5
+ type ArtifactSource = Pick<ContentProvider, "readArtifact" | "listArtifactPaths">;
6
+ function canReadArtifacts(value: ArtifactSource | undefined): value is ArtifactSource {
7
+ return typeof value?.listArtifactPaths === "function" && typeof value.readArtifact === "function";
8
+ }
9
+ export interface CollectionReadOptions {
10
+ includeUnpublished?: boolean;
11
+ now?: string;
12
+ content?: ArtifactSource;
13
+ }
2
14
 
3
15
  /** A blog collection = a route base + a per-tenant content dir. */
4
16
  export interface Collection {
@@ -10,15 +22,24 @@ export interface Collection {
10
22
  // collection id are both path segments filtered at call time. The derived index
11
23
  // is the set of `posts/*.json` files — there is no hand-maintained index.json.
12
24
  const postGlobs = import.meta.glob<BlogPost>(
13
- "../../../../../tenants/*/content/*/posts/*.json",
25
+ [
26
+ "../../../../../tenants/*/content/*/posts/*.json",
27
+ "../../../../../tenants/e2e/*/content/*/posts/*.json",
28
+ ],
14
29
  { import: "default" },
15
30
  );
16
31
  const redirectGlobs = import.meta.glob<Record<string, string>>(
17
- "../../../../../tenants/*/content/*/redirects.json",
32
+ [
33
+ "../../../../../tenants/*/content/*/redirects.json",
34
+ "../../../../../tenants/e2e/*/content/*/redirects.json",
35
+ ],
18
36
  { import: "default" },
19
37
  );
20
38
  const authorsGlobs = import.meta.glob<Record<string, AuthorBio>>(
21
- "../../../../../tenants/*/content/*/authors.json",
39
+ [
40
+ "../../../../../tenants/*/content/*/authors.json",
41
+ "../../../../../tenants/e2e/*/content/*/authors.json",
42
+ ],
22
43
  { import: "default" },
23
44
  );
24
45
 
@@ -117,7 +138,7 @@ function normalize(raw: BlogPost): BlogPost {
117
138
  }
118
139
 
119
140
  function postsPrefix(tenantId: string, collection: string): string {
120
- return `/tenants/${tenantId}/content/${collection}/posts/`;
141
+ return `${tenantDirSegment(tenantId)}content/${collection}/posts/`;
121
142
  }
122
143
 
123
144
  function toSummary(p: BlogPost): BlogPostSummary {
@@ -147,9 +168,22 @@ function toSummary(p: BlogPost): BlogPostSummary {
147
168
  * the same glob the index is built from (no per-tenant config needed). Sorted for
148
169
  * a stable admin ordering. A tenant with no blog content returns `[]`.
149
170
  */
150
- export function listCollections(tenantId: string): string[] {
151
- const marker = `/tenants/${tenantId}/content/`;
171
+ export async function listCollections(
172
+ tenantId: string,
173
+ content?: ArtifactSource,
174
+ ): Promise<string[]> {
175
+ const marker = `${tenantDirSegment(tenantId)}content/`;
152
176
  const collections = new Set<string>();
177
+ const runtimePaths = canReadArtifacts(content)
178
+ ? await content.listArtifactPaths("")
179
+ : null;
180
+ if (runtimePaths !== null) {
181
+ for (const path of runtimePaths) {
182
+ const match = /^([^/]+)\/posts\/[^/]+\.json$/.exec(path);
183
+ if (match?.[1] && SEGMENT_RE.test(match[1])) collections.add(match[1]);
184
+ }
185
+ return [...collections].sort();
186
+ }
153
187
  for (const key of Object.keys(postGlobs)) {
154
188
  const at = key.indexOf(marker);
155
189
  if (at < 0) continue;
@@ -168,12 +202,33 @@ export function listCollections(tenantId: string): string[] {
168
202
  export async function listPosts(
169
203
  tenantId: string,
170
204
  collection: string,
171
- opts?: { includeUnpublished?: boolean; now?: string },
205
+ opts?: CollectionReadOptions,
172
206
  ): Promise<BlogPostSummary[]> {
173
207
  if (!SEGMENT_RE.test(collection)) return [];
174
208
  const prefix = postsPrefix(tenantId, collection);
175
- const keys = Object.keys(postGlobs).filter((key) => key.includes(prefix));
176
- const posts = (await Promise.all(keys.map((key) => postGlobs[key]!()))).map(normalize);
209
+ const runtimeContent = canReadArtifacts(opts?.content) ? opts.content : null;
210
+ const runtimePaths = runtimeContent
211
+ ? await runtimeContent.listArtifactPaths(`${collection}/posts/`)
212
+ : null;
213
+ const posts = runtimePaths !== null
214
+ ? (await Promise.all(
215
+ runtimePaths
216
+ .filter((path) => path.endsWith(".json"))
217
+ .map(async (path) => {
218
+ const raw = await runtimeContent?.readArtifact(path);
219
+ if (!raw) return null;
220
+ try {
221
+ return normalize(JSON.parse(raw) as BlogPost);
222
+ } catch {
223
+ return null;
224
+ }
225
+ }),
226
+ )).filter((post): post is BlogPost => post !== null)
227
+ : (await Promise.all(
228
+ Object.keys(postGlobs)
229
+ .filter((key) => key.includes(prefix))
230
+ .map((key) => postGlobs[key]!()),
231
+ )).map(normalize);
177
232
  const includeUnpublished = opts?.includeUnpublished ?? false;
178
233
  const now = opts?.now ?? new Date().toISOString();
179
234
  return posts
@@ -191,9 +246,23 @@ export async function getPost(
191
246
  tenantId: string,
192
247
  collection: string,
193
248
  slug: string,
194
- opts?: { includeUnpublished?: boolean; now?: string },
249
+ opts?: CollectionReadOptions,
195
250
  ): Promise<BlogPost | null> {
196
251
  if (!SEGMENT_RE.test(collection) || !SLUG_RE.test(slug)) return null;
252
+ if (canReadArtifacts(opts?.content)) {
253
+ const paths = await opts.content.listArtifactPaths(`${collection}/posts/`);
254
+ const raw = await opts.content.readArtifact(`${collection}/posts/${slug}.json`);
255
+ if (raw) {
256
+ try {
257
+ const post = normalize(JSON.parse(raw) as BlogPost);
258
+ if (!(opts.includeUnpublished ?? false) && !isVisibleOnLive(post, opts.now)) return null;
259
+ return post;
260
+ } catch {
261
+ return null;
262
+ }
263
+ }
264
+ if (paths !== null) return null;
265
+ }
197
266
  const key = Object.keys(postGlobs).find(
198
267
  (candidate) =>
199
268
  candidate.includes(postsPrefix(tenantId, collection)) && candidate.endsWith(`/${slug}.json`),
@@ -215,7 +284,7 @@ export async function listTranslations(
215
284
  tenantId: string,
216
285
  collection: string,
217
286
  translationGroupId: string,
218
- opts?: { includeUnpublished?: boolean; now?: string },
287
+ opts?: CollectionReadOptions,
219
288
  ): Promise<BlogPostSummary[]> {
220
289
  if (!translationGroupId) return [];
221
290
  const posts = await listPosts(tenantId, collection, opts);
@@ -233,7 +302,7 @@ export async function getTranslations(
233
302
  tenantId: string,
234
303
  collection: string,
235
304
  slug: string,
236
- opts?: { includeUnpublished?: boolean; now?: string },
305
+ opts?: CollectionReadOptions,
237
306
  ): Promise<BlogPostSummary[]> {
238
307
  const post = await getPost(tenantId, collection, slug, opts);
239
308
  if (!post) return [];
@@ -252,9 +321,10 @@ export async function relatedPosts(
252
321
  collection: string,
253
322
  slug: string,
254
323
  n = 3,
324
+ content?: ArtifactSource,
255
325
  ): Promise<BlogPostSummary[]> {
256
- const pool = (await listPosts(tenantId, collection)).filter((p) => p.slug !== slug);
257
- const current = await getPost(tenantId, collection, slug);
326
+ const pool = (await listPosts(tenantId, collection, { content })).filter((p) => p.slug !== slug);
327
+ const current = await getPost(tenantId, collection, slug, { content });
258
328
  const category = current?.categories[0];
259
329
  const result = category ? pool.filter((p) => p.categories.includes(category)) : [];
260
330
  for (const p of pool) {
@@ -268,9 +338,10 @@ export async function resolveRedirect(
268
338
  tenantId: string,
269
339
  collection: string,
270
340
  slug: string,
341
+ content?: ArtifactSource,
271
342
  ): Promise<string | null> {
272
343
  if (!SEGMENT_RE.test(collection)) return null;
273
- const redirects = await getRedirects(tenantId, collection);
344
+ const redirects = await getRedirects(tenantId, collection, content);
274
345
  return redirects[slug] ?? null;
275
346
  }
276
347
 
@@ -282,11 +353,24 @@ export async function resolveRedirect(
282
353
  export async function getRedirects(
283
354
  tenantId: string,
284
355
  collection: string,
356
+ content?: ArtifactSource,
285
357
  ): Promise<Record<string, string>> {
286
358
  if (!SEGMENT_RE.test(collection)) return {};
359
+ if (canReadArtifacts(content)) {
360
+ const paths = await content.listArtifactPaths(`${collection}/`);
361
+ const raw = await content.readArtifact(`${collection}/redirects.json`);
362
+ if (raw) {
363
+ try {
364
+ return JSON.parse(raw) as Record<string, string>;
365
+ } catch {
366
+ return {};
367
+ }
368
+ }
369
+ if (paths !== null) return {};
370
+ }
287
371
  const key = Object.keys(redirectGlobs).find(
288
372
  (candidate) =>
289
- candidate.includes(`/tenants/${tenantId}/content/${collection}/`) &&
373
+ candidate.includes(`${tenantDirSegment(tenantId)}content/${collection}/`) &&
290
374
  candidate.endsWith("/redirects.json"),
291
375
  );
292
376
  if (!key) return {};
@@ -301,11 +385,24 @@ export async function getRedirects(
301
385
  export async function getAuthors(
302
386
  tenantId: string,
303
387
  collection: string,
388
+ content?: ArtifactSource,
304
389
  ): Promise<Record<string, AuthorBio>> {
305
390
  if (!SEGMENT_RE.test(collection)) return {};
391
+ if (canReadArtifacts(content)) {
392
+ const paths = await content.listArtifactPaths(`${collection}/`);
393
+ const raw = await content.readArtifact(`${collection}/authors.json`);
394
+ if (raw) {
395
+ try {
396
+ return JSON.parse(raw) as Record<string, AuthorBio>;
397
+ } catch {
398
+ return {};
399
+ }
400
+ }
401
+ if (paths !== null) return {};
402
+ }
306
403
  const key = Object.keys(authorsGlobs).find(
307
404
  (candidate) =>
308
- candidate.includes(`/tenants/${tenantId}/content/${collection}/`) &&
405
+ candidate.includes(`${tenantDirSegment(tenantId)}content/${collection}/`) &&
309
406
  candidate.endsWith("/authors.json"),
310
407
  );
311
408
  if (!key) return {};
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Minimal, dependency-free Markdown -> sanitized HTML for the admin blog editor
3
- * (B3.4 of docs/plans/2026-08-21-blog-platform-workstream.md). The storefront
3
+ * (see docs/plans/2026-08-21-blog-platform-workstream.md). The storefront
4
4
  * ships no markdown library (see the "No new deps" convention in §1), and the
5
5
  * migration package's `sanitize-html` is a node-only devtool — so authoring from
6
6
  * the admin gets this small hand-rolled renderer instead.
@@ -3,41 +3,108 @@
3
3
  // derived engine lives in ./collection.ts and powers `the-build` + any new
4
4
  // collection; switching `/blog` onto it is a later migration once order is
5
5
  // encoded as data (see B0.1 §"/blog migration caution").
6
+ import { tenantDirSegment } from "@tot/public-runtime";
6
7
  import type { BlogPost, BlogPostSummary } from "./types.js";
8
+ import type { ContentProvider } from "../storyblok/provider.js";
7
9
 
10
+ type ArtifactSource = Pick<ContentProvider, "readArtifact" | "listArtifactPaths">;
11
+
12
+ function canReadArtifacts(value: ArtifactSource | undefined): value is ArtifactSource {
13
+ return typeof value?.listArtifactPaths === "function" && typeof value.readArtifact === "function";
14
+ }
15
+
16
+ // Both tenant tiers: real at `tenants/<id>/`, platform test one level deeper at
17
+ // `tenants/e2e/<id>/`. `tenantDirSegment` is the one inverse used to look a
18
+ // tenant's keys back up.
8
19
  const indexGlobs = import.meta.glob<BlogPostSummary[]>(
9
- "../../../../../tenants/*/content/blog/index.json",
20
+ [
21
+ "../../../../../tenants/*/content/blog/index.json",
22
+ "../../../../../tenants/e2e/*/content/blog/index.json",
23
+ ],
10
24
  { import: "default" },
11
25
  );
12
26
  const postGlobs = import.meta.glob<BlogPost>(
13
- "../../../../../tenants/*/content/blog/posts/*.json",
27
+ [
28
+ "../../../../../tenants/*/content/blog/posts/*.json",
29
+ "../../../../../tenants/e2e/*/content/blog/posts/*.json",
30
+ ],
14
31
  { import: "default" },
15
32
  );
16
33
  const redirectGlobs = import.meta.glob<Record<string, string>>(
17
- "../../../../../tenants/*/content/blog/redirects.json",
34
+ [
35
+ "../../../../../tenants/*/content/blog/redirects.json",
36
+ "../../../../../tenants/e2e/*/content/blog/redirects.json",
37
+ ],
18
38
  { import: "default" },
19
39
  );
20
40
 
21
41
  function tenantKey(keys: string[], tenantId: string, suffix: string): string | undefined {
22
- const segment = `/tenants/${tenantId}/content/blog/`;
42
+ const segment = `${tenantDirSegment(tenantId)}content/blog/`;
23
43
  return keys.find((key) => key.includes(segment) && key.endsWith(suffix));
24
44
  }
25
45
 
26
- export async function listBlogPosts(tenantId: string): Promise<BlogPostSummary[]> {
46
+ export async function listBlogPosts(
47
+ tenantId: string,
48
+ source?: ArtifactSource,
49
+ ): Promise<BlogPostSummary[]> {
50
+ if (canReadArtifacts(source)) {
51
+ const paths = await source.listArtifactPaths("blog/");
52
+ const raw = await source.readArtifact("blog/index.json");
53
+ if (raw) {
54
+ try {
55
+ return JSON.parse(raw) as BlogPostSummary[];
56
+ } catch {
57
+ return [];
58
+ }
59
+ }
60
+ if (paths !== null) return [];
61
+ }
27
62
  const key = tenantKey(Object.keys(indexGlobs), tenantId, "/index.json")
28
- ?? Object.keys(indexGlobs).find((candidate) => candidate.includes(`/tenants/${tenantId}/content/blog/index.json`));
63
+ ?? Object.keys(indexGlobs).find((candidate) => candidate.includes(`${tenantDirSegment(tenantId)}content/blog/index.json`));
29
64
  return key ? indexGlobs[key]!() : [];
30
65
  }
31
66
 
32
- export async function getBlogPost(tenantId: string, slug: string): Promise<BlogPost | null> {
67
+ export async function getBlogPost(
68
+ tenantId: string,
69
+ slug: string,
70
+ source?: ArtifactSource,
71
+ ): Promise<BlogPost | null> {
33
72
  if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) return null;
73
+ if (canReadArtifacts(source)) {
74
+ const paths = await source.listArtifactPaths("blog/posts/");
75
+ const raw = await source.readArtifact(`blog/posts/${slug}.json`);
76
+ if (raw) {
77
+ try {
78
+ return JSON.parse(raw) as BlogPost;
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+ if (paths !== null) return null;
84
+ }
34
85
  const key = tenantKey(Object.keys(postGlobs), tenantId, `/posts/${slug}.json`);
35
86
  return key ? postGlobs[key]!() : null;
36
87
  }
37
88
 
38
- export async function resolveBlogRedirect(tenantId: string, slug: string): Promise<string | null> {
89
+ export async function resolveBlogRedirect(
90
+ tenantId: string,
91
+ slug: string,
92
+ source?: ArtifactSource,
93
+ ): Promise<string | null> {
94
+ if (canReadArtifacts(source)) {
95
+ const paths = await source.listArtifactPaths("blog/");
96
+ const raw = await source.readArtifact("blog/redirects.json");
97
+ if (raw) {
98
+ try {
99
+ return (JSON.parse(raw) as Record<string, string>)[slug] ?? null;
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ if (paths !== null) return null;
105
+ }
39
106
  const key = tenantKey(Object.keys(redirectGlobs), tenantId, "/redirects.json")
40
- ?? Object.keys(redirectGlobs).find((candidate) => candidate.includes(`/tenants/${tenantId}/content/blog/redirects.json`));
107
+ ?? Object.keys(redirectGlobs).find((candidate) => candidate.includes(`${tenantDirSegment(tenantId)}content/blog/redirects.json`));
41
108
  if (!key) return null;
42
109
  const redirects = await redirectGlobs[key]!();
43
110
  return redirects[slug] ?? null;
@@ -5,7 +5,7 @@ export interface BlogImage {
5
5
  height?: number;
6
6
  }
7
7
 
8
- export type PostStatus = "draft" | "published" | "unlisted" | "scheduled"; // "scheduled" added in B3.1
8
+ export type PostStatus = "draft" | "published" | "unlisted" | "scheduled";
9
9
 
10
10
  /**
11
11
  * Per-post entitlement gate — who may read the body. Orthogonal to `PostStatus`
@@ -29,17 +29,17 @@ export interface BlogPostSummary {
29
29
  tags: string[];
30
30
  featuredImage?: BlogImage;
31
31
  status: PostStatus; // seeded/legacy posts without this field default to "published"
32
- scheduledAt?: string; // ISO date; when status === "scheduled", the post goes live once this instant has passed (B3.1)
33
- canonicalUrl?: string; // syndication override (B1.6)
34
- inLanguage?: string; // default "en" (B1.6)
35
- access?: BlogAccess; // entitlement gate; default "public". Orthogonal to `status` (B4.0)
36
- translationGroupId?: string; // stable id shared by every language variant of one article; absent = singleton (I1)
32
+ scheduledAt?: string; // ISO date; when status === "scheduled", the post goes live once this instant has passed
33
+ canonicalUrl?: string; // syndication override
34
+ inLanguage?: string; // default "en"
35
+ access?: BlogAccess; // entitlement gate; default "public". Orthogonal to `status`
36
+ translationGroupId?: string; // stable id shared by every language variant of one article; absent = singleton
37
37
  }
38
38
 
39
39
  export interface BlogPost extends BlogPostSummary {
40
40
  oldSlugs: string[];
41
41
  contentHtml: string;
42
- // Markdown source for the admin editor (B3.4). When present, `contentHtml` is
42
+ // Markdown source for the admin editor. When present, `contentHtml` is
43
43
  // its rendered output; migrated/legacy posts may carry only `contentHtml`.
44
44
  bodyMarkdown?: string;
45
45
  }
@@ -55,6 +55,6 @@ export interface AuthorBio {
55
55
  title?: string; // role/title, e.g. "Founder"
56
56
  avatar?: string; // image path (site-relative or absolute)
57
57
  url?: string; // personal/site URL
58
- sameAs?: string[]; // social profile URLs — E-E-A-T signal (feeds Person.sameAs, B1.6)
59
- twitter?: string; // handle, e.g. "@ada" (feeds twitter:creator, B1.6)
58
+ sameAs?: string[]; // social profile URLs — E-E-A-T signal (feeds Person.sameAs)
59
+ twitter?: string; // handle, e.g. "@ada" (feeds twitter:creator)
60
60
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "assignments": {
3
+ "/contact/contact-sales": { "headerVariant": "minimal" },
4
+ "/contact/contact-sales/thank-you": { "headerVariant": "minimal" }
5
+ }
6
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "lang": "en",
3
+ "head": {
4
+ "favicon": "/tenants/tokenoftrust.com/favicon.png",
5
+ "themeStylesheets": ["/tenants/tokenoftrust.com/themes/tot-navy.css"]
6
+ },
7
+ "sharedMarketingCss": true,
8
+ "header": {
9
+ "variant": "primary",
10
+ "brand": {
11
+ "href": "/",
12
+ "label": "Token of Trust",
13
+ "logo": {
14
+ "src": "/tenants/tokenoftrust.com/logo-wordmark.png",
15
+ "alt": "Token of Trust",
16
+ "width": 180,
17
+ "height": 32
18
+ }
19
+ },
20
+ "nav": [
21
+ { "id": "nav-platform", "label": "Platform", "href": "/#platform" },
22
+ { "id": "nav-industries", "label": "Industries", "href": "/#industries" },
23
+ { "id": "nav-resources", "label": "Resources", "href": "https://help.tokenoftrust.com", "external": true },
24
+ { "id": "nav-pricing", "label": "Pricing", "href": "https://tokenoftrust.com/pricing/", "external": true },
25
+ { "id": "nav-login", "label": "Login", "href": "https://app.tokenoftrust.com", "external": true }
26
+ ],
27
+ "ctas": [
28
+ { "id": "cta-book-demo", "label": "Book Demo", "href": "https://tokenoftrust.com/contact/", "variant": "secondary" },
29
+ { "id": "cta-start-free", "label": "Start For Free", "href": "https://app.tokenoftrust.com", "variant": "primary" }
30
+ ]
31
+ },
32
+ "footer": {
33
+ "variant": "default",
34
+ "brand": {
35
+ "text": "Customer verification and compliance for regulated ecommerce. 200+ countries. We will never sell your data."
36
+ },
37
+ "columns": [
38
+ {
39
+ "title": "Platform",
40
+ "links": [
41
+ { "id": "ftr-platform-idv", "label": "Identity Verification", "href": "/#platform" },
42
+ { "id": "ftr-platform-age", "label": "Age Assurance", "href": "/#platform" },
43
+ { "id": "ftr-platform-fraud", "label": "Fraud Prevention", "href": "/#platform" },
44
+ { "id": "ftr-platform-evidence", "label": "Compliance Evidence", "href": "/#platform" },
45
+ { "id": "ftr-platform-aml", "label": "AML · PACT Act · Tax", "href": "/#platform" }
46
+ ]
47
+ },
48
+ {
49
+ "title": "Industries",
50
+ "links": [
51
+ { "id": "ftr-industries-alcohol", "label": "Alcohol", "href": "/#industries" },
52
+ { "id": "ftr-industries-vape", "label": "Tobacco & Vape", "href": "/#industries" },
53
+ { "id": "ftr-industries-age-restricted", "label": "Age-Restricted Goods", "href": "/#industries" },
54
+ { "id": "ftr-industries-marketplaces", "label": "Marketplaces", "href": "/#industries" }
55
+ ]
56
+ },
57
+ {
58
+ "title": "Company",
59
+ "links": [
60
+ { "id": "ftr-company-pricing", "label": "Pricing", "href": "https://tokenoftrust.com/pricing/", "external": true },
61
+ { "id": "ftr-company-contact", "label": "Contact Sales", "href": "https://tokenoftrust.com/contact/", "external": true },
62
+ { "id": "ftr-company-resources", "label": "Resources", "href": "https://help.tokenoftrust.com", "external": true },
63
+ { "id": "ftr-company-login", "label": "Login", "href": "https://app.tokenoftrust.com", "external": true }
64
+ ]
65
+ },
66
+ {
67
+ "title": "Legal",
68
+ "links": [
69
+ { "id": "ftr-legal-privacy", "label": "Privacy Policy", "href": "https://tokenoftrust.com/privacy/", "external": true },
70
+ { "id": "ftr-legal-terms", "label": "Terms", "href": "https://tokenoftrust.com/terms/", "external": true }
71
+ ]
72
+ }
73
+ ],
74
+ "social": [],
75
+ "bottom": {
76
+ "note": "GDPR · CCPA · PIPEDA compliant. 256-bit SSL. We will never sell your data."
77
+ }
78
+ }
79
+ }