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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +8 -3
  3. package/apps/storefront/env.d.ts +54 -5
  4. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  5. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  6. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  7. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  8. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  9. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  10. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  11. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  12. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  13. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  14. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  15. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  16. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  17. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  18. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  19. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  20. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  22. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  23. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  24. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  25. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  26. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  27. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  28. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  29. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  30. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  32. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  33. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  34. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  37. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  38. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  41. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  42. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  43. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  44. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  45. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  46. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  47. package/apps/storefront/src/components/Img.astro +1 -1
  48. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  49. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  50. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  51. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  52. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  53. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  54. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  55. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  59. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  60. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  61. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  62. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  63. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  64. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  65. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  66. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  67. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  68. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  69. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  70. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  71. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  72. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  73. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  74. package/apps/storefront/src/config/resolver.ts +18 -10
  75. package/apps/storefront/src/layouts/Layout.astro +100 -20
  76. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  77. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  78. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  79. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  80. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  81. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  82. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  83. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  84. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  87. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  89. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  90. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  91. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  92. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  93. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  94. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  95. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  96. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  97. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  98. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  99. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  100. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  104. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  105. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  106. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  109. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  110. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  111. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  112. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  113. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  114. package/apps/storefront/src/lib/blog/collection.ts +114 -17
  115. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  116. package/apps/storefront/src/lib/blog/provider.ts +76 -9
  117. package/apps/storefront/src/lib/blog/types.ts +9 -9
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  119. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  120. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  121. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  122. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  123. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  124. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  126. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  127. package/apps/storefront/src/lib/content/index.ts +5 -5
  128. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  129. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  130. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  131. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  132. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  133. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  134. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  135. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  136. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  137. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  138. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  139. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  140. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  141. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  142. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  143. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  144. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  145. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  146. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  147. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  148. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  149. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  150. package/apps/storefront/src/lib/membership/model.ts +1 -2
  151. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  153. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  154. package/apps/storefront/src/lib/previewReload.ts +57 -1
  155. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  156. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  157. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  158. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  159. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  160. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  161. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  162. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  163. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  164. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  165. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  166. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  173. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  176. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  177. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  178. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  179. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  180. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  181. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  182. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  183. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  184. package/apps/storefront/src/lib/social/notify.ts +4 -4
  185. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  186. package/apps/storefront/src/lib/storyblok/provider.ts +203 -69
  187. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  188. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  189. package/apps/storefront/src/middleware/index.ts +273 -51
  190. package/apps/storefront/src/pages/[...slug].astro +9 -4
  191. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  192. package/apps/storefront/src/pages/admin/select.astro +96 -0
  193. package/apps/storefront/src/pages/admin.astro +16 -2
  194. package/apps/storefront/src/pages/api/activity.ts +8 -8
  195. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  196. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  197. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  199. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  201. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  202. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  203. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  204. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  205. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  206. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  207. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  208. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  209. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  210. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  211. package/apps/storefront/src/pages/blog/index.astro +2 -2
  212. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  213. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  214. package/apps/storefront/src/pages/index.astro +10 -5
  215. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  216. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  217. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  218. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +42 -31
  219. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +8 -3
  220. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +6 -2
  221. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +6 -2
  222. package/apps/storefront/src/pages/style-guide/index.astro +19 -5
  223. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  224. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  225. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  226. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  227. package/apps/storefront/src/styles/admin.css +27 -1
  228. package/apps/storefront/src/themes/tenants/index.ts +19 -7
  229. package/apps/storefront/tsconfig.json +1 -1
  230. package/package.json +1 -1
  231. package/packages/public-runtime/package.json +2 -1
  232. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  250. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  251. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  252. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  253. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  254. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  255. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  256. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  257. package/packages/public-runtime/src/activity/event.ts +6 -6
  258. package/packages/public-runtime/src/activity/index.ts +2 -2
  259. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  260. package/packages/public-runtime/src/candidate-index.ts +42 -8
  261. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  262. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  263. package/packages/public-runtime/src/checkout-style.ts +7 -7
  264. package/packages/public-runtime/src/checkout.ts +3 -3
  265. package/packages/public-runtime/src/chrome.ts +777 -0
  266. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  267. package/packages/public-runtime/src/csp.ts +99 -0
  268. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  269. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  270. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  271. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  272. package/packages/public-runtime/src/evidence.ts +288 -0
  273. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  274. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  275. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  276. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  277. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  278. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  279. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  280. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  281. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  282. package/packages/public-runtime/src/index.ts +16 -1
  283. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  284. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  285. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  286. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  287. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  288. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  289. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  290. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  291. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  292. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  293. package/packages/public-runtime/src/integration/csp.ts +105 -0
  294. package/packages/public-runtime/src/integration/digest.ts +43 -0
  295. package/packages/public-runtime/src/integration/environment.ts +228 -0
  296. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  297. package/packages/public-runtime/src/integration/events.ts +701 -0
  298. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  299. package/packages/public-runtime/src/integration/index.ts +90 -0
  300. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  301. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  302. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  303. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  305. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  306. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  307. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  308. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  309. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  310. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  311. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  312. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  313. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  314. package/packages/public-runtime/src/integration/observability.ts +451 -0
  315. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  316. package/packages/public-runtime/src/integration/phases.ts +182 -0
  317. package/packages/public-runtime/src/integration/plan.ts +112 -0
  318. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  319. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  320. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  321. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  322. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  323. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  324. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  325. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  326. package/packages/public-runtime/src/integration/result.ts +239 -0
  327. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  328. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  329. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  330. package/packages/public-runtime/src/integration/version.ts +92 -0
  331. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  332. package/packages/public-runtime/src/membership.ts +2 -2
  333. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  334. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  335. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  336. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  337. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  338. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  339. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  340. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  341. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  342. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  343. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  344. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  345. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  346. package/packages/public-runtime/src/static-bundle.ts +5 -5
  347. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  348. package/packages/public-runtime/src/tenant.ts +136 -9
  349. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  350. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  351. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  352. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  353. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  354. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  355. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  356. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  357. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  358. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  359. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  360. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  361. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  362. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  363. package/scripts/build/copy-tenant-assets.mjs +12 -5
  364. package/scripts/dev/ai-edit.mjs +2 -1
  365. package/scripts/dev/file-browser.mjs +2 -2
  366. package/scripts/dev/file-writer.mjs +2 -2
  367. package/scripts/dev/integration-guardrails.mjs +498 -0
  368. package/scripts/dev/safe-path.mjs +2 -2
  369. package/scripts/dev/tenant-source-root.mjs +3 -2
  370. package/scripts/tot-dev.mjs +5 -4
@@ -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
+ }
@@ -0,0 +1,447 @@
1
+ /**
2
+ * Pure, transport-free orchestration for the review->install path of the GTM
3
+ * migration assistant: turn a reviewed, stored `MigrationDraftV1` into a
4
+ * DISABLED desired managed-analytics install/revision, strictly through WS1's
5
+ * pinned PUBLIC contract (`@tot/public-runtime`'s managed-analytics v1).
6
+ *
7
+ * SECURITY / PRODUCT CONTRACT (a reviewer WILL reject violations):
8
+ * - Consumes ONLY the pinned PUBLIC managed-analytics v1 contract — its
9
+ * request/response TYPES and its VALIDATORS. It never reimplements WS1's
10
+ * schemas or server, never imports a private module, never touches a
11
+ * database, config, tenant, or materializer, and never calls a live API
12
+ * directly: every backend effect goes through the injected
13
+ * {@link Ws1ManagedAnalyticsClient} interface (typed by pinned types), so
14
+ * the whole module is unit-testable against a FAKE client with no transport.
15
+ * - LOAD-BY-ID: the caller passes a draft `id`; this module LOADS the stored
16
+ * draft from WS1 (`getMigrationDraft`) and re-validates it through the
17
+ * pinned `validateMigrationDraftV1`. A caller can NEVER submit a hand-crafted
18
+ * draft object past review — translated/untrusted draft data is not accepted.
19
+ * - DISABLED-ONLY: the produced install is created with `status:"disabled"`
20
+ * and its revision is a `status:"draft"` — enforced by the pinned
21
+ * `CreateIntegrationInstallBody` literal AND re-asserted on the response.
22
+ * This path CANNOT candidate/publish/activate/enable in the same request:
23
+ * the injected client interface deliberately OMITS
24
+ * `setIntegrationInstallStatus` (and every other enable/publish/mutate-draft
25
+ * op), so enabling is not even type-reachable from here.
26
+ * - The adapter VERSION the install needs is resolved from WS1's own catalog
27
+ * (`getAdapterDefinition`), never trusted from the caller.
28
+ *
29
+ * Pure: no I/O of its own, no globals, no `Date.now`. Same inputs + same client
30
+ * responses -> same decision.
31
+ */
32
+ import {
33
+ MANAGED_ANALYTICS_API_VERSION,
34
+ snapshotManagedAnalyticsDeclarativeInput,
35
+ validateApiErrorEnvelope,
36
+ validateGetIntegrationInstallRequest,
37
+ validateGetMigrationDraftRequest,
38
+ validateMigrationDraftV1,
39
+ stableIntegrationStringify,
40
+ validateAdapterDefinition,
41
+ validateIntegrationInstall,
42
+ validateIntegrationRevisionV1,
43
+ validateCreateIntegrationInstallRequest,
44
+ validateReviseIntegrationInstallRequest,
45
+ type OpaquePublicId,
46
+ type IntegrationInstall,
47
+ type IntegrationRevisionV1,
48
+ type ManagedAnalyticsErrorCode,
49
+ type ApiSuccess,
50
+ type ApiErrorEnvelope,
51
+ type GetMigrationDraftRequest,
52
+ type GetMigrationDraftResponse,
53
+ type MigrationDraftV1,
54
+ type AdapterDefinition,
55
+ type GetAdapterDefinitionRequest,
56
+ type GetAdapterDefinitionResponse,
57
+ type GetIntegrationInstallRequest,
58
+ type GetIntegrationInstallResponse,
59
+ type CreateIntegrationInstallRequest,
60
+ type CreateIntegrationInstallResponse,
61
+ type ReviseIntegrationInstallRequest,
62
+ type ReviseIntegrationInstallResponse,
63
+ } from "@tot/public-runtime";
64
+
65
+ /**
66
+ * The MINIMAL WS1 backend seam this path needs, typed entirely by the pinned
67
+ * contract's request/response types. It is a strict SUBSET of the pinned
68
+ * `ManagedAnalyticsApiV1Client`: reads used to load the draft + resolve the
69
+ * adapter version + (for a revise) bind the target install and observe its CAS
70
+ * state, plus the two SAFE writes that yield a disabled install/revision. It intentionally
71
+ * exposes NO `setIntegrationInstallStatus`, `deleteIntegrationInstall`,
72
+ * `createMigrationDraft`, `updateMigrationDraft`, or `deleteMigrationDraft` — so
73
+ * enabling/publishing/activating and any draft mutation are type-impossible from
74
+ * this module. Any concrete pinned client (HTTP at the edge) structurally
75
+ * satisfies it; tests inject a fake.
76
+ */
77
+ export interface Ws1ManagedAnalyticsClient {
78
+ getMigrationDraft(request: GetMigrationDraftRequest): Promise<GetMigrationDraftResponse>;
79
+ getAdapterDefinition(request: GetAdapterDefinitionRequest): Promise<GetAdapterDefinitionResponse>;
80
+ getIntegrationInstall(request: GetIntegrationInstallRequest): Promise<GetIntegrationInstallResponse>;
81
+ createIntegrationInstall(request: CreateIntegrationInstallRequest): Promise<CreateIntegrationInstallResponse>;
82
+ reviseIntegrationInstall(request: ReviseIntegrationInstallRequest): Promise<ReviseIntegrationInstallResponse>;
83
+ }
84
+
85
+ /** Everything the orchestration needs; the draft is referenced BY ID, never by value. */
86
+ export interface ReviewInstallInput {
87
+ /** The stored draft's opaque id — the server loads it; a draft object is never accepted. */
88
+ readonly draftId: OpaquePublicId;
89
+ /**
90
+ * Optional existing DISABLED install to revise into a new draft revision. When
91
+ * absent, a NEW disabled install is created. A non-disabled target is refused.
92
+ */
93
+ readonly targetInstallId?: OpaquePublicId;
94
+ /**
95
+ * The original CAS revision for a revise. A retry MUST reuse this value with
96
+ * the same idempotency key even when the target read now shows the committed
97
+ * `expectedRevision + 1`; otherwise WS1 correctly treats the changed request
98
+ * as an idempotency conflict.
99
+ */
100
+ readonly expectedRevision?: number;
101
+ /** Stable opaque idempotency key for the write/retry (>=8 chars, per the pinned mutation envelope). */
102
+ readonly idempotencyKey: OpaquePublicId;
103
+ /** Correlation id echoed on every result for support/audit. */
104
+ readonly correlationId: string;
105
+ }
106
+
107
+ export interface ReviewInstallOk {
108
+ readonly ok: true;
109
+ readonly correlationId: string;
110
+ /** Always `status:"disabled"` — re-asserted against the pinned validator. */
111
+ readonly install: IntegrationInstall;
112
+ /** Always `status:"draft"` — the newly created desired revision. */
113
+ readonly revision: IntegrationRevisionV1;
114
+ /** The one validated catalog snapshot whose version was used for this revision. */
115
+ readonly adapter: AdapterDefinition;
116
+ readonly idempotencyReplayed: boolean;
117
+ }
118
+
119
+ export interface ReviewInstallErr {
120
+ readonly ok: false;
121
+ readonly correlationId: string;
122
+ /** HTTP-ish status the route surfaces directly. */
123
+ readonly status: number;
124
+ /** A safe, redacted operator message — never raw input. */
125
+ readonly error: string;
126
+ /** The pinned error code when the failure came from WS1; a local code otherwise. */
127
+ readonly code?: ManagedAnalyticsErrorCode | "safety_refused";
128
+ }
129
+
130
+ export type ReviewInstallResult = ReviewInstallOk | ReviewInstallErr;
131
+
132
+ const V = MANAGED_ANALYTICS_API_VERSION;
133
+
134
+ /** Map a pinned `ApiErrorEnvelope`'s code to an HTTP status without forwarding its message. */
135
+ const STATUS_BY_ERROR_CODE: Record<ManagedAnalyticsErrorCode, number> = {
136
+ invalid_request: 400,
137
+ unauthorized: 401,
138
+ forbidden: 403,
139
+ not_found: 404,
140
+ validation_failed: 422,
141
+ unsupported_version: 400,
142
+ stale_revision: 409,
143
+ idempotency_conflict: 409,
144
+ adapter_deprecated: 409,
145
+ conflict: 409,
146
+ internal_error: 502,
147
+ };
148
+
149
+ function fail(correlationId: string, status: number, error: string, code?: ReviewInstallErr["code"]): ReviewInstallErr {
150
+ return { ok: false, correlationId, status, error, code };
151
+ }
152
+
153
+ type ClientCall<T> =
154
+ | { readonly ok: true; readonly value: T }
155
+ | { readonly ok: false; readonly result: ReviewInstallErr };
156
+
157
+ /** Collapse rejected client promises to one stable response without inspecting the rejection. */
158
+ async function callWs1<T>(operation: () => Promise<T>, correlationId: string): Promise<ClientCall<T>> {
159
+ try {
160
+ return { ok: true, value: await operation() };
161
+ } catch {
162
+ return {
163
+ ok: false,
164
+ result: fail(
165
+ correlationId,
166
+ 502,
167
+ "The Integration Management API request failed safely.",
168
+ "internal_error",
169
+ ),
170
+ };
171
+ }
172
+ }
173
+
174
+ /**
175
+ * True when a pinned API result is the error envelope (its discriminant is
176
+ * `error`). `MutationSuccess<T>` is a subtype of `ApiSuccess<T>`, so a mutation
177
+ * response narrows through this guard too, keeping `idempotencyReplayed` on the
178
+ * success branch.
179
+ */
180
+ function mapApiError(res: ApiErrorEnvelope, correlationId: string): ReviewInstallErr {
181
+ const { code } = res.error;
182
+ return fail(correlationId, STATUS_BY_ERROR_CODE[code] ?? 502, `The Integration Management API returned ${code}.`, code);
183
+ }
184
+
185
+ type PreparedApiResponse<T> =
186
+ | { readonly ok: true; readonly value: ApiSuccess<T> & Readonly<Record<string, unknown>> }
187
+ | { readonly ok: false; readonly result: ReviewInstallErr };
188
+
189
+ /** Snapshot and validate the response envelope before any property access. */
190
+ export function prepareWs1ApiResponse<T>(
191
+ input: unknown,
192
+ correlationId: string,
193
+ kind: "read" | "mutation" = "read",
194
+ ): PreparedApiResponse<T> {
195
+ const prepared = snapshotManagedAnalyticsDeclarativeInput<Record<string, unknown>>(input);
196
+ if (!prepared.ok) {
197
+ return { ok: false, result: fail(correlationId, 502, "The Integration Management API returned an unsafe response.", "internal_error") };
198
+ }
199
+ const value = prepared.value;
200
+ if (Object.prototype.hasOwnProperty.call(value, "error")) {
201
+ const checked = validateApiErrorEnvelope(value);
202
+ if (!checked.ok) {
203
+ return { ok: false, result: fail(correlationId, 502, "The Integration Management API returned an invalid error response.", "internal_error") };
204
+ }
205
+ return { ok: false, result: mapApiError(checked.value, correlationId) };
206
+ }
207
+ const expectedKeys = kind === "mutation"
208
+ ? ["apiVersion", "data", "idempotencyReplayed", "requestId"]
209
+ : ["apiVersion", "data", "requestId"];
210
+ const actualKeys = Object.keys(value).sort();
211
+ const requestIdCheck = validateApiErrorEnvelope({
212
+ apiVersion: V,
213
+ requestId: value["requestId"],
214
+ error: { code: "internal_error", message: "Internal error." },
215
+ });
216
+ const data = value["data"];
217
+ if (
218
+ stableIntegrationStringify(actualKeys) !== stableIntegrationStringify(expectedKeys) ||
219
+ value["apiVersion"] !== V ||
220
+ !requestIdCheck.ok ||
221
+ (kind === "mutation" && typeof value["idempotencyReplayed"] !== "boolean") ||
222
+ data === null ||
223
+ typeof data !== "object" ||
224
+ Array.isArray(data)
225
+ ) {
226
+ return { ok: false, result: fail(correlationId, 502, "The Integration Management API returned an invalid success response.", "internal_error") };
227
+ }
228
+ return { ok: true, value: value as ApiSuccess<T> & Readonly<Record<string, unknown>> };
229
+ }
230
+
231
+ /**
232
+ * Convert a reviewed, stored migration draft into a DISABLED desired install +
233
+ * draft revision, entirely through the pinned WS1 contract. Never enables,
234
+ * publishes, or activates. Returns a safe error (never throws on WS1 failures)
235
+ * for: an unknown draft id, a stored draft that fails the pinned validator, an
236
+ * unknown/deprecated adapter, a non-disabled revise target, a request that fails
237
+ * the pinned request validator, or a response that comes back not-disabled.
238
+ */
239
+ export async function reviewInstallDraft(
240
+ client: Ws1ManagedAnalyticsClient,
241
+ input: ReviewInstallInput,
242
+ expectedDraftSnapshot?: MigrationDraftV1,
243
+ ): Promise<ReviewInstallResult> {
244
+ const { draftId, targetInstallId, idempotencyKey, correlationId } = input;
245
+
246
+ // Validate every caller-selected read identity through the pinned public
247
+ // contract before ANY client call. Besides the opaque-id grammar/length cap,
248
+ // these validators run the shared PII/secret/URL/code safety scan, preventing
249
+ // hostile identifiers from crossing even the injected transport boundary.
250
+ const draftReq: GetMigrationDraftRequest = { apiVersion: V, query: { draftId } };
251
+ const draftReqCheck = validateGetMigrationDraftRequest(draftReq);
252
+ if (!draftReqCheck.ok) {
253
+ return fail(correlationId, 422, "The requested draft identifier failed contract validation.", "validation_failed");
254
+ }
255
+ const installReqCheck = targetInstallId === undefined
256
+ ? undefined
257
+ : validateGetIntegrationInstallRequest({ apiVersion: V, query: { installId: targetInstallId } });
258
+ if (installReqCheck !== undefined && !installReqCheck.ok) {
259
+ return fail(correlationId, 422, "The requested install identifier failed contract validation.", "validation_failed");
260
+ }
261
+
262
+ // 1. LOAD the stored draft BY ID (never from caller-supplied data).
263
+ const draftCall = await callWs1(
264
+ () => client.getMigrationDraft(draftReqCheck.value),
265
+ correlationId,
266
+ );
267
+ if (!draftCall.ok) return draftCall.result;
268
+ const draftRes = prepareWs1ApiResponse<MigrationDraftV1>(draftCall.value, correlationId);
269
+ if (!draftRes.ok) return draftRes.result;
270
+ const draftCheck = validateMigrationDraftV1(draftRes.value.data);
271
+ if (!draftCheck.ok) {
272
+ return fail(correlationId, 422, "The stored draft failed contract validation.", "validation_failed");
273
+ }
274
+ const draft = draftCheck.value;
275
+ if (draft.draftId !== draftReqCheck.value.query.draftId) {
276
+ return fail(correlationId, 409, "The stored migration draft did not match the requested draft.", "safety_refused");
277
+ }
278
+ if (expectedDraftSnapshot !== undefined && stableIntegrationStringify(draft) !== stableIntegrationStringify(expectedDraftSnapshot)) {
279
+ return fail(correlationId, 409, "The stored migration draft changed after the reviewed create response.", "safety_refused");
280
+ }
281
+
282
+ // A revise target is bound by id, adapter, and retry-compatible CAS state
283
+ // before catalog resolution or any write.
284
+ let targetInstall: IntegrationInstall | undefined;
285
+ let targetAlreadyAdvanced = false;
286
+ if (targetInstallId !== undefined && installReqCheck?.ok) {
287
+ const installCall = await callWs1(
288
+ () => client.getIntegrationInstall(installReqCheck.value),
289
+ correlationId,
290
+ );
291
+ if (!installCall.ok) return installCall.result;
292
+ const installRes = prepareWs1ApiResponse<IntegrationInstall>(installCall.value, correlationId);
293
+ if (!installRes.ok) return installRes.result;
294
+ const targetCheck = validateIntegrationInstall(installRes.value.data);
295
+ if (!targetCheck.ok) {
296
+ return fail(correlationId, 502, "The target install failed contract validation.", "internal_error");
297
+ }
298
+ if (targetCheck.value.installId !== installReqCheck.value.query.installId) {
299
+ return fail(correlationId, 409, "The stored install did not match the requested target.", "safety_refused");
300
+ }
301
+ if (targetCheck.value.status !== "disabled") {
302
+ return fail(correlationId, 409, "The target install is not disabled; this path only revises a disabled install.", "conflict");
303
+ }
304
+ if (targetCheck.value.adapterId !== draft.proposedAdapterId) {
305
+ return fail(correlationId, 409, "The target install adapter did not match the reviewed draft.", "safety_refused");
306
+ }
307
+ if (
308
+ input.expectedRevision !== undefined &&
309
+ targetCheck.value.revision !== input.expectedRevision &&
310
+ targetCheck.value.revision !== input.expectedRevision + 1
311
+ ) {
312
+ return fail(correlationId, 409, "The target install revision no longer matches this review attempt.", "conflict");
313
+ }
314
+ targetAlreadyAdvanced = input.expectedRevision !== undefined &&
315
+ targetCheck.value.revision === input.expectedRevision + 1;
316
+ targetInstall = targetCheck.value;
317
+ }
318
+
319
+ // 2. Resolve the adapter VERSION from WS1's catalog — never trust the caller.
320
+ const adapterReq: GetAdapterDefinitionRequest = { apiVersion: V, query: { adapterId: draft.proposedAdapterId } };
321
+ const adapterCall = await callWs1(() => client.getAdapterDefinition(adapterReq), correlationId);
322
+ if (!adapterCall.ok) return adapterCall.result;
323
+ const adapterRes = prepareWs1ApiResponse<AdapterDefinition>(adapterCall.value, correlationId);
324
+ if (!adapterRes.ok) return adapterRes.result;
325
+ const adapterCheck = validateAdapterDefinition(adapterRes.value.data);
326
+ if (!adapterCheck.ok) {
327
+ return fail(correlationId, 502, "The adapter definition failed contract validation.", "internal_error");
328
+ }
329
+ const adapter = adapterCheck.value;
330
+ if (adapter.adapterId !== draft.proposedAdapterId) {
331
+ return fail(correlationId, 409, "The adapter snapshot did not match the reviewed draft.", "safety_refused");
332
+ }
333
+ if (adapter.availability === "deprecated") {
334
+ return fail(correlationId, 409, "The proposed adapter is deprecated and cannot take a new install.", "adapter_deprecated");
335
+ }
336
+
337
+ // 3a. REVISE an existing DISABLED install into a new draft revision.
338
+ if (targetInstall !== undefined) {
339
+ const expectedRevision = input.expectedRevision ?? targetInstall.revision;
340
+ const reviseReq: ReviseIntegrationInstallRequest = {
341
+ apiVersion: V,
342
+ idempotencyKey,
343
+ expectedRevision,
344
+ body: {
345
+ installId: targetInstall.installId,
346
+ adapterVersion: adapter.adapterVersion,
347
+ configuration: draft.proposedConfiguration,
348
+ },
349
+ };
350
+ const reqCheck = validateReviseIntegrationInstallRequest(reviseReq);
351
+ if (!reqCheck.ok) {
352
+ return fail(correlationId, 422, "The constructed revise request failed contract validation.", "validation_failed");
353
+ }
354
+ const reviseCall = await callWs1(() => client.reviseIntegrationInstall(reviseReq), correlationId);
355
+ if (!reviseCall.ok) return reviseCall.result;
356
+ return finalize(
357
+ reviseCall.value,
358
+ correlationId,
359
+ adapter,
360
+ draft.proposedConfiguration,
361
+ expectedRevision,
362
+ targetInstall.installId,
363
+ targetAlreadyAdvanced,
364
+ );
365
+ }
366
+
367
+ // 3b. CREATE a NEW install, forced DISABLED by the pinned body literal.
368
+ const createReq: CreateIntegrationInstallRequest = {
369
+ apiVersion: V,
370
+ idempotencyKey,
371
+ expectedRevision: 0,
372
+ body: {
373
+ adapterId: draft.proposedAdapterId,
374
+ adapterVersion: adapter.adapterVersion,
375
+ configuration: draft.proposedConfiguration,
376
+ status: "disabled",
377
+ },
378
+ };
379
+ const createCheck = validateCreateIntegrationInstallRequest(createReq);
380
+ if (!createCheck.ok) {
381
+ return fail(correlationId, 422, "The constructed install request failed contract validation.", "validation_failed");
382
+ }
383
+ const createCall = await callWs1(() => client.createIntegrationInstall(createReq), correlationId);
384
+ if (!createCall.ok) return createCall.result;
385
+ return finalize(createCall.value, correlationId, adapter, draft.proposedConfiguration);
386
+ }
387
+
388
+ /**
389
+ * Validate a create/revise mutation response and REFUSE anything that is not a
390
+ * disabled install with a draft revision — defense-in-depth so a misbehaving
391
+ * backend can never hand this path an enabled/active result. Creates require a
392
+ * coherent install revision / candidate sequence pair but do not assume `1`:
393
+ * WS1 may revive a tombstoned adapter at its preserved history's next revision.
394
+ */
395
+ function finalize(
396
+ res: CreateIntegrationInstallResponse | ReviseIntegrationInstallResponse,
397
+ correlationId: string,
398
+ adapter: AdapterDefinition,
399
+ expectedConfiguration: Readonly<Record<string, string | boolean | number>>,
400
+ expectedPriorRevision?: number,
401
+ expectedInstallId?: OpaquePublicId,
402
+ requireIdempotencyReplay = false,
403
+ ): ReviewInstallResult {
404
+ const prepared = prepareWs1ApiResponse<{ install: IntegrationInstall; revision: IntegrationRevisionV1 }>(
405
+ res,
406
+ correlationId,
407
+ "mutation",
408
+ );
409
+ if (!prepared.ok) return prepared.result;
410
+ const idempotencyReplayed = prepared.value["idempotencyReplayed"];
411
+ if (typeof idempotencyReplayed !== "boolean") {
412
+ return fail(correlationId, 502, "The Integration Management API returned an invalid mutation response.", "internal_error");
413
+ }
414
+ const installCheck = validateIntegrationInstall(prepared.value.data.install);
415
+ const revisionCheck = validateIntegrationRevisionV1(prepared.value.data.revision);
416
+ if (!installCheck.ok || !revisionCheck.ok) {
417
+ return fail(correlationId, 502, "The install response failed contract validation.", "internal_error");
418
+ }
419
+ if (installCheck.value.status !== "disabled") {
420
+ return fail(correlationId, 409, "Refusing a result whose install is not disabled.", "safety_refused");
421
+ }
422
+ if (revisionCheck.value.status !== "draft") {
423
+ return fail(correlationId, 409, "Refusing a result whose revision is not a draft.", "safety_refused");
424
+ }
425
+ if (
426
+ installCheck.value.adapterId !== adapter.adapterId ||
427
+ revisionCheck.value.adapterId !== adapter.adapterId ||
428
+ revisionCheck.value.adapterVersion !== adapter.adapterVersion ||
429
+ revisionCheck.value.installId !== installCheck.value.installId ||
430
+ stableIntegrationStringify(revisionCheck.value.configuration) !== stableIntegrationStringify(expectedConfiguration) ||
431
+ (requireIdempotencyReplay && !idempotencyReplayed) ||
432
+ installCheck.value.revision !== revisionCheck.value.sequence ||
433
+ (expectedPriorRevision !== undefined && installCheck.value.revision !== expectedPriorRevision + 1) ||
434
+ (expectedInstallId !== undefined && installCheck.value.installId !== expectedInstallId) ||
435
+ (expectedInstallId === undefined && installCheck.value.activeRevisionId !== null)
436
+ ) {
437
+ return fail(correlationId, 409, "Refusing an install result that differs from the reviewed candidate.", "safety_refused");
438
+ }
439
+ return {
440
+ ok: true,
441
+ correlationId,
442
+ install: installCheck.value,
443
+ revision: revisionCheck.value,
444
+ adapter,
445
+ idempotencyReplayed,
446
+ };
447
+ }