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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/env.d.ts +54 -5
  3. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  4. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  5. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  6. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  7. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  8. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  9. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  10. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  11. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  12. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  13. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  14. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  15. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  16. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  17. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  18. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  19. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  20. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  22. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  23. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  24. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  25. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  26. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  27. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  28. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  29. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  30. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  32. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  33. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  34. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  37. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  38. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  41. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  42. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  43. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  44. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  45. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  46. package/apps/storefront/src/components/Img.astro +1 -1
  47. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  48. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  49. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  50. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  51. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  52. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  53. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  54. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  55. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  56. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  57. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  59. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  60. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  61. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  62. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  63. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  64. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  65. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  66. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  67. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  68. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  69. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  70. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  71. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  72. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  73. package/apps/storefront/src/config/resolver.ts +4 -4
  74. package/apps/storefront/src/layouts/Layout.astro +100 -20
  75. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  76. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  77. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  78. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  79. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  80. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  81. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  82. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  83. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  84. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  87. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  89. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  90. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  91. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  92. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  93. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  94. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  95. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  96. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  97. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  98. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  99. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  100. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  104. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  105. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  106. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  109. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  110. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  111. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  112. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  113. package/apps/storefront/src/lib/blog/collection.ts +97 -10
  114. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  115. package/apps/storefront/src/lib/blog/provider.ts +57 -3
  116. package/apps/storefront/src/lib/blog/types.ts +9 -9
  117. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  119. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  120. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  121. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  122. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  123. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  124. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  126. package/apps/storefront/src/lib/content/index.ts +5 -5
  127. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  128. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  129. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  130. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  131. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  132. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  133. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  134. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  135. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  136. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  137. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  138. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  139. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  140. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  141. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  142. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  143. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  144. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  145. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  146. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  147. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  148. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  149. package/apps/storefront/src/lib/membership/model.ts +1 -2
  150. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  151. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  153. package/apps/storefront/src/lib/previewReload.ts +57 -1
  154. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  155. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  156. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  157. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  158. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  159. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  160. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  161. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  162. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  163. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  164. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  165. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  166. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  173. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  176. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  177. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  178. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  179. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  180. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  181. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  182. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  183. package/apps/storefront/src/lib/social/notify.ts +4 -4
  184. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  185. package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
  186. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  187. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  188. package/apps/storefront/src/middleware/index.ts +273 -51
  189. package/apps/storefront/src/pages/[...slug].astro +9 -4
  190. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  191. package/apps/storefront/src/pages/admin/select.astro +96 -0
  192. package/apps/storefront/src/pages/admin.astro +16 -2
  193. package/apps/storefront/src/pages/api/activity.ts +8 -8
  194. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  195. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  196. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  197. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  199. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  201. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  202. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  203. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  204. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  205. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  206. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  207. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  208. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  209. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  210. package/apps/storefront/src/pages/blog/index.astro +2 -2
  211. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  212. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  213. package/apps/storefront/src/pages/index.astro +10 -5
  214. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  215. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  216. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  217. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
  218. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
  219. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  220. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  221. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  222. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  223. package/apps/storefront/src/styles/admin.css +27 -1
  224. package/apps/storefront/tsconfig.json +1 -1
  225. package/package.json +1 -1
  226. package/packages/public-runtime/package.json +2 -1
  227. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  228. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  229. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  230. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  231. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  232. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  250. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  251. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  252. package/packages/public-runtime/src/activity/event.ts +6 -6
  253. package/packages/public-runtime/src/activity/index.ts +2 -2
  254. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  255. package/packages/public-runtime/src/candidate-index.ts +42 -8
  256. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  257. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  258. package/packages/public-runtime/src/checkout-style.ts +7 -7
  259. package/packages/public-runtime/src/checkout.ts +3 -3
  260. package/packages/public-runtime/src/chrome.ts +777 -0
  261. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  262. package/packages/public-runtime/src/csp.ts +99 -0
  263. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  264. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  265. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  266. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  267. package/packages/public-runtime/src/evidence.ts +288 -0
  268. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  269. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  270. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  271. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  272. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  273. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  274. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  275. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  276. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  277. package/packages/public-runtime/src/index.ts +16 -1
  278. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  279. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  280. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  281. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  282. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  283. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  284. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  285. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  286. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  287. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  288. package/packages/public-runtime/src/integration/csp.ts +105 -0
  289. package/packages/public-runtime/src/integration/digest.ts +43 -0
  290. package/packages/public-runtime/src/integration/environment.ts +228 -0
  291. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  292. package/packages/public-runtime/src/integration/events.ts +701 -0
  293. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  294. package/packages/public-runtime/src/integration/index.ts +90 -0
  295. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  296. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  297. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  298. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  299. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  300. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  301. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  302. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  303. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  305. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  306. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  307. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  308. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  309. package/packages/public-runtime/src/integration/observability.ts +451 -0
  310. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  311. package/packages/public-runtime/src/integration/phases.ts +182 -0
  312. package/packages/public-runtime/src/integration/plan.ts +112 -0
  313. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  314. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  315. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  316. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  317. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  318. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  319. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  320. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  321. package/packages/public-runtime/src/integration/result.ts +239 -0
  322. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  323. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  324. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  325. package/packages/public-runtime/src/integration/version.ts +92 -0
  326. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  327. package/packages/public-runtime/src/membership.ts +2 -2
  328. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  329. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  330. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  331. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  332. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  333. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  334. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  335. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  336. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  337. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  338. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  339. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  340. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  341. package/packages/public-runtime/src/static-bundle.ts +5 -5
  342. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  343. package/packages/public-runtime/src/tenant.ts +45 -9
  344. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  345. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  346. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  347. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  348. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  349. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  350. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  351. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  352. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  353. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  354. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  355. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  356. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  357. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  358. package/scripts/dev/file-browser.mjs +2 -2
  359. package/scripts/dev/file-writer.mjs +2 -2
  360. package/scripts/dev/integration-guardrails.mjs +498 -0
  361. package/scripts/dev/safe-path.mjs +2 -2
  362. package/scripts/dev/tenant-source-root.mjs +1 -1
  363. package/scripts/tot-dev.mjs +2 -2
@@ -18,6 +18,7 @@ import { DEFAULT_APP_GATEWAY_KID, APP_GATEWAY_KID_ENV } from "./gatewayKeys.js";
18
18
  import { fromD1 } from "../d1/catalog.js";
19
19
  import { readD1, readEnv } from "../env.js";
20
20
  import { MANAGE_ROLES, type AdminAuthResult } from "./adminSession.js";
21
+ import { ManagedAnalyticsApiV1Facade } from "./managedAnalyticsService.js";
21
22
 
22
23
  /**
23
24
  * No-store JSON response — install/rotate bodies carry a one-time client
@@ -44,6 +45,13 @@ export async function getRegistryService(): Promise<AppRegistryService | Respons
44
45
  return new AppRegistryService(db, credentials);
45
46
  }
46
47
 
48
+ /** Build the managed-analytics v1 service over the same tenant control-plane D1. */
49
+ export async function getManagedAnalyticsService(): Promise<ManagedAnalyticsApiV1Facade | Response> {
50
+ const d1 = await readD1("STOREFRONT_APPS_DB");
51
+ if (!d1) return json({ error: "app registry not configured" }, 503);
52
+ return new ManagedAnalyticsApiV1Facade(fromD1(d1));
53
+ }
54
+
47
55
  /** HTTP status per `RegistryError` code — the one shared fail-closed mapping. */
48
56
  const STATUS_BY_REGISTRY_CODE: Record<RegistryErrorCode, number> = {
49
57
  scope_denied: 400,
@@ -52,6 +60,8 @@ const STATUS_BY_REGISTRY_CODE: Record<RegistryErrorCode, number> = {
52
60
  invalid_state: 409,
53
61
  invalid_webhook_endpoint: 400,
54
62
  invalid_widget_placement: 400,
63
+ unknown_version: 404,
64
+ invalid_version_facets: 400,
55
65
  };
56
66
 
57
67
  /** Map a `RegistryError` to its HTTP status. Never echoes internals — code + message only. */
@@ -27,7 +27,7 @@
27
27
  * DEDICATED `APPS_ADMIN_SECRET` only — unlike grants' session, this does
28
28
  * NOT fall back to the shared `PREVIEW_RECONCILE_SECRET`, since that
29
29
  * secret must not double as a cross-tenant app-registry master key
30
- * (decision d2-admin-auth-dedicated-secret). Unconfigured `APPS_ADMIN_SECRET`
30
+ * Unconfigured `APPS_ADMIN_SECRET`
31
31
  * means this path is unavailable — fails closed rather than trusting
32
32
  * another deployment's secret. The tenant is resolved server-side from
33
33
  * `APPS_ADMIN_TENANT` env or the `X-Tot-Tenant` header (honoured only
@@ -0,0 +1,281 @@
1
+ /**
2
+ * The managed-app version-retention + atomic-rollback spine (ext-apps-02) — the
3
+ * MANAGED-APP analog of the tenant integration lifecycle's known-good/rollback
4
+ * (`integrationLifecycle.ts`). Where that module keeps a tenant's own resolved
5
+ * integration revisions and repoints `live` to a known-good one, this persists
6
+ * every activated managed-app VERSION's COMPLETE restorable facet set and rolls an
7
+ * installed app back to a prior version's whole facet set in one atomic step.
8
+ *
9
+ * Two acceptance properties are schema-level, not conventions:
10
+ * - RETAINED — every activation is a new immutable `app_version_snapshots` row
11
+ * carrying the app's FULL restorable facet set (code ref/digest + entry module,
12
+ * public config, contributions, granted scopes, consent declaration, CSP
13
+ * origins) as one JSON blob. Nothing is mutated in place; the row list IS the
14
+ * retention history and audit trail (actor + time per activation).
15
+ * - ATOMIC ROLLBACK — a rollback restores a retained version's COMPLETE facet set
16
+ * in a SINGLE-ROW insert that copies that version's `facets` blob verbatim. You
17
+ * get the whole prior facet set or nothing — never a partial/mixed activation,
18
+ * never a silently-defaulted facet. It is forward-only (records `restoredFromSeq`
19
+ * and appends a new activation), so history is never rewritten and the rollback
20
+ * is itself an audited row.
21
+ *
22
+ * Fail-closed, mirroring the tenant lifecycle's discipline: a retained/restored
23
+ * facet set is scanned for secret material ({@link findForbiddenFields}) BEFORE any
24
+ * write, and a rollback to a version that was never retained is REFUSED with the
25
+ * store left completely untouched (no new row, current version unchanged).
26
+ *
27
+ * Pure over the `Queryable` seam (same shape `ConsentConfigStore`/`AppRegistryService`
28
+ * use), so it runs unchanged against real D1 in the Worker
29
+ * (`fromD1(env.STOREFRONT_APPS_DB)`) and an in-memory `node:sqlite` in tests. The
30
+ * current active version is `MAX(seq)` per install — the same "the latest row is
31
+ * current" model as `consent_config_revisions`.
32
+ */
33
+ import type {
34
+ ExecutionTier,
35
+ PlanIntegrationEntry,
36
+ PlanIntegrationConsent,
37
+ NormalizedContribution,
38
+ } from "@tot/public-runtime";
39
+ import { findForbiddenFields } from "@tot/public-runtime";
40
+ import type { Queryable, QueryStatement } from "../d1/catalog.js";
41
+
42
+ /**
43
+ * One managed-app version's COMPLETE restorable facet set — everything a rollback
44
+ * must put back to reconstitute the prior version with no missing or defaulted
45
+ * facet. All values are public (no secret material); the store proves it with a
46
+ * secret scan before persisting.
47
+ */
48
+ export interface AppVersionFacets {
49
+ /** The app package version this activation serves. */
50
+ appVersion: string;
51
+ /** The code identity — content digest of the pinned package. */
52
+ digest: string;
53
+ /** Where this version's code runs (elevated vs boxed). */
54
+ executionTier: ExecutionTier;
55
+ /** The exact-version entry module the lifecycle runtime imports. */
56
+ entry: PlanIntegrationEntry;
57
+ /** Resolved per-version public config; carries no secret material. */
58
+ publicConfig: Record<string, unknown>;
59
+ /** The normalized contributions this version registers. */
60
+ contributions: NormalizedContribution[];
61
+ /** The scopes granted to this version. */
62
+ scopes: string[];
63
+ /** The consent declaration the entry loader gates on. */
64
+ consent: PlanIntegrationConsent;
65
+ /** Sorted, deduped exact CSP origins this version needs. */
66
+ cspOrigins: string[];
67
+ }
68
+
69
+ /** One retained activation — an immutable version-retention/audit-trail entry. */
70
+ export interface AppVersionSnapshot {
71
+ id: string;
72
+ installId: string;
73
+ tenantId: string;
74
+ /** Monotonic per install; the largest is the currently active version. */
75
+ seq: number;
76
+ /** Mirror of `facets.appVersion` for cheap listing/filtering. */
77
+ appVersion: string;
78
+ /** The complete restorable facet set. */
79
+ facets: AppVersionFacets;
80
+ /** Opaque actor id — never PII. */
81
+ actor: string;
82
+ note: string | null;
83
+ /** The source `seq` when this activation is a rollback, else null. */
84
+ restoredFromSeq: number | null;
85
+ createdAt: string;
86
+ }
87
+
88
+ /** Retain (activate) a new managed-app version, persisting its full facet set. */
89
+ export interface RetainVersionInput {
90
+ installId: string;
91
+ tenantId: string;
92
+ facets: AppVersionFacets;
93
+ actor: string;
94
+ note?: string | null;
95
+ }
96
+
97
+ /** Roll an install back to a retained version, restoring its complete facet set. */
98
+ export interface RollbackVersionInput {
99
+ installId: string;
100
+ /** The retained activation's `seq` — its stable version handle — to restore. */
101
+ toSeq: number;
102
+ actor: string;
103
+ note?: string | null;
104
+ }
105
+
106
+ export interface PlannedVersionWrite {
107
+ snapshot: AppVersionSnapshot;
108
+ statement: QueryStatement;
109
+ }
110
+
111
+ export type AppVersionErrorCode = "unknown_target" | "integrity";
112
+
113
+ /** Thrown by every fail-closed refusal here — callers branch on `.code`. */
114
+ export class AppVersionError extends Error {
115
+ constructor(
116
+ message: string,
117
+ readonly code: AppVersionErrorCode,
118
+ readonly detail?: string[],
119
+ ) {
120
+ super(message);
121
+ this.name = "AppVersionError";
122
+ }
123
+ }
124
+
125
+ interface SnapshotRow {
126
+ id: string;
127
+ install_id: string;
128
+ tenant_id: string;
129
+ seq: number;
130
+ app_version: string;
131
+ facets: string;
132
+ actor: string;
133
+ note: string | null;
134
+ restored_from_seq: number | null;
135
+ created_at: string;
136
+ }
137
+
138
+ function toSnapshot(row: SnapshotRow): AppVersionSnapshot {
139
+ return {
140
+ id: row.id,
141
+ installId: row.install_id,
142
+ tenantId: row.tenant_id,
143
+ seq: row.seq,
144
+ appVersion: row.app_version,
145
+ facets: JSON.parse(row.facets) as AppVersionFacets,
146
+ actor: row.actor,
147
+ note: row.note,
148
+ restoredFromSeq: row.restored_from_seq,
149
+ createdAt: row.created_at,
150
+ };
151
+ }
152
+
153
+ export class AppVersionStore {
154
+ constructor(
155
+ private readonly db: Queryable,
156
+ private readonly now: () => string = () => new Date().toISOString(),
157
+ private readonly newId: () => string = () => crypto.randomUUID(),
158
+ ) {}
159
+
160
+ /**
161
+ * Retain (activate) a managed-app version by appending a new immutable snapshot
162
+ * carrying its COMPLETE restorable facet set. The `seq` is assigned as
163
+ * `MAX(seq)+1` for that install; the UNIQUE `(install_id, seq)` index makes a
164
+ * concurrent double-assignment fail loudly rather than overwrite an earlier
165
+ * activation. Fail-closed: a facet set naming secret material is REFUSED before
166
+ * any write (defense in depth over the plan/evidence contracts that produced it).
167
+ */
168
+ async retainVersion(input: RetainVersionInput): Promise<AppVersionSnapshot> {
169
+ const planned = await this.planRetainVersion(input);
170
+ await this.db.run(planned.statement.sql, ...(planned.statement.params ?? []));
171
+ return planned.snapshot;
172
+ }
173
+
174
+ /** Validate and plan retention without writing, for a caller-owned D1 batch. */
175
+ async planRetainVersion(input: RetainVersionInput): Promise<PlannedVersionWrite> {
176
+ return this.planAppend(input.installId, input.tenantId, input.facets, input.actor, input.note ?? null, null);
177
+ }
178
+
179
+ /**
180
+ * Roll an install back to a retained version, restoring its COMPLETE facet set in
181
+ * one atomic single-row insert that copies the target activation's `facets` blob
182
+ * verbatim (forward-only: records `restoredFromSeq`). No partial/mixed state is
183
+ * reachable — the whole prior facet set is restored or nothing is. Fail-closed: a
184
+ * `toSeq` that was never retained is REFUSED (`unknown_target`) and the store is
185
+ * left completely untouched — no new row, the current version unchanged.
186
+ */
187
+ async rollbackTo(input: RollbackVersionInput): Promise<AppVersionSnapshot> {
188
+ const planned = await this.planRollbackTo(input);
189
+ await this.db.run(planned.statement.sql, ...(planned.statement.params ?? []));
190
+ return planned.snapshot;
191
+ }
192
+
193
+ /** Plan a forward rollback without advancing MAX(seq) until the batch commits. */
194
+ async planRollbackTo(input: RollbackVersionInput): Promise<PlannedVersionWrite> {
195
+ const target = await this.getSnapshot(input.installId, input.toSeq);
196
+ if (!target) {
197
+ throw new AppVersionError(
198
+ `Cannot roll back install "${input.installId}" to version seq ${input.toSeq}: no such retained version.`,
199
+ "unknown_target",
200
+ );
201
+ }
202
+ const note = input.note ?? `Rollback to version ${target.appVersion} (seq ${input.toSeq}).`;
203
+ return this.planAppend(
204
+ input.installId,
205
+ target.tenantId,
206
+ target.facets,
207
+ input.actor,
208
+ note,
209
+ input.toSeq,
210
+ );
211
+ }
212
+
213
+ /** The current (active) version of an install — `MAX(seq)` — or null if none retained. */
214
+ async currentVersion(installId: string): Promise<AppVersionSnapshot | null> {
215
+ const row = await this.db.first<SnapshotRow>(
216
+ `SELECT * FROM app_version_snapshots WHERE install_id = ? ORDER BY seq DESC LIMIT 1`,
217
+ installId,
218
+ );
219
+ return row ? toSnapshot(row) : null;
220
+ }
221
+
222
+ /** A specific retained activation by its `seq`, or null if absent. */
223
+ async getSnapshot(installId: string, seq: number): Promise<AppVersionSnapshot | null> {
224
+ const row = await this.db.first<SnapshotRow>(
225
+ `SELECT * FROM app_version_snapshots WHERE install_id = ? AND seq = ?`,
226
+ installId,
227
+ seq,
228
+ );
229
+ return row ? toSnapshot(row) : null;
230
+ }
231
+
232
+ /** An install's full retention history, newest first — the version audit trail. */
233
+ async listVersions(installId: string): Promise<AppVersionSnapshot[]> {
234
+ const rows = await this.db.all<SnapshotRow>(
235
+ `SELECT * FROM app_version_snapshots WHERE install_id = ? ORDER BY seq DESC`,
236
+ installId,
237
+ );
238
+ return rows.map(toSnapshot);
239
+ }
240
+
241
+ /**
242
+ * Append a new activation. Shared by retain (source `null`) and rollback (source
243
+ * `restoredFromSeq`). Scans the facet set for secret material BEFORE the insert,
244
+ * then writes exactly one row — the atomicity boundary: the whole facet set lands
245
+ * or nothing does.
246
+ */
247
+ private async planAppend(
248
+ installId: string,
249
+ tenantId: string,
250
+ facets: AppVersionFacets,
251
+ actor: string,
252
+ note: string | null,
253
+ restoredFromSeq: number | null,
254
+ ): Promise<PlannedVersionWrite> {
255
+ const forbidden = findForbiddenFields(facets, "facets");
256
+ if (forbidden.length > 0) {
257
+ throw new AppVersionError(
258
+ `Refusing to persist version facets naming secret material: ${forbidden.map((v) => v.path).join(", ")}.`,
259
+ "integrity",
260
+ forbidden.map((v) => v.path),
261
+ );
262
+ }
263
+ const maxRow = await this.db.first<{ max_seq: number | null }>(
264
+ `SELECT MAX(seq) AS max_seq FROM app_version_snapshots WHERE install_id = ?`,
265
+ installId,
266
+ );
267
+ const seq = (maxRow?.max_seq ?? 0) + 1;
268
+ const createdAt = this.now();
269
+ const id = `avs_${this.newId()}`;
270
+ const statement: QueryStatement = {
271
+ sql: `INSERT INTO app_version_snapshots
272
+ (id, install_id, tenant_id, seq, app_version, facets, actor, note, restored_from_seq, created_at)
273
+ VALUES (?,?,?,?,?,?,?,?,?,?)`,
274
+ params: [id, installId, tenantId, seq, facets.appVersion, JSON.stringify(facets), actor, note, restoredFromSeq, createdAt],
275
+ };
276
+ return {
277
+ statement,
278
+ snapshot: { id, installId, tenantId, seq, appVersion: facets.appVersion, facets, actor, note, restoredFromSeq, createdAt },
279
+ };
280
+ }
281
+ }
@@ -0,0 +1,228 @@
1
+ /**
2
+ * The merchant consent configuration store — the durable spine behind ext-admin-03.
3
+ * It persists a tenant's consent configuration as an APPEND-ONLY revision history and
4
+ * the demonstrated-CMP-demand ledger, and reads back exactly the shapes the pure
5
+ * ext-policy-03 layer consumes.
6
+ *
7
+ * Two acceptance properties are schema-level, not conventions:
8
+ * - AUDITED — every save is a new immutable revision that names its actor and time;
9
+ * nothing is mutated in place, so the revision list IS the audit trail.
10
+ * - REVERSIBLE — a revert is a forward-only new revision that copies a prior config
11
+ * and records `revertedFrom`; history is never rewritten, and rolling back leaves
12
+ * its own audited entry.
13
+ *
14
+ * Pure over the `Queryable` seam (same shape `CandidateStore`/`OrdersStore` use), so it
15
+ * runs unchanged against real D1 in the Worker (`fromD1(env.STOREFRONT_APPS_DB)`) and an
16
+ * in-memory `node:sqlite` in tests. This is the DATA MODEL + repository API only — the
17
+ * validation (`validateConsentConfig`) and the broker materialization
18
+ * (`toTenantConsentPolicy`/`buildTenantConsentBroker`) live in `@tot/public-runtime`;
19
+ * this store persists already-validated JSON and never re-derives policy.
20
+ */
21
+ import type { Queryable } from "../d1/catalog.js";
22
+ import type { CmpDemandLedger, TenantConsentConfig } from "@tot/public-runtime";
23
+
24
+ /** One persisted consent-config revision — an immutable audit-trail entry. */
25
+ export interface ConsentConfigRevision {
26
+ id: string;
27
+ tenantId: string;
28
+ /** Monotonic per tenant; the largest is the current config. */
29
+ revision: number;
30
+ config: TenantConsentConfig;
31
+ /** Opaque actor id — never PII. */
32
+ actor: string;
33
+ note: string | null;
34
+ /** The source revision when this one is a revert, else null. */
35
+ revertedFrom: number | null;
36
+ createdAt: string;
37
+ }
38
+
39
+ /** Save a new revision of a tenant's (already-validated) consent configuration. */
40
+ export interface SaveConsentConfigInput {
41
+ tenantId: string;
42
+ config: TenantConsentConfig;
43
+ actor: string;
44
+ note?: string | null;
45
+ }
46
+
47
+ /** Revert a tenant's consent configuration to a prior revision (forward-only). */
48
+ export interface RevertConsentConfigInput {
49
+ tenantId: string;
50
+ /** The revision whose config to restore as a NEW revision. */
51
+ toRevision: number;
52
+ actor: string;
53
+ note?: string | null;
54
+ }
55
+
56
+ /** Record demonstrated demand for a CMP adapter from one tenant. */
57
+ export interface RecordCmpDemandInput {
58
+ tenantId: string;
59
+ adapterId: string;
60
+ source?: string | null;
61
+ }
62
+
63
+ interface RevisionRow {
64
+ id: string;
65
+ tenant_id: string;
66
+ revision: number;
67
+ config: string;
68
+ actor: string;
69
+ note: string | null;
70
+ reverted_from: number | null;
71
+ created_at: string;
72
+ }
73
+
74
+ interface DemandRow {
75
+ id: string;
76
+ tenant_id: string;
77
+ adapter_id: string;
78
+ source: string | null;
79
+ at: string;
80
+ }
81
+
82
+ function toRevision(row: RevisionRow): ConsentConfigRevision {
83
+ return {
84
+ id: row.id,
85
+ tenantId: row.tenant_id,
86
+ revision: row.revision,
87
+ config: JSON.parse(row.config) as TenantConsentConfig,
88
+ actor: row.actor,
89
+ note: row.note,
90
+ revertedFrom: row.reverted_from,
91
+ createdAt: row.created_at,
92
+ };
93
+ }
94
+
95
+ export class ConsentConfigStore {
96
+ constructor(
97
+ private readonly db: Queryable,
98
+ private readonly now: () => string = () => new Date().toISOString(),
99
+ private readonly newId: () => string = () => crypto.randomUUID(),
100
+ ) {}
101
+
102
+ /**
103
+ * Append a new revision of a tenant's consent config. The revision number is
104
+ * assigned as `MAX(revision)+1` for that tenant; the UNIQUE `(tenant_id, revision)`
105
+ * index makes a concurrent double-assignment fail loudly rather than overwrite an
106
+ * earlier entry. The prior revisions are never touched — the history is the audit.
107
+ */
108
+ async saveRevision(input: SaveConsentConfigInput): Promise<ConsentConfigRevision> {
109
+ return this.appendRevision(input.tenantId, input.config, input.actor, input.note ?? null, null);
110
+ }
111
+
112
+ /**
113
+ * Restore a prior revision as a NEW revision (forward-only revert). Reads the target
114
+ * revision's config and appends it again with `revertedFrom` set — so a rollback is
115
+ * itself an audited entry and no history is rewritten. Throws if the target is absent.
116
+ */
117
+ async revertTo(input: RevertConsentConfigInput): Promise<ConsentConfigRevision> {
118
+ const target = await this.getRevision(input.tenantId, input.toRevision);
119
+ if (!target) {
120
+ throw new Error(
121
+ `Cannot revert tenant "${input.tenantId}" to revision ${input.toRevision}: no such revision.`,
122
+ );
123
+ }
124
+ const note = input.note ?? `Revert to revision ${input.toRevision}.`;
125
+ return this.appendRevision(input.tenantId, target.config, input.actor, note, input.toRevision);
126
+ }
127
+
128
+ private async appendRevision(
129
+ tenantId: string,
130
+ config: TenantConsentConfig,
131
+ actor: string,
132
+ note: string | null,
133
+ revertedFrom: number | null,
134
+ ): Promise<ConsentConfigRevision> {
135
+ const maxRow = await this.db.first<{ max_revision: number | null }>(
136
+ `SELECT MAX(revision) AS max_revision FROM consent_config_revisions WHERE tenant_id = ?`,
137
+ tenantId,
138
+ );
139
+ const revision = (maxRow?.max_revision ?? 0) + 1;
140
+ const createdAt = this.now();
141
+ await this.db.run(
142
+ `INSERT INTO consent_config_revisions
143
+ (id, tenant_id, revision, config, actor, note, reverted_from, created_at)
144
+ VALUES (?,?,?,?,?,?,?,?)`,
145
+ `ccr_${this.newId()}`,
146
+ tenantId,
147
+ revision,
148
+ JSON.stringify(config),
149
+ actor,
150
+ note,
151
+ revertedFrom,
152
+ createdAt,
153
+ );
154
+ const row = await this.db.first<RevisionRow>(
155
+ `SELECT * FROM consent_config_revisions WHERE tenant_id = ? AND revision = ?`,
156
+ tenantId,
157
+ revision,
158
+ );
159
+ return toRevision(row!);
160
+ }
161
+
162
+ /** The current (latest) revision of a tenant's consent config, or null if none saved. */
163
+ async currentRevision(tenantId: string): Promise<ConsentConfigRevision | null> {
164
+ const row = await this.db.first<RevisionRow>(
165
+ `SELECT * FROM consent_config_revisions WHERE tenant_id = ?
166
+ ORDER BY revision DESC LIMIT 1`,
167
+ tenantId,
168
+ );
169
+ return row ? toRevision(row) : null;
170
+ }
171
+
172
+ /** A specific revision, or null if absent. */
173
+ async getRevision(tenantId: string, revision: number): Promise<ConsentConfigRevision | null> {
174
+ const row = await this.db.first<RevisionRow>(
175
+ `SELECT * FROM consent_config_revisions WHERE tenant_id = ? AND revision = ?`,
176
+ tenantId,
177
+ revision,
178
+ );
179
+ return row ? toRevision(row) : null;
180
+ }
181
+
182
+ /** A tenant's full revision history, newest first — the audit trail. */
183
+ async listRevisions(tenantId: string): Promise<ConsentConfigRevision[]> {
184
+ const rows = await this.db.all<RevisionRow>(
185
+ `SELECT * FROM consent_config_revisions WHERE tenant_id = ? ORDER BY revision DESC`,
186
+ tenantId,
187
+ );
188
+ return rows.map(toRevision);
189
+ }
190
+
191
+ /**
192
+ * Record demonstrated demand for a CMP adapter from one tenant. Idempotent per
193
+ * `(tenant, adapter)` — a tenant re-asking updates the recorded time/source on the
194
+ * SAME row and never inflates the distinct-tenant demand count.
195
+ */
196
+ async recordCmpDemand(input: RecordCmpDemandInput): Promise<void> {
197
+ await this.db.run(
198
+ `INSERT INTO consent_cmp_demand (id, tenant_id, adapter_id, source, at)
199
+ VALUES (?,?,?,?,?)
200
+ ON CONFLICT(tenant_id, adapter_id) DO UPDATE SET
201
+ source = excluded.source,
202
+ at = excluded.at`,
203
+ `cmd_${this.newId()}`,
204
+ input.tenantId,
205
+ input.adapterId,
206
+ input.source ?? null,
207
+ this.now(),
208
+ );
209
+ }
210
+
211
+ /**
212
+ * The whole demand ledger, in the `@tot/public-runtime` shape — passed straight into
213
+ * `validateConsentConfig` so a CMP choice is admitted only from demonstrated demand.
214
+ */
215
+ async demandLedger(): Promise<CmpDemandLedger> {
216
+ const rows = await this.db.all<DemandRow>(
217
+ `SELECT * FROM consent_cmp_demand ORDER BY adapter_id ASC, tenant_id ASC`,
218
+ );
219
+ return {
220
+ signals: rows.map((r) => ({
221
+ adapterId: r.adapter_id,
222
+ tenantPublicId: r.tenant_id,
223
+ at: r.at,
224
+ source: r.source ?? undefined,
225
+ })),
226
+ };
227
+ }
228
+ }