@tokenoftrust/storefront-runner 1.4.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (474) hide show
  1. package/apps/storefront/astro.config.mjs +17 -1
  2. package/apps/storefront/env.d.ts +54 -5
  3. package/apps/storefront/integrations/materialize-guard.mjs +54 -0
  4. package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
  5. package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
  6. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  7. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  8. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  9. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  10. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  11. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  12. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  13. package/apps/storefront/migrations-apps/meta/0002_snapshot.json +1160 -0
  14. package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -0
  15. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  16. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  17. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  18. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  19. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  20. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  21. package/apps/storefront/migrations-apps/meta/_journal.json +56 -0
  22. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  23. package/apps/storefront/public/js/dashboard-team.js +38 -2
  24. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  25. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  26. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  27. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  28. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  29. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  30. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  31. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  32. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  33. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  34. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  35. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  36. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  37. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  38. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  39. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  40. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  41. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  42. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  43. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  44. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  45. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  46. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  47. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  48. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  49. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  50. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  51. package/apps/storefront/src/components/Img.astro +1 -1
  52. package/apps/storefront/src/components/Seo.astro +65 -1
  53. package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
  54. package/apps/storefront/src/components/admin/AdminBlogTab.astro +1435 -0
  55. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
  56. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  57. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1980 -2564
  58. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  59. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
  60. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  61. package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
  62. package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
  63. package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
  64. package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
  65. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  66. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  67. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  68. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  69. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  70. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  71. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  72. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  73. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  74. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  75. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  76. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  77. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  78. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  79. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  80. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  81. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  82. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  83. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  84. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  85. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  86. package/apps/storefront/src/config/resolver.ts +4 -4
  87. package/apps/storefront/src/layouts/Layout.astro +146 -21
  88. package/apps/storefront/src/lib/activity/alerts.ts +17 -19
  89. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  90. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +89 -9
  91. package/apps/storefront/src/lib/activity/deployVersion.ts +20 -27
  92. package/apps/storefront/src/lib/activity/ingest.ts +8 -8
  93. package/apps/storefront/src/lib/activity/ingestAuth.ts +1 -1
  94. package/apps/storefront/src/lib/activity/killSwitch.ts +6 -6
  95. package/apps/storefront/src/lib/activity/query.ts +9 -9
  96. package/apps/storefront/src/lib/activity/recordActivity.ts +11 -15
  97. package/apps/storefront/src/lib/activity/store.ts +4 -6
  98. package/apps/storefront/src/lib/activity/uiActor.ts +12 -12
  99. package/apps/storefront/src/lib/activity/workerCommit.ts +6 -6
  100. package/apps/storefront/src/lib/admin/adminShell.ts +292 -0
  101. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
  102. package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
  103. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
  104. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
  105. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  106. package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
  107. package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
  108. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
  109. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
  110. package/apps/storefront/src/lib/apps/adminService.ts +14 -4
  111. package/apps/storefront/src/lib/apps/adminSession.ts +54 -45
  112. package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
  113. package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
  114. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  115. package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
  116. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  117. package/apps/storefront/src/lib/apps/credentials.ts +2 -2
  118. package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
  119. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  120. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  121. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  122. package/apps/storefront/src/lib/apps/healthAggregate.ts +5 -5
  123. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  124. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  125. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  126. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  127. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  128. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  129. package/apps/storefront/src/lib/apps/orders/attributionService.ts +2 -2
  130. package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
  131. package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
  132. package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
  133. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  134. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +9 -1
  135. package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
  136. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +7 -7
  137. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  138. package/apps/storefront/src/lib/apps/registryService.ts +270 -16
  139. package/apps/storefront/src/lib/apps/scopes.ts +2 -2
  140. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  141. package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
  142. package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
  143. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  144. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  145. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  146. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  147. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  148. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  149. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  150. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  151. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  152. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  153. package/apps/storefront/src/lib/apps/widgets/eligibility.ts +2 -2
  154. package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
  155. package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
  156. package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
  157. package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
  158. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  159. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  160. package/apps/storefront/src/lib/auth/adminEntry.ts +11 -11
  161. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  162. package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
  163. package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
  164. package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +2 -4
  165. package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
  166. package/apps/storefront/src/lib/auth/session.ts +7 -8
  167. package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
  168. package/apps/storefront/src/lib/basePath.ts +9 -2
  169. package/apps/storefront/src/lib/blog/access.ts +29 -0
  170. package/apps/storefront/src/lib/blog/collection.ts +400 -0
  171. package/apps/storefront/src/lib/blog/markdown.ts +174 -0
  172. package/apps/storefront/src/lib/blog/provider.ts +62 -3
  173. package/apps/storefront/src/lib/blog/reactions.ts +87 -0
  174. package/apps/storefront/src/lib/blog/rss.ts +92 -0
  175. package/apps/storefront/src/lib/blog/teaser.ts +83 -0
  176. package/apps/storefront/src/lib/blog/types.ts +34 -0
  177. package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
  178. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  179. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  180. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  181. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  182. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  183. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  184. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  185. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  186. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  187. package/apps/storefront/src/lib/content/index.ts +5 -5
  188. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  189. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  190. package/apps/storefront/src/lib/d1/catalog.ts +14 -2
  191. package/apps/storefront/src/lib/d1/schema-apps.ts +372 -17
  192. package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
  193. package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
  194. package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
  195. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  196. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  197. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  198. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  199. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  200. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  201. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  202. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  203. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  204. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  205. package/apps/storefront/src/lib/dev/cockpitStore.ts +1 -9
  206. package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
  207. package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
  208. package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
  209. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  210. package/apps/storefront/src/lib/edgeCache.ts +34 -5
  211. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  212. package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
  213. package/apps/storefront/src/lib/i18n.ts +25 -0
  214. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  215. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  216. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  217. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  218. package/apps/storefront/src/lib/jsonld.ts +118 -0
  219. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  220. package/apps/storefront/src/lib/membership/model.ts +1 -2
  221. package/apps/storefront/src/lib/monitoring/manifest.ts +11 -11
  222. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
  223. package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
  224. package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
  225. package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
  226. package/apps/storefront/src/lib/newsletter/send.ts +265 -0
  227. package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
  228. package/apps/storefront/src/lib/previewReload.ts +57 -1
  229. package/apps/storefront/src/lib/publish/adminPublishTab.ts +4625 -0
  230. package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
  231. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  232. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  233. package/apps/storefront/src/lib/publish/dispatchHealth.ts +5 -7
  234. package/apps/storefront/src/lib/publish/domainState.ts +22 -13
  235. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  236. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  237. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  238. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  239. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  240. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  241. package/apps/storefront/src/lib/publish/panelPolling.ts +78 -0
  242. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  243. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  244. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  245. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  246. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  247. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  248. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  249. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  250. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  251. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  252. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  253. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  254. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  255. package/apps/storefront/src/lib/publish/shipWorkspace.ts +121 -51
  256. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  257. package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -0
  258. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  259. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  260. package/apps/storefront/src/lib/rate/kvWindow.ts +53 -0
  261. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  262. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  263. package/apps/storefront/src/lib/search/index.ts +2 -2
  264. package/apps/storefront/src/lib/seo/alternates.ts +42 -0
  265. package/apps/storefront/src/lib/seo/meta.ts +65 -0
  266. package/apps/storefront/src/lib/social/golive.ts +212 -0
  267. package/apps/storefront/src/lib/social/notify.ts +32 -0
  268. package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
  269. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  270. package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
  271. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  272. package/apps/storefront/src/lib/the-build/provider.ts +32 -0
  273. package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
  274. package/apps/storefront/src/lib/the-build/toc.ts +95 -0
  275. package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
  276. package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
  277. package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
  278. package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
  279. package/apps/storefront/src/middleware/index.ts +385 -52
  280. package/apps/storefront/src/pages/[...slug].astro +9 -4
  281. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  282. package/apps/storefront/src/pages/admin/select.astro +96 -0
  283. package/apps/storefront/src/pages/admin.astro +25 -1313
  284. package/apps/storefront/src/pages/api/activity.ts +8 -8
  285. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  286. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  287. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  288. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  289. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  290. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  291. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  292. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  293. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  294. package/apps/storefront/src/pages/api/blog/[collection]/[slug]/react.ts +104 -0
  295. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  296. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  297. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  298. package/apps/storefront/src/pages/api/newsletter/[collection]/subscribe.ts +146 -0
  299. package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
  300. package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
  301. package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
  302. package/apps/storefront/src/pages/api/rum/vitals.ts +20 -2
  303. package/apps/storefront/src/pages/blog/[slug].astro +99 -19
  304. package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
  305. package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
  306. package/apps/storefront/src/pages/blog/index.astro +4 -2
  307. package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
  308. package/apps/storefront/src/pages/cockpit.astro +77 -1353
  309. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
  310. package/apps/storefront/src/pages/index.astro +10 -5
  311. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  312. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  313. package/apps/storefront/src/pages/search.astro +1 -1
  314. package/apps/storefront/src/pages/sitemap.xml.ts +21 -1
  315. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
  316. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
  317. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  318. package/apps/storefront/src/pages/the-build/[slug].astro +129 -0
  319. package/apps/storefront/src/pages/the-build/index.astro +146 -0
  320. package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
  321. package/apps/storefront/src/styles/admin.css +1106 -0
  322. package/apps/storefront/tsconfig.json +6 -1
  323. package/package.json +1 -1
  324. package/packages/public-runtime/package.json +3 -1
  325. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  326. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  327. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  328. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  329. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  330. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  331. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  332. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  333. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  334. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  335. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  336. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  337. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  338. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  339. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  340. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  341. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  342. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  343. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  344. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  345. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  346. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  347. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  348. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  349. package/packages/public-runtime/src/activity/catalog.ts +89 -33
  350. package/packages/public-runtime/src/activity/event.ts +6 -6
  351. package/packages/public-runtime/src/activity/index.ts +2 -2
  352. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  353. package/packages/public-runtime/src/candidate-index.ts +42 -8
  354. package/packages/public-runtime/src/catalog-d1.ts +1 -1
  355. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  356. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  357. package/packages/public-runtime/src/checkout-style.ts +7 -7
  358. package/packages/public-runtime/src/checkout.ts +3 -3
  359. package/packages/public-runtime/src/chrome.ts +777 -0
  360. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  361. package/packages/public-runtime/src/csp.ts +99 -0
  362. package/packages/public-runtime/src/customization-preview.ts +25 -2
  363. package/packages/public-runtime/src/customization-runtime.ts +156 -3
  364. package/packages/public-runtime/src/customization-versioning.ts +10 -0
  365. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  366. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  367. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  368. package/packages/public-runtime/src/evidence.ts +288 -0
  369. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  370. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  371. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  372. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  373. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  374. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  375. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  376. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  377. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  378. package/packages/public-runtime/src/index.ts +17 -1
  379. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  380. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  381. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  382. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  383. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  384. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  385. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  386. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  387. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  388. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  389. package/packages/public-runtime/src/integration/csp.ts +105 -0
  390. package/packages/public-runtime/src/integration/digest.ts +43 -0
  391. package/packages/public-runtime/src/integration/environment.ts +228 -0
  392. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  393. package/packages/public-runtime/src/integration/events.ts +701 -0
  394. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  395. package/packages/public-runtime/src/integration/index.ts +90 -0
  396. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  397. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  398. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  399. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  400. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  401. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  402. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  403. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  404. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  405. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  406. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  407. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  408. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  409. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  410. package/packages/public-runtime/src/integration/observability.ts +451 -0
  411. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  412. package/packages/public-runtime/src/integration/phases.ts +182 -0
  413. package/packages/public-runtime/src/integration/plan.ts +112 -0
  414. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  415. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  416. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  417. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  418. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  419. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  420. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  421. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  422. package/packages/public-runtime/src/integration/result.ts +239 -0
  423. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  424. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  425. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  426. package/packages/public-runtime/src/integration/version.ts +92 -0
  427. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  428. package/packages/public-runtime/src/membership.ts +2 -2
  429. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  430. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  431. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  432. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  433. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  434. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  435. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  436. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  437. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  438. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  439. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  440. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  441. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  442. package/packages/public-runtime/src/static-bundle.ts +357 -0
  443. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  444. package/packages/public-runtime/src/tenant.ts +81 -17
  445. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  446. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  447. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  448. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  449. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  450. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  451. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  452. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  453. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  454. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  455. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  456. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  457. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  458. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  459. package/scripts/build/copy-tenant-assets.mjs +14 -4
  460. package/scripts/dev/ai-edit.mjs +1 -1
  461. package/scripts/dev/checkout-watch.mjs +2 -2
  462. package/scripts/dev/file-browser.mjs +4 -6
  463. package/scripts/dev/file-writer.mjs +3 -3
  464. package/scripts/dev/git-status.mjs +2 -2
  465. package/scripts/dev/integration-guardrails.mjs +498 -0
  466. package/scripts/dev/locate-handler.mjs +3 -3
  467. package/scripts/dev/port-check.mjs +3 -3
  468. package/scripts/dev/publish.mjs +5 -9
  469. package/scripts/dev/safe-path.mjs +2 -2
  470. package/scripts/dev/shot.mjs +1 -1
  471. package/scripts/dev/tenant-source-root.mjs +1 -1
  472. package/scripts/dev/unified-diff.mjs +8 -3
  473. package/scripts/tot-dev.mjs +8 -11
  474. package/apps/storefront/src/lib/dev/vcBinding.ts +0 -80
@@ -0,0 +1,87 @@
1
+ /**
2
+ * E1.1 — per-post reader reactions: an anonymous, additive emoji tally, the
3
+ * first reader-sentiment signal for the blog platform (complements view counts,
4
+ * which measure reads). A near-clone of `viewCounts.ts`: KV counters keyed by
5
+ * (tenant, collection, slug, reaction), best-effort read-modify-write, no PII,
6
+ * no per-visitor identity, no moderation (an emoji count carries no user text —
7
+ * worst case is an inflated tally, self-limiting and low-stakes).
8
+ *
9
+ * Storage is the TENANT_CACHE KV namespace (same binding view-counts uses).
10
+ * Under concurrency some increments are lost to KV's last-write-wins semantics:
11
+ * acceptable for a demand signal, not a billing counter.
12
+ *
13
+ * Tenant/collection-generic by construction — nothing here is bound to a
14
+ * specific tenant or collection.
15
+ */
16
+ import { getPost } from "./collection.js";
17
+
18
+ /** The fixed reaction vocabulary. Not free-form — a closed set like the pillar vocabulary. */
19
+ export const REACTIONS = ["like", "insightful", "celebrate"] as const;
20
+ export type Reaction = (typeof REACTIONS)[number];
21
+
22
+ /** Narrow an untrusted string to a known reaction, or null. */
23
+ export function isReaction(value: unknown): value is Reaction {
24
+ return typeof value === "string" && (REACTIONS as readonly string[]).includes(value);
25
+ }
26
+
27
+ /** KV key for one (post, reaction) rolling tally. */
28
+ export function reactionsKey(
29
+ tenantId: string,
30
+ collection: string,
31
+ slug: string,
32
+ reaction: Reaction,
33
+ ): string {
34
+ return `blogreact:${tenantId}:${collection}:${slug}:${reaction}`;
35
+ }
36
+
37
+ async function readCount(kv: KVNamespace, key: string): Promise<number> {
38
+ const raw = await kv.get(key);
39
+ const n = raw == null ? 0 : Number.parseInt(raw, 10);
40
+ return Number.isFinite(n) && n >= 0 ? n : 0;
41
+ }
42
+
43
+ /**
44
+ * Increment the tally for `reaction` on a post IF the reaction is in the vocab
45
+ * AND the post is live (published/unlisted/due-scheduled) for this tenant —
46
+ * `getPost` returns null on the live path for a draft/404/non-post. Best-effort:
47
+ * never throws (a reaction must never fail the reader's request).
48
+ */
49
+ export async function recordReaction(
50
+ kv: KVNamespace,
51
+ tenantId: string,
52
+ collection: string,
53
+ slug: string,
54
+ reaction: string,
55
+ ): Promise<void> {
56
+ try {
57
+ if (!isReaction(reaction)) return;
58
+ const post = await getPost(tenantId, collection, slug);
59
+ if (!post) return; // not a live post (draft / 404 / non-blog) — do not count
60
+ const key = reactionsKey(tenantId, collection, slug, reaction);
61
+ const next = (await readCount(kv, key)) + 1;
62
+ await kv.put(key, String(next));
63
+ } catch {
64
+ /* swallow — reactions are best-effort */
65
+ }
66
+ }
67
+
68
+ /**
69
+ * The full tally map for a post: every vocab reaction to its count (0 if unset).
70
+ * A null KV (local dev, no binding) yields all-zero counts. Read by the reader
71
+ * reaction bar and the admin engagement panel (E1.2).
72
+ */
73
+ export async function getReactionCounts(
74
+ kv: KVNamespace | undefined,
75
+ tenantId: string,
76
+ collection: string,
77
+ slug: string,
78
+ ): Promise<Record<Reaction, number>> {
79
+ const out = Object.fromEntries(REACTIONS.map((r) => [r, 0])) as Record<Reaction, number>;
80
+ if (!kv) return out;
81
+ await Promise.all(
82
+ REACTIONS.map(async (reaction) => {
83
+ out[reaction] = await readCount(kv, reactionsKey(tenantId, collection, slug, reaction));
84
+ }),
85
+ );
86
+ return out;
87
+ }
@@ -0,0 +1,92 @@
1
+ import type { BlogPostSummary } from "./types.js";
2
+
3
+ /**
4
+ * Hand-rolled RSS 2.0 renderer (no `@astrojs/rss` dependency), the syndication
5
+ * mirror of `sitemap.xml.ts`. Tenant- and collection-agnostic: the caller passes
6
+ * the resolved canonical base + the collection's route base, so the SAME renderer
7
+ * serves `/the-build/rss.xml`, `/blog/rss.xml`, and any tenant. Feeding it the
8
+ * engine's live-visible list (`listPosts` default) inherits the visibility
9
+ * chokepoint — drafts/unlisted never reach a feed.
10
+ */
11
+ export interface RssFeedInput {
12
+ channelTitle: string;
13
+ channelDescription: string;
14
+ /** Resolved canonical base, e.g. `https://x.com` or a path-prefixed preview host. */
15
+ canonicalBase: string;
16
+ /** The collection's route base, e.g. `/the-build`. */
17
+ collectionBase: string;
18
+ /**
19
+ * Feed items. `BlogPost` is assignable here (it extends `BlogPostSummary`),
20
+ * so an item may carry a full `contentHtml` body — which is emitted as
21
+ * `<content:encoded>` for public posts only. Gated posts stay in the feed but
22
+ * never syndicate their body (see the per-item guard below).
23
+ */
24
+ posts: Array<BlogPostSummary & { contentHtml?: string }>;
25
+ }
26
+
27
+ /** XML-escape text inside a tag (mirrors sitemap.xml.ts). */
28
+ export function escapeXml(value: string): string {
29
+ return value
30
+ .replace(/&/g, "&amp;")
31
+ .replace(/</g, "&lt;")
32
+ .replace(/>/g, "&gt;")
33
+ .replace(/"/g, "&quot;")
34
+ .replace(/'/g, "&apos;");
35
+ }
36
+
37
+ /** RFC-822 date (`Sun, 01 Mar 2026 00:00:00 GMT`), the RSS `pubDate` format. */
38
+ function rfc822(iso: string): string {
39
+ const d = new Date(iso);
40
+ return Number.isNaN(d.getTime()) ? "" : d.toUTCString();
41
+ }
42
+
43
+ export function renderRssFeed(input: RssFeedInput): string {
44
+ const { channelTitle, channelDescription, posts } = input;
45
+ const base = input.canonicalBase.replace(/\/$/, "");
46
+ const collectionBase = `/${input.collectionBase.replace(/^\/+|\/+$/g, "")}`;
47
+ const channelLink = `${base}${collectionBase}/`;
48
+ const feedLink = `${base}${collectionBase}/rss.xml`;
49
+
50
+ const items = posts
51
+ .map((post) => {
52
+ const link = `${base}${collectionBase}/${post.slug}/`;
53
+ const pubDate = rfc822(post.publishedAt);
54
+ const categories = post.categories
55
+ .map((c) => ` <category>${escapeXml(c)}</category>`)
56
+ .join("\n");
57
+ // Entitlement guard: a gated post (access !== "public") stays discoverable
58
+ // in the feed but NEVER syndicates its body — otherwise a feed reader would
59
+ // unlock paywalled content. Only public posts emit <content:encoded>.
60
+ const gated = (post.access ?? "public") !== "public";
61
+ const contentEncoded =
62
+ !gated && post.contentHtml
63
+ ? ` <content:encoded><![CDATA[${post.contentHtml}]]></content:encoded>`
64
+ : "";
65
+ return [
66
+ " <item>",
67
+ ` <title>${escapeXml(post.title)}</title>`,
68
+ ` <link>${escapeXml(link)}</link>`,
69
+ ` <guid isPermaLink="true">${escapeXml(link)}</guid>`,
70
+ pubDate ? ` <pubDate>${pubDate}</pubDate>` : "",
71
+ ` <dc:creator>${escapeXml(post.author)}</dc:creator>`,
72
+ ` <description>${escapeXml(post.excerpt)}</description>`,
73
+ contentEncoded,
74
+ categories,
75
+ ]
76
+ .filter(Boolean)
77
+ .join("\n");
78
+ })
79
+ .join("\n");
80
+
81
+ return `<?xml version="1.0" encoding="UTF-8"?>
82
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
83
+ <channel>
84
+ <title>${escapeXml(channelTitle)}</title>
85
+ <link>${escapeXml(channelLink)}</link>
86
+ <atom:link href="${escapeXml(feedLink)}" rel="self" type="application/rss+xml" />
87
+ <description>${escapeXml(channelDescription)}</description>
88
+ ${items}
89
+ </channel>
90
+ </rss>
91
+ `;
92
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Teaser truncation for gated posts. A members-only post is still a 200 with
3
+ * partial content (indexable — see the B4.0 spike's SEO note), so an anonymous /
4
+ * unentitled reader gets the first few blocks of the real body plus a paywall
5
+ * card, not a hard wall.
6
+ *
7
+ * Truncation is by TOP-LEVEL BLOCK COUNT, cutting only on an element boundary so
8
+ * the returned string is never a partial tag. The blog renderer emits each block
9
+ * (`<p>`, `<h2>`, `<ul>`, `<blockquote>`, `<pre>`, …) as a top-level sibling, so
10
+ * "first N blocks" is a clean prefix of the rendered HTML; the boundary scan also
11
+ * tolerates legacy/migrated `contentHtml` from other sources.
12
+ */
13
+
14
+ const VOID_TAGS = new Set([
15
+ "area", "base", "br", "col", "embed", "hr", "img", "input",
16
+ "link", "meta", "param", "source", "track", "wbr",
17
+ ]);
18
+
19
+ /** Default teaser length: enough to be a genuine content sample, not the whole story. */
20
+ const DEFAULT_BLOCKS = 3;
21
+
22
+ /**
23
+ * The first `blocks` top-level block elements of `contentHtml`. Returns the whole
24
+ * string when it has fewer than `blocks` top-level elements. Cuts only after a
25
+ * top-level element closes (or a void/self-closing element), so HTML is never
26
+ * severed mid-tag.
27
+ */
28
+ export function teaserHtml(contentHtml: string, blocks: number = DEFAULT_BLOCKS): string {
29
+ if (blocks <= 0) return "";
30
+ const n = contentHtml.length;
31
+ let i = 0;
32
+ let depth = 0;
33
+ let topLevel = 0;
34
+
35
+ while (i < n) {
36
+ if (contentHtml[i] !== "<") {
37
+ i++;
38
+ continue;
39
+ }
40
+ if (contentHtml.startsWith("<!--", i)) {
41
+ const end = contentHtml.indexOf("-->", i + 4);
42
+ i = end === -1 ? n : end + 3;
43
+ continue;
44
+ }
45
+ const gt = contentHtml.indexOf(">", i);
46
+ if (gt === -1) break; // malformed tail — stop rather than emit a partial tag
47
+ const tag = contentHtml.slice(i, gt + 1);
48
+ i = gt + 1;
49
+
50
+ const closing = tag.startsWith("</");
51
+ const selfClosing = tag.endsWith("/>");
52
+ const name = tag.match(/^<\/?\s*([a-zA-Z][a-zA-Z0-9-]*)/)?.[1]?.toLowerCase() ?? "";
53
+
54
+ if (closing) {
55
+ if (depth > 0) depth--;
56
+ if (depth === 0) {
57
+ topLevel++;
58
+ if (topLevel >= blocks) return contentHtml.slice(0, i);
59
+ }
60
+ } else if (selfClosing || VOID_TAGS.has(name)) {
61
+ if (depth === 0) {
62
+ topLevel++;
63
+ if (topLevel >= blocks) return contentHtml.slice(0, i);
64
+ }
65
+ } else {
66
+ depth++;
67
+ }
68
+ }
69
+ return contentHtml;
70
+ }
71
+
72
+ /**
73
+ * The body HTML to render for an article: full content when the viewer is
74
+ * entitled (or the post is ungated), a teaser otherwise. The single place the
75
+ * gate/teaser branch lives, so the public/full-content path stays provably
76
+ * unchanged — `articleBodyHtml(html, { gated: false, ... }) === html`.
77
+ */
78
+ export function articleBodyHtml(
79
+ contentHtml: string,
80
+ opts: { gated: boolean; unlock: boolean },
81
+ ): string {
82
+ return opts.gated && !opts.unlock ? teaserHtml(contentHtml) : contentHtml;
83
+ }
@@ -5,6 +5,16 @@ export interface BlogImage {
5
5
  height?: number;
6
6
  }
7
7
 
8
+ export type PostStatus = "draft" | "published" | "unlisted" | "scheduled";
9
+
10
+ /**
11
+ * Per-post entitlement gate — who may read the body. Orthogonal to `PostStatus`
12
+ * (which answers "is this published to the live site"): a post has both. Checked
13
+ * at render time via `viewerHasBlogAccess`, never folded into `isVisibleOnLive`.
14
+ * Room for future `"tier:<id>"` / `"assurance:<acr>"`; only these three today.
15
+ */
16
+ export type BlogAccess = "public" | "authenticated" | "members";
17
+
8
18
  export interface BlogPostSummary {
9
19
  source: { platform: "wordpress"; id: string };
10
20
  slug: string;
@@ -18,9 +28,33 @@ export interface BlogPostSummary {
18
28
  categories: string[];
19
29
  tags: string[];
20
30
  featuredImage?: BlogImage;
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
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
21
37
  }
22
38
 
23
39
  export interface BlogPost extends BlogPostSummary {
24
40
  oldSlugs: string[];
25
41
  contentHtml: string;
42
+ // Markdown source for the admin editor. When present, `contentHtml` is
43
+ // its rendered output; migrated/legacy posts may carry only `contentHtml`.
44
+ bodyMarkdown?: string;
45
+ }
46
+
47
+ /**
48
+ * Optional per-tenant × per-collection author bios, keyed by the author's slug
49
+ * (see `authorSlug`). Sourced from `tenants/<id>/content/<collection>/authors.json`;
50
+ * absent file or missing key falls back to just the byline name.
51
+ */
52
+ export interface AuthorBio {
53
+ name: string;
54
+ bio?: string;
55
+ title?: string; // role/title, e.g. "Founder"
56
+ avatar?: string; // image path (site-relative or absolute)
57
+ url?: string; // personal/site URL
58
+ sameAs?: string[]; // social profile URLs — E-E-A-T signal (feeds Person.sameAs)
59
+ twitter?: string; // handle, e.g. "@ada" (feeds twitter:creator)
26
60
  }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Per-post view counts: the "first demand signal" for the blog platform.
3
+ *
4
+ * Rides the EXISTING RUM beacon (`api/rum/vitals.ts`) rather than adding a new
5
+ * ingestion path. Each page load emits exactly one CLS field-vitals sample (LCP
6
+ * and INP are conditional), so the beacon handler treats that CLS sample as the
7
+ * one-per-view signal and increments counters when it lands on a blog-post URL.
8
+ * All counts are keyed by (tenant, collection, slug) — no PII, no per-visitor
9
+ * identity.
10
+ *
11
+ * Two counters per view, written together:
12
+ * - a rolling LIFETIME total (`blogViewsKey`), unbounded; and
13
+ * - a per-UTC-day BUCKET (`blogViewsDayKey`, TTL-expiring) that powers the
14
+ * time-series sparkline and the "views over last N days" top-posts ranking.
15
+ * The day buckets are additive to the lifetime total, not a replacement.
16
+ *
17
+ * Storage is the TENANT_CACHE KV namespace — the same binding the newsletter stub
18
+ * writes to and the only durable store bound on the live `preview` worker. The
19
+ * increment is a read-modify-write, so under concurrency some increments can be
20
+ * lost to KV's last-write-wins semantics: acceptable for a rough demand signal,
21
+ * not a billing counter.
22
+ *
23
+ * Tenant/collection-generic by construction — nothing here is bound to
24
+ * `the-build` or `tokenoftrust.com`.
25
+ */
26
+ import { getPost, listPosts } from "./collection.js";
27
+
28
+ // Mirror the engine's guards (collection.ts): a collection is one path segment;
29
+ // a slug is lower-kebab. Keeping them local avoids widening the engine's surface.
30
+ const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
31
+ const SEGMENT_RE = /^[a-z0-9][a-z0-9._-]*$/;
32
+
33
+ // Daily buckets auto-expire this many days after their last write. Comfortably
34
+ // past the widest window the dashboard reads (30d), so a "last N days" query
35
+ // never asks for a key that should still exist but was reaped.
36
+ const DAY_BUCKET_TTL_DAYS = 45;
37
+ const DAY_MS = 86_400_000;
38
+
39
+ /** KV key for one post's rolling lifetime view count. */
40
+ export function blogViewsKey(tenantId: string, collection: string, slug: string): string {
41
+ return `blogviews:${tenantId}:${collection}:${slug}`;
42
+ }
43
+
44
+ /** `yyyy-mm-dd` (UTC) stamp for a Date — the day-bucket suffix. */
45
+ export function dayStamp(date: Date): string {
46
+ return date.toISOString().slice(0, 10);
47
+ }
48
+
49
+ /**
50
+ * KV key for one post's views on ONE UTC day. Distinct from `blogViewsKey` by
51
+ * the trailing `:<yyyy-mm-dd>` segment, so the lifetime counter and the daily
52
+ * buckets never collide.
53
+ */
54
+ export function blogViewsDayKey(
55
+ tenantId: string,
56
+ collection: string,
57
+ slug: string,
58
+ day: string,
59
+ ): string {
60
+ return `${blogViewsKey(tenantId, collection, slug)}:${day}`;
61
+ }
62
+
63
+ /**
64
+ * Parse a request pathname into a candidate (collection, slug) post ref, or null
65
+ * if it is not shaped like a post URL. A post lives at `<base>/<slug>/`, so the
66
+ * LAST two non-empty path segments are the candidate collection + slug. Taking
67
+ * the last two (not the first) naturally ignores any `/<domain>` basePath prefix
68
+ * (DNS-free preview routing) and rejects listing/section pages (`/the-build/`,
69
+ * `/the-build/category/x/`, `/the-build/author/x/`) and file routes
70
+ * (`/the-build/rss.xml`, whose slug fails `SLUG_RE`) — those either fail the
71
+ * guards or resolve to no post. This is only a SHAPE guard; the caller confirms
72
+ * the post actually exists and is live via `getPost`.
73
+ */
74
+ export function parseBlogPostPath(path: string): { collection: string; slug: string } | null {
75
+ const segs = path.split("/").filter(Boolean);
76
+ if (segs.length < 2) return null;
77
+ const slug = segs[segs.length - 1]!;
78
+ const collection = segs[segs.length - 2]!;
79
+ if (!SEGMENT_RE.test(collection) || !SLUG_RE.test(slug)) return null;
80
+ return { collection, slug };
81
+ }
82
+
83
+ async function readCount(kv: KVNamespace, key: string): Promise<number> {
84
+ const raw = await kv.get(key);
85
+ const n = raw == null ? 0 : Number.parseInt(raw, 10);
86
+ return Number.isFinite(n) && n >= 0 ? n : 0;
87
+ }
88
+
89
+ /**
90
+ * Increment the view counter for a post IF the pathname resolves to a live
91
+ * (published or unlisted) post for this tenant. Drafts, 404s, and non-blog paths
92
+ * are ignored — `getPost` returns null on the live path for anything not visible.
93
+ * Best-effort: never throws (a telemetry beacon must never fail the request).
94
+ */
95
+ export async function recordBlogView(
96
+ kv: KVNamespace,
97
+ tenantId: string,
98
+ path: string,
99
+ ): Promise<void> {
100
+ try {
101
+ const ref = parseBlogPostPath(path);
102
+ if (!ref) return;
103
+ const post = await getPost(tenantId, ref.collection, ref.slug);
104
+ if (!post) return; // not a live post (draft / 404 / non-blog) — do not count
105
+ const key = blogViewsKey(tenantId, ref.collection, ref.slug);
106
+ const dayKey = blogViewsDayKey(tenantId, ref.collection, ref.slug, dayStamp(new Date()));
107
+ const [lifetime, today] = await Promise.all([readCount(kv, key), readCount(kv, dayKey)]);
108
+ await Promise.all([
109
+ // Rolling lifetime counter — unbounded.
110
+ kv.put(key, String(lifetime + 1)),
111
+ // Additive daily bucket for the time-series/top-posts dashboard. TTL keeps
112
+ // the day-bucket keyspace bounded (a day is only ever written on that day,
113
+ // so it expires ~45d after it stops receiving writes).
114
+ kv.put(dayKey, String(today + 1), { expirationTtl: (DAY_BUCKET_TTL_DAYS * DAY_MS) / 1000 }),
115
+ ]);
116
+ } catch {
117
+ /* swallow — view counting is best-effort telemetry */
118
+ }
119
+ }
120
+
121
+ /**
122
+ * READER for the future admin "Blog" tab (B2.1, not yet in this base branch).
123
+ * The tab is server-rendered with `Astro.locals.tenant` and can obtain the KV via
124
+ * `readKv("TENANT_CACHE")`, so it calls this directly with the slugs from
125
+ * `listPosts(tenantId, collection, { includeUnpublished: true })` to render a
126
+ * "Views" column. Returns a slug -> count map (missing keys read as 0); a null KV
127
+ * (local dev, no binding) yields all-zero counts.
128
+ */
129
+ export async function getBlogViewCounts(
130
+ kv: KVNamespace | undefined,
131
+ tenantId: string,
132
+ collection: string,
133
+ slugs: readonly string[],
134
+ ): Promise<Record<string, number>> {
135
+ const out: Record<string, number> = {};
136
+ for (const slug of slugs) out[slug] = 0;
137
+ if (!kv) return out;
138
+ await Promise.all(
139
+ slugs.map(async (slug) => {
140
+ out[slug] = await readCount(kv, blogViewsKey(tenantId, collection, slug));
141
+ }),
142
+ );
143
+ return out;
144
+ }
145
+
146
+ /** The UTC day stamps for the last `days` days, oldest first (today is last). */
147
+ export function recentDayStamps(days: number, now: Date = new Date()): string[] {
148
+ const n = Math.max(1, Math.floor(days));
149
+ const end = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
150
+ const out: string[] = [];
151
+ for (let i = n - 1; i >= 0; i--) out.push(dayStamp(new Date(end - i * DAY_MS)));
152
+ return out;
153
+ }
154
+
155
+ /** One day of a post's view series. */
156
+ export interface DailyViews {
157
+ day: string;
158
+ views: number;
159
+ }
160
+
161
+ /**
162
+ * Per-day view series for one post over the last `days` days (oldest first),
163
+ * for a sparkline/trend. Missing days read as 0; a null KV yields an all-zero
164
+ * series of the right length.
165
+ */
166
+ export async function getPostViewSeries(
167
+ kv: KVNamespace | undefined,
168
+ tenantId: string,
169
+ collection: string,
170
+ slug: string,
171
+ days: number,
172
+ now: Date = new Date(),
173
+ ): Promise<DailyViews[]> {
174
+ const stamps = recentDayStamps(days, now);
175
+ if (!kv) return stamps.map((day) => ({ day, views: 0 }));
176
+ return Promise.all(
177
+ stamps.map(async (day) => ({
178
+ day,
179
+ views: await readCount(kv, blogViewsDayKey(tenantId, collection, slug, day)),
180
+ })),
181
+ );
182
+ }
183
+
184
+ /** One ranked row in the top-posts dashboard. */
185
+ export interface TopPost {
186
+ collection: string;
187
+ slug: string;
188
+ title: string;
189
+ status: string;
190
+ /** Views within the window (`sinceDays`), or lifetime when no window given. */
191
+ views: number;
192
+ /** Lifetime total, always populated. */
193
+ lifetime: number;
194
+ /** Per-day series over the window, oldest first — omitted when unwindowed. */
195
+ series?: DailyViews[];
196
+ }
197
+
198
+ /**
199
+ * Rank a collection's posts by view count, most-viewed first.
200
+ *
201
+ * KV has no cross-key aggregation, so we take the simplest correct approach:
202
+ * enumerate the known posts via `listPosts` and read each one's counter(s). At
203
+ * blog scale (tens of posts) this is a bounded fan-out of parallel KV gets, not
204
+ * a scan — fine for an admin-tab render, and it needs no write-time index to
205
+ * keep in sync. `opts.sinceDays` ranks by a windowed sum of the daily buckets
206
+ * (and returns the series for a sparkline); without it, ranking is by lifetime.
207
+ */
208
+ export async function getTopPosts(
209
+ kv: KVNamespace | undefined,
210
+ tenantId: string,
211
+ collection: string,
212
+ opts?: { limit?: number; sinceDays?: number; now?: Date },
213
+ ): Promise<TopPost[]> {
214
+ const posts = await listPosts(tenantId, collection, { includeUnpublished: true });
215
+ const now = opts?.now ?? new Date();
216
+ const rows = await Promise.all(
217
+ posts.map(async (post) => {
218
+ const lifetime = kv ? await readCount(kv, blogViewsKey(tenantId, collection, post.slug)) : 0;
219
+ if (opts?.sinceDays == null) {
220
+ return {
221
+ collection,
222
+ slug: post.slug,
223
+ title: post.title,
224
+ status: post.status,
225
+ views: lifetime,
226
+ lifetime,
227
+ } satisfies TopPost;
228
+ }
229
+ const series = await getPostViewSeries(
230
+ kv,
231
+ tenantId,
232
+ collection,
233
+ post.slug,
234
+ opts.sinceDays,
235
+ now,
236
+ );
237
+ const views = series.reduce((sum, d) => sum + d.views, 0);
238
+ return { collection, slug: post.slug, title: post.title, status: post.status, views, lifetime, series } satisfies TopPost;
239
+ }),
240
+ );
241
+ rows.sort((a, b) => b.views - a.views || b.lifetime - a.lifetime);
242
+ return opts?.limit != null ? rows.slice(0, Math.max(0, opts.limit)) : rows;
243
+ }
@@ -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
+ }