@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
@@ -9,18 +9,29 @@
9
9
  * event, so the two concerns stay separate: analytics stays anonymized, and
10
10
  * this stays a narrow, owner-only-readable accountability record.
11
11
  *
12
- * `sub` (the tot20-issued, stable user id) would be a better key than email —
13
- * email is a mutable account attribute, not the identity itself — but `sub`
14
- * is dropped before `SessionRecord` today (see `auth/session.ts`). Threading
15
- * it through is a real follow-up, not done here; email is what every call site
16
- * already has.
12
+ * MCP-submitted candidates carry the tot20-issued stable subject plus a verified
13
+ * email/display label. Storefront-local UI actions still use their authenticated
14
+ * session email until the same subject is threaded through `SessionRecord`.
17
15
  */
18
16
  import { displayNameFromEmail } from "./recordActivity";
19
17
 
20
18
  export type ChangeActorAction = "submit" | "reject" | "resolve" | "accept" | "ship" | "build";
21
19
 
20
+ export interface ChangeActorIdentity {
21
+ kind: "human" | "service";
22
+ /** Immutable identity-provider subject for a person, or app subject for a service. */
23
+ subject: string;
24
+ /** Present only for a verified human identity. */
25
+ email: string | null;
26
+ /** Owner-facing label. */
27
+ name: string;
28
+ }
29
+
22
30
  export interface ChangeActorRecord {
23
- email: string;
31
+ /** Older records may predate structured identity and omit these two fields. */
32
+ kind?: ChangeActorIdentity["kind"];
33
+ subject?: string;
34
+ email: string | null;
24
35
  /** Best-effort display name — no real name is captured anywhere today, so
25
36
  * this is always {@link displayNameFromEmail}'s local-part guess. Swap the
26
37
  * source here (not the callers) if a real name ever becomes available. */
@@ -37,6 +48,14 @@ function attributionKey(tenantId: string, subjectId: string, action: ChangeActor
37
48
  return `cust:change-actor:${tenantId}:${subjectId}:${action}`;
38
49
  }
39
50
 
51
+ function submitAuthorizationKey(
52
+ tenantId: string,
53
+ changeId: string,
54
+ authorizationId: string,
55
+ ): string {
56
+ return `cust:change-actor-authorization:${tenantId}:${changeId}:${authorizationId}`;
57
+ }
58
+
40
59
  /**
41
60
  * Persist who took `action` on `subjectId` (a `changeId` for reject/accept/build,
42
61
  * or the aggregate id for ship). Best-effort — never blocks or fails the
@@ -51,6 +70,8 @@ export async function recordChangeActor(
51
70
  ): Promise<void> {
52
71
  try {
53
72
  const record: ChangeActorRecord = {
73
+ kind: "human",
74
+ subject: `email:${email.trim().toLowerCase()}`,
54
75
  email,
55
76
  name: displayNameFromEmail(email),
56
77
  at: new Date().toISOString(),
@@ -61,6 +82,65 @@ export async function recordChangeActor(
61
82
  }
62
83
  }
63
84
 
85
+ /**
86
+ * Persist an actor as a required part of a privileged mutation's authorization
87
+ * boundary. Unlike {@link recordChangeActor}, storage failures propagate so the
88
+ * caller can fail closed before performing the mutation.
89
+ */
90
+ export async function recordRequiredChangeActor(
91
+ kv: ChangeActorKv,
92
+ tenantId: string,
93
+ subjectId: string,
94
+ action: ChangeActorAction,
95
+ actor: ChangeActorIdentity,
96
+ at: string,
97
+ ): Promise<void> {
98
+ const record: ChangeActorRecord = { ...actor, at };
99
+ await kv.put(attributionKey(tenantId, subjectId, action), JSON.stringify(record));
100
+ }
101
+
102
+ /**
103
+ * Stage an immutable, per-attempt submit authorization before the forge write.
104
+ * This deliberately does not replace the canonical `submit` actor: a failed
105
+ * Gitea update must never make an older PR look like it was submitted by the
106
+ * actor whose later attempt failed.
107
+ */
108
+ export async function recordCandidateSubmitAuthorization(
109
+ kv: ChangeActorKv,
110
+ tenantId: string,
111
+ changeId: string,
112
+ authorizationId: string,
113
+ actor: ChangeActorIdentity,
114
+ authorizedAt: string,
115
+ ): Promise<void> {
116
+ const record: ChangeActorRecord = { ...actor, at: authorizedAt };
117
+ await kv.put(
118
+ submitAuthorizationKey(tenantId, changeId, authorizationId),
119
+ JSON.stringify(record),
120
+ );
121
+ }
122
+
123
+ /** Read one exact pre-forge authorization. Missing/corrupt data fails closed. */
124
+ export async function readCandidateSubmitAuthorization(
125
+ kv: ChangeActorKv,
126
+ tenantId: string,
127
+ changeId: string,
128
+ authorizationId: string,
129
+ ): Promise<ChangeActorRecord | null> {
130
+ try {
131
+ const raw = await kv.get(submitAuthorizationKey(tenantId, changeId, authorizationId));
132
+ return raw ? JSON.parse(raw) as ChangeActorRecord : null;
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ /** Browser responses never need the immutable identity-provider subject. */
139
+ export function ownerVisibleChangeActor(record: ChangeActorRecord): ChangeActorRecord {
140
+ const { subject: _subject, ...visible } = record;
141
+ return visible;
142
+ }
143
+
64
144
  /** Read every recorded action's attribution for one subject. Best-effort per
65
145
  * action: a missing/corrupt record is simply absent from the result, never
66
146
  * a thrown error. */
@@ -101,7 +101,7 @@ export function observeDeployVersion(
101
101
  input: DeployVersionObservedInput = {},
102
102
  ): ActivityEvent | undefined {
103
103
  if (observedThisIsolate) return undefined;
104
- // Kill switch (D8): telemetry off ⇒ skip the observation entirely (fail-open). Do
104
+ // Kill switch: telemetry off ⇒ skip the observation entirely (fail-open). Do
105
105
  // NOT set the per-isolate guard, so re-enabling emits the observation on the next call.
106
106
  if (isActivityDisabled()) return undefined;
107
107
  observedThisIsolate = true;
@@ -22,6 +22,7 @@ const TAB_IDS = [
22
22
  "ai-workflows",
23
23
  "blog",
24
24
  "publish",
25
+ "integrations",
25
26
  "settings",
26
27
  ] as const;
27
28
 
@@ -35,6 +36,7 @@ const ASK_CONTEXTS: Record<string, string> = {
35
36
  "ai-workflows": "AI Workflows",
36
37
  blog: "Blog",
37
38
  publish: "Publish",
39
+ integrations: "Integrations",
38
40
  settings: "Settings",
39
41
  };
40
42
 
@@ -4,7 +4,6 @@
4
4
  * staff-override authorization (`resolveAdminTenantMode`) that admin.astro
5
5
  * actually runs. This module re-exports that type so every mock service under
6
6
  * `lib/admin/**` can keep importing it from a stable relative path
7
- * (`../../envelope`) without declaring a second, competing interface (decision
8
- * `envelope-type-duplication-follow-up`).
7
+ * (`../../envelope`) without declaring a second, competing interface.
9
8
  */
10
9
  export type { AdminTenantEnvelope } from "@/lib/adminTenantEnvelope";
@@ -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
+ }