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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/apps/storefront/astro.config.mjs +3 -3
  2. package/apps/storefront/env.d.ts +54 -5
  3. package/apps/storefront/integrations/materialize-guard.mjs +12 -14
  4. package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
  5. package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
  6. package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
  7. package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
  8. package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
  9. package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
  10. package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
  11. package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
  12. package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
  13. package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
  14. package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
  15. package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
  16. package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
  17. package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
  18. package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
  19. package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
  20. package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
  21. package/apps/storefront/public/shared/commerce-chrome.css +20 -2
  22. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
  23. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
  24. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
  25. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
  26. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
  27. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
  28. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
  29. package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
  30. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
  31. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
  32. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
  33. package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
  34. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
  35. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
  36. package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
  37. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
  38. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
  39. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
  40. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
  41. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
  42. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
  43. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
  44. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
  45. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
  46. package/apps/storefront/src/components/Img.astro +1 -1
  47. package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
  48. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
  49. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
  50. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  51. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
  52. package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
  53. package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
  54. package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
  55. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
  56. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
  57. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
  58. package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
  59. package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
  60. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
  61. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
  62. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
  63. package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
  64. package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
  65. package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
  66. package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
  67. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
  68. package/apps/storefront/src/components/membership/TierCards.astro +2 -2
  69. package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
  70. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
  71. package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
  72. package/apps/storefront/src/config/devTenantSeed.ts +9 -9
  73. package/apps/storefront/src/config/resolver.ts +4 -4
  74. package/apps/storefront/src/layouts/Layout.astro +100 -20
  75. package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
  76. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
  77. package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
  78. package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
  79. package/apps/storefront/src/lib/admin/envelope.ts +1 -2
  80. package/apps/storefront/src/lib/apps/adminService.ts +10 -0
  81. package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
  82. package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
  83. package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
  84. package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
  85. package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
  86. package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
  87. package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
  88. package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
  89. package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
  90. package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
  91. package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
  92. package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
  93. package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
  94. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
  95. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
  96. package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
  97. package/apps/storefront/src/lib/apps/registryService.ts +264 -10
  98. package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
  99. package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
  100. package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
  101. package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
  102. package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
  103. package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
  104. package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
  105. package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
  106. package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
  107. package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
  108. package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
  109. package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
  110. package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
  111. package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
  112. package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
  113. package/apps/storefront/src/lib/blog/collection.ts +97 -10
  114. package/apps/storefront/src/lib/blog/markdown.ts +1 -1
  115. package/apps/storefront/src/lib/blog/provider.ts +57 -3
  116. package/apps/storefront/src/lib/blog/types.ts +9 -9
  117. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
  118. package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
  119. package/apps/storefront/src/lib/chrome/model.ts +48 -253
  120. package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
  121. package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
  122. package/apps/storefront/src/lib/chrome/variant.ts +39 -0
  123. package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
  124. package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
  125. package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
  126. package/apps/storefront/src/lib/content/index.ts +5 -5
  127. package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
  128. package/apps/storefront/src/lib/content-edit/route.ts +1 -1
  129. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  130. package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
  131. package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
  132. package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
  133. package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
  134. package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
  135. package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
  136. package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
  137. package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
  138. package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
  139. package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
  140. package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
  141. package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
  142. package/apps/storefront/src/lib/edgeCache.ts +18 -6
  143. package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
  144. package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
  145. package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
  146. package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
  147. package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
  148. package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
  149. package/apps/storefront/src/lib/membership/model.ts +1 -2
  150. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
  151. package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
  152. package/apps/storefront/src/lib/newsletter/send.ts +3 -3
  153. package/apps/storefront/src/lib/previewReload.ts +57 -1
  154. package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
  155. package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
  156. package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
  157. package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
  158. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
  159. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
  160. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
  161. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
  162. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
  163. package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
  164. package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
  165. package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
  166. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
  167. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
  168. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
  169. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
  170. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
  171. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
  172. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
  173. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
  174. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
  175. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
  176. package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
  177. package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
  178. package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
  179. package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
  180. package/apps/storefront/src/lib/rawChrome.ts +125 -47
  181. package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
  182. package/apps/storefront/src/lib/seo/alternates.ts +1 -1
  183. package/apps/storefront/src/lib/social/notify.ts +4 -4
  184. package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
  185. package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
  186. package/apps/storefront/src/lib/subscription/model.ts +4 -4
  187. package/apps/storefront/src/lib/the-build/provider.ts +9 -4
  188. package/apps/storefront/src/middleware/index.ts +273 -51
  189. package/apps/storefront/src/pages/[...slug].astro +9 -4
  190. package/apps/storefront/src/pages/admin/review-release.astro +265 -0
  191. package/apps/storefront/src/pages/admin/select.astro +96 -0
  192. package/apps/storefront/src/pages/admin.astro +16 -2
  193. package/apps/storefront/src/pages/api/activity.ts +8 -8
  194. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
  195. package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
  196. package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
  197. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
  198. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
  199. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
  200. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
  201. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
  202. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
  203. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
  204. package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
  205. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
  206. package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
  207. package/apps/storefront/src/pages/blog/[slug].astro +5 -5
  208. package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
  209. package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
  210. package/apps/storefront/src/pages/blog/index.astro +2 -2
  211. package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
  212. package/apps/storefront/src/pages/cockpit.astro +61 -1362
  213. package/apps/storefront/src/pages/index.astro +10 -5
  214. package/apps/storefront/src/pages/llms.txt.ts +1 -1
  215. package/apps/storefront/src/pages/products/[handle].astro +7 -6
  216. package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
  217. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
  218. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
  219. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
  220. package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
  221. package/apps/storefront/src/pages/the-build/index.astro +2 -2
  222. package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
  223. package/apps/storefront/src/styles/admin.css +27 -1
  224. package/apps/storefront/tsconfig.json +1 -1
  225. package/package.json +1 -1
  226. package/packages/public-runtime/package.json +2 -1
  227. package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
  228. package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
  229. package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
  230. package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
  231. package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
  232. package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
  233. package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
  234. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
  235. package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
  236. package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
  237. package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
  238. package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
  239. package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
  240. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
  241. package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
  242. package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
  243. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
  244. package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
  245. package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
  246. package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
  247. package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
  248. package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
  249. package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
  250. package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
  251. package/packages/public-runtime/src/activity/catalog.ts +41 -37
  252. package/packages/public-runtime/src/activity/event.ts +6 -6
  253. package/packages/public-runtime/src/activity/index.ts +2 -2
  254. package/packages/public-runtime/src/activity/redaction.ts +4 -4
  255. package/packages/public-runtime/src/candidate-index.ts +42 -8
  256. package/packages/public-runtime/src/checkout-orders.ts +2 -2
  257. package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
  258. package/packages/public-runtime/src/checkout-style.ts +7 -7
  259. package/packages/public-runtime/src/checkout.ts +3 -3
  260. package/packages/public-runtime/src/chrome.ts +777 -0
  261. package/packages/public-runtime/src/compliance-obligations.ts +310 -0
  262. package/packages/public-runtime/src/csp.ts +99 -0
  263. package/packages/public-runtime/src/customization-runtime.ts +32 -1
  264. package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
  265. package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
  266. package/packages/public-runtime/src/embed-catalog.mjs +40 -0
  267. package/packages/public-runtime/src/evidence.ts +288 -0
  268. package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
  269. package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
  270. package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
  271. package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
  272. package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
  273. package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
  274. package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
  275. package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
  276. package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
  277. package/packages/public-runtime/src/index.ts +16 -1
  278. package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
  279. package/packages/public-runtime/src/integration/assurance.ts +228 -0
  280. package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
  281. package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
  282. package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
  283. package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
  284. package/packages/public-runtime/src/integration/consent-config.ts +260 -0
  285. package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
  286. package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
  287. package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
  288. package/packages/public-runtime/src/integration/csp.ts +105 -0
  289. package/packages/public-runtime/src/integration/digest.ts +43 -0
  290. package/packages/public-runtime/src/integration/environment.ts +228 -0
  291. package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
  292. package/packages/public-runtime/src/integration/events.ts +701 -0
  293. package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
  294. package/packages/public-runtime/src/integration/index.ts +90 -0
  295. package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
  296. package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
  297. package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
  298. package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
  299. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
  300. package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
  301. package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
  302. package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
  303. package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
  304. package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
  305. package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
  306. package/packages/public-runtime/src/integration/manifest.ts +470 -0
  307. package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
  308. package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
  309. package/packages/public-runtime/src/integration/observability.ts +451 -0
  310. package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
  311. package/packages/public-runtime/src/integration/phases.ts +182 -0
  312. package/packages/public-runtime/src/integration/plan.ts +112 -0
  313. package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
  314. package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
  315. package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
  316. package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
  317. package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
  318. package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
  319. package/packages/public-runtime/src/integration/render-phases.ts +267 -0
  320. package/packages/public-runtime/src/integration/resolve.ts +465 -0
  321. package/packages/public-runtime/src/integration/result.ts +239 -0
  322. package/packages/public-runtime/src/integration/secrets.ts +92 -0
  323. package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
  324. package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
  325. package/packages/public-runtime/src/integration/version.ts +92 -0
  326. package/packages/public-runtime/src/integration-capabilities.ts +520 -0
  327. package/packages/public-runtime/src/membership.ts +2 -2
  328. package/packages/public-runtime/src/raw-html-policy.ts +2 -4
  329. package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
  330. package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
  331. package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
  332. package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
  333. package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
  334. package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
  335. package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
  336. package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
  337. package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
  338. package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
  339. package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
  340. package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
  341. package/packages/public-runtime/src/static-bundle.ts +5 -5
  342. package/packages/public-runtime/src/tenant-assets.ts +143 -4
  343. package/packages/public-runtime/src/tenant.ts +45 -9
  344. package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
  345. package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
  346. package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
  347. package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
  348. package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
  349. package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
  350. package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
  351. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
  352. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
  353. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
  354. package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
  355. package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
  356. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
  357. package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
  358. package/scripts/dev/file-browser.mjs +2 -2
  359. package/scripts/dev/file-writer.mjs +2 -2
  360. package/scripts/dev/integration-guardrails.mjs +498 -0
  361. package/scripts/dev/safe-path.mjs +2 -2
  362. package/scripts/dev/tenant-source-root.mjs +1 -1
  363. package/scripts/tot-dev.mjs +2 -2
@@ -0,0 +1,465 @@
1
+ /**
2
+ * The server-computed effective-plan resolver (ADR 0014 C1
3
+ * `integration-schema-shared`, S1/S2 `integration-phases-fixed-dag`; ext-kernel-03).
4
+ *
5
+ * One resolver merges the three declaration origins — platform adapters,
6
+ * tenant-local declarations, and installed managed apps — into a single immutable
7
+ * {@link EffectivePlan} for one tenant/environment/version. It is a PURE,
8
+ * SYNCHRONOUS function over already-materialized inputs: it performs no network,
9
+ * disk, or KV access, so it is safe on the render hot path. Materializing the
10
+ * declarations (Gitea/KV/D1 → in-memory) is the control plane's job; resolving
11
+ * them into a plan is this function's, and the two never blur.
12
+ *
13
+ * The resolver is FAIL-CLOSED: an invalid manifest, a missing entry asset (a
14
+ * partially materialized version), a secret-bearing or off-schema public config,
15
+ * a duplicate app, or an unresolvable order yields violations and NO plan — never
16
+ * a partial one. It is DETERMINISTIC: the same set of declarations resolves to a
17
+ * byte-identical plan (including `planRevision`) regardless of input array order,
18
+ * and a plan-affecting change to any input changes `planRevision`, which is the
19
+ * component the candidate/edge-cache identity folds in.
20
+ */
21
+ import type { ContractResult, ContractViolation } from "./result.js";
22
+ import { Violations, isPlainObject, joinPath } from "./result.js";
23
+ import { EFFECTIVE_PLAN_SCHEMA_VERSION } from "./version.js";
24
+ import type { BrowserEnvironment } from "./environment.js";
25
+ import { validateBrowserEnvironment } from "./environment.js";
26
+ import { phaseRank, type ReservedPhase } from "./phases.js";
27
+ import {
28
+ validateIntegrationManifest,
29
+ INTEGRATION_ORIGINS,
30
+ type IntegrationManifest,
31
+ type IntegrationOrigin,
32
+ type IntegrationDependency,
33
+ type IntegrationContribution,
34
+ } from "./manifest.js";
35
+ import type {
36
+ EffectivePlan,
37
+ PlanIntegration,
38
+ NormalizedContribution,
39
+ SourceReference,
40
+ } from "./plan.js";
41
+ import type { CommerceEventType } from "./events.js";
42
+ import { fnv1a128Hex, stableStringify } from "./digest.js";
43
+ import { assertPlanBrowserSafe } from "./plan.js";
44
+ import {
45
+ INTEGRATION_CSP_DIRECTIVES,
46
+ validateCspSourceSet,
47
+ type CspSourceSet,
48
+ } from "./csp.js";
49
+ import { findForbiddenFields } from "./secrets.js";
50
+
51
+ /**
52
+ * One already-materialized declaration the resolver merges. The `manifest` is
53
+ * re-validated as defense-in-depth; `publicConfig` carries the resolved tenant
54
+ * values (never a schema); `materializedAssets` is the set of asset paths present
55
+ * for this integration's version — a missing entry asset blocks the whole plan.
56
+ */
57
+ export interface IntegrationDeclarationInput {
58
+ origin: IntegrationOrigin;
59
+ /** Declaration id, adapter id, or board slug this integration traces to. */
60
+ ref: string;
61
+ manifest: IntegrationManifest;
62
+ publicConfig: Record<string, unknown>;
63
+ materializedAssets: readonly string[];
64
+ }
65
+
66
+ /** The complete, already-materialized input for one tenant/environment/version. */
67
+ export interface ResolvePlanInput {
68
+ tenantPublicId: string;
69
+ environment: BrowserEnvironment;
70
+ declarations: readonly IntegrationDeclarationInput[];
71
+ }
72
+
73
+ const ORIGIN_RANK: Record<IntegrationOrigin, number> = {
74
+ "platform-adapter": 0,
75
+ "tenant-local": 1,
76
+ "managed-app": 2,
77
+ };
78
+
79
+ /**
80
+ * Resolve already-materialized declarations into one immutable effective plan.
81
+ * Returns `{ ok: true, value }` only when every input validates and every entry
82
+ * asset is present; otherwise `{ ok: false, violations }` with no plan.
83
+ */
84
+ export function resolveEffectivePlan(
85
+ input: ResolvePlanInput,
86
+ ): ContractResult<EffectivePlan> {
87
+ const v = new Violations();
88
+
89
+ const envResult = validateBrowserEnvironment(input.environment);
90
+ if (!envResult.ok) {
91
+ for (const violation of prefix(envResult.violations, "environment")) v.items.push(violation);
92
+ }
93
+
94
+ if (typeof input.tenantPublicId !== "string" || input.tenantPublicId.length === 0) {
95
+ v.add("tenantPublicId", "type", "tenantPublicId must be a non-empty string.");
96
+ }
97
+
98
+ if (!Array.isArray(input.declarations)) {
99
+ v.add("declarations", "type", "declarations must be an array.");
100
+ return { ok: false, violations: v.items };
101
+ }
102
+
103
+ const seenAppIds = new Set<string>();
104
+ input.declarations.forEach((decl, i) => {
105
+ validateDeclaration(decl, i, seenAppIds, v);
106
+ });
107
+
108
+ if (!v.ok) return { ok: false, violations: v.items };
109
+
110
+ // Every declaration validated: build the plan. Canonical ordering by
111
+ // (origin, appId, version) makes the output independent of input array order.
112
+ const sorted = [...input.declarations].sort(compareDeclarations);
113
+
114
+ const contributions: NormalizedContribution[] = [];
115
+ const orderNodes: OrderNode[] = [];
116
+ const orderEdges: OrderEdge[] = [];
117
+ const dependencies: IntegrationDependency[] = [];
118
+ const integrations: PlanIntegration[] = [];
119
+ const provenance: SourceReference[] = [];
120
+ const publicConfig: Record<string, Record<string, unknown>> = {};
121
+ const consentSet = new Set<string>();
122
+ const cspMerge = new Map<string, Set<string>>();
123
+
124
+ sorted.forEach((decl, integrationIndex) => {
125
+ const { app, browser } = decl.manifest;
126
+ const source: SourceReference = {
127
+ origin: decl.origin,
128
+ ref: decl.ref,
129
+ appId: app.id,
130
+ version: app.version,
131
+ digest: app.digest,
132
+ };
133
+ provenance.push(source);
134
+ integrations.push({
135
+ origin: decl.origin,
136
+ appId: app.id,
137
+ version: app.version,
138
+ digest: app.digest,
139
+ executionTier: browser.executionTier,
140
+ // The declared entry asset — validated present in `materializedAssets` by
141
+ // validateDeclaration — is the exact-version module the runtime imports.
142
+ entry: {
143
+ url: browser.entry.asset,
144
+ format: browser.entry.format,
145
+ phase: browser.entry.phase,
146
+ },
147
+ consent: {
148
+ purposes: [...browser.consent.purposes],
149
+ loadWhen: browser.consent.loadWhen,
150
+ },
151
+ // Every declared event was cross-checked browser-deliverable by
152
+ // validateIntegrationManifest above (an unknown/reserved/verified-provider type
153
+ // fails the plan), so the set is safe to narrow to CommerceEventType for the wire.
154
+ events: [...browser.permissions.events] as CommerceEventType[],
155
+ });
156
+ publicConfig[app.id] = { ...decl.publicConfig };
157
+
158
+ for (const purpose of browser.consent.purposes) consentSet.add(purpose);
159
+ for (const directive of INTEGRATION_CSP_DIRECTIVES) {
160
+ const origins = browser.csp.sources[directive];
161
+ if (!origins || origins.length === 0) continue;
162
+ let set = cspMerge.get(directive);
163
+ if (!set) cspMerge.set(directive, (set = new Set()));
164
+ for (const origin of origins) set.add(origin);
165
+ }
166
+
167
+ const localContributionIds = new Set<string>();
168
+ browser.contributions.forEach((contribution: IntegrationContribution, declIndex: number) => {
169
+ const id = globalId(app.id, app.version, contribution.id);
170
+ contributions.push({
171
+ id,
172
+ origin: decl.origin,
173
+ phase: contribution.phase,
174
+ kind: contribution.kind,
175
+ source,
176
+ });
177
+ orderNodes.push({ id, phase: contribution.phase, integrationIndex, declIndex });
178
+ localContributionIds.add(contribution.id);
179
+ });
180
+
181
+ // Carry every declared edge (including entry-referencing ones) namespaced for
182
+ // the render engine; constrain `order` only by contribution-to-contribution
183
+ // edges, since `order` is over contribution ids.
184
+ for (const dep of browser.dependencies) {
185
+ dependencies.push({
186
+ contribution: globalId(app.id, app.version, dep.contribution),
187
+ after: globalId(app.id, app.version, dep.after),
188
+ });
189
+ if (localContributionIds.has(dep.contribution) && localContributionIds.has(dep.after)) {
190
+ orderEdges.push({
191
+ dependent: globalId(app.id, app.version, dep.contribution),
192
+ prerequisite: globalId(app.id, app.version, dep.after),
193
+ });
194
+ }
195
+ }
196
+ });
197
+
198
+ const order = resolveOrder(orderNodes, orderEdges);
199
+ if (order.length !== orderNodes.length) {
200
+ v.add("order", "cycle", "Contribution order could not be resolved; the graph is not acyclic.");
201
+ return { ok: false, violations: v.items };
202
+ }
203
+
204
+ const cspNeeds: CspSourceSet = {};
205
+ for (const directive of INTEGRATION_CSP_DIRECTIVES) {
206
+ const set = cspMerge.get(directive);
207
+ if (set && set.size > 0) cspNeeds[directive] = [...set].sort();
208
+ }
209
+ validateCspSourceSet(cspNeeds, v, "cspNeeds");
210
+
211
+ const planBody: Omit<EffectivePlan, "planRevision"> = {
212
+ schemaVersion: EFFECTIVE_PLAN_SCHEMA_VERSION,
213
+ environment: envResult.ok ? envResult.value : input.environment,
214
+ tenantPublicId: input.tenantPublicId,
215
+ integrations,
216
+ contributions,
217
+ order,
218
+ publicConfig,
219
+ dependencies,
220
+ consentNeeds: [...consentSet].sort(),
221
+ cspNeeds,
222
+ provenance,
223
+ };
224
+
225
+ const planRevision = computePlanRevision(planBody);
226
+ const plan: EffectivePlan = { ...planBody, planRevision };
227
+
228
+ // Defense-in-depth: the finished plan must carry no secret material.
229
+ for (const violation of assertPlanBrowserSafe(plan)) v.items.push(violation);
230
+
231
+ if (!v.ok) return { ok: false, violations: v.items };
232
+ return { ok: true, value: plan };
233
+ }
234
+
235
+ /**
236
+ * Resolve the environment-only effective plan: a valid {@link BrowserEnvironment}
237
+ * with NO integration declarations. The render engine consumes this on every
238
+ * request until declaration materialization is wired — it carries the environment,
239
+ * a stable `planRevision`, and empty contributions, so a page with no integration
240
+ * declaration emits no vendor runtime/markup. Throws (fail closed) only on the
241
+ * impossible case of an invalid environment.
242
+ */
243
+ export function resolveEnvironmentOnlyPlan(
244
+ environment: BrowserEnvironment,
245
+ tenantPublicId: string,
246
+ ): EffectivePlan {
247
+ const result = resolveEffectivePlan({ tenantPublicId, environment, declarations: [] });
248
+ if (!result.ok) {
249
+ throw new Error(
250
+ `resolveEnvironmentOnlyPlan: environment-only plan failed to resolve: ` +
251
+ result.violations.map((x) => `${x.path}: ${x.message}`).join("; "),
252
+ );
253
+ }
254
+ return result.value;
255
+ }
256
+
257
+ /**
258
+ * The stable identity component the candidate/edge-cache key folds in. It changes
259
+ * when the tenant, environment stage, version revision, or the resolved plan
260
+ * itself changes — so a plan change invalidates a cached response and a removed
261
+ * version leaves no matching key.
262
+ */
263
+ export function planCacheIdentity(plan: EffectivePlan): string {
264
+ return `${plan.tenantPublicId}:${plan.environment.environment}:${plan.environment.revision}:${plan.planRevision}`;
265
+ }
266
+
267
+ interface OrderNode {
268
+ id: string;
269
+ phase: ReservedPhase;
270
+ integrationIndex: number;
271
+ declIndex: number;
272
+ }
273
+
274
+ interface OrderEdge {
275
+ dependent: string;
276
+ prerequisite: string;
277
+ }
278
+
279
+ function validateDeclaration(
280
+ decl: IntegrationDeclarationInput,
281
+ index: number,
282
+ seenAppIds: Set<string>,
283
+ v: Violations,
284
+ ): void {
285
+ const path = `declarations[${index}]`;
286
+ if (!isPlainObject(decl)) {
287
+ v.add(path, "type", "Each declaration must be an object.");
288
+ return;
289
+ }
290
+
291
+ if (!(INTEGRATION_ORIGINS as readonly string[]).includes(decl.origin)) {
292
+ v.add(`${path}.origin`, "unknown_enum", `origin must be one of: ${INTEGRATION_ORIGINS.join(", ")}.`);
293
+ }
294
+ if (typeof decl.ref !== "string" || decl.ref.length === 0) {
295
+ v.add(`${path}.ref`, "type", "ref must be a non-empty string.");
296
+ }
297
+ if (!Array.isArray(decl.materializedAssets)) {
298
+ v.add(`${path}.materializedAssets`, "type", "materializedAssets must be an array of asset paths.");
299
+ }
300
+ if (!isPlainObject(decl.publicConfig)) {
301
+ v.add(`${path}.publicConfig`, "type", "publicConfig must be an object of resolved tenant values.");
302
+ }
303
+
304
+ const manifestResult = validateIntegrationManifest(decl.manifest);
305
+ if (!manifestResult.ok) {
306
+ for (const violation of prefix(manifestResult.violations, `${path}.manifest`)) v.items.push(violation);
307
+ return; // A malformed manifest is unsafe to inspect further.
308
+ }
309
+
310
+ const manifest = manifestResult.value;
311
+ const appId = manifest.app.id;
312
+ if (seenAppIds.has(appId)) {
313
+ v.add(`${path}.manifest.app.id`, "duplicate", `Duplicate integration "${appId}"; an app resolves at most once.`);
314
+ }
315
+ seenAppIds.add(appId);
316
+
317
+ // Fail closed on a partially materialized version: the declared entry module
318
+ // must be present before the integration can be served.
319
+ const assets = Array.isArray(decl.materializedAssets) ? decl.materializedAssets : [];
320
+ if (!assets.includes(manifest.browser.entry.asset)) {
321
+ v.add(
322
+ `${path}.manifest.browser.entry.asset`,
323
+ "missing_asset",
324
+ `Entry asset "${manifest.browser.entry.asset}" is not materialized; a partial version blocks the plan.`,
325
+ );
326
+ }
327
+
328
+ if (isPlainObject(decl.publicConfig)) {
329
+ validatePublicConfigValues(
330
+ manifest.browser.publicConfigSchema,
331
+ decl.publicConfig,
332
+ v,
333
+ `${path}.publicConfig`,
334
+ );
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Validate resolved tenant public-config VALUES against the integration's closed
340
+ * public-config schema: no secret keys, no key outside the schema, every required
341
+ * key present, and each value's primitive type matching the schema.
342
+ */
343
+ function validatePublicConfigValues(
344
+ schema: Record<string, unknown>,
345
+ config: Record<string, unknown>,
346
+ v: Violations,
347
+ path: string,
348
+ ): void {
349
+ for (const violation of findForbiddenFields(config, path)) v.items.push(violation);
350
+
351
+ const propsRaw = schema["properties"];
352
+ const props = isPlainObject(propsRaw) ? propsRaw : {};
353
+ const allowed = new Set(Object.keys(props));
354
+ const required = Array.isArray(schema["required"])
355
+ ? (schema["required"] as unknown[]).filter((r): r is string => typeof r === "string")
356
+ : [];
357
+
358
+ for (const key of Object.keys(config)) {
359
+ if (!allowed.has(key)) {
360
+ v.add(joinPath(path, key), "unknown_field", `Public config key "${key}" is not in the integration schema; tenant config is closed.`);
361
+ continue;
362
+ }
363
+ const propSchema = props[key];
364
+ const expected =
365
+ isPlainObject(propSchema) && typeof propSchema["type"] === "string"
366
+ ? (propSchema["type"] as string)
367
+ : undefined;
368
+ if (!valueMatchesType(config[key], expected)) {
369
+ v.add(joinPath(path, key), "type", `Public config value "${key}" must be of type ${expected ?? "string|number|boolean"}.`);
370
+ }
371
+ }
372
+
373
+ for (const key of required) {
374
+ if (!(key in config)) {
375
+ v.add(joinPath(path, key), "type", `Required public config key "${key}" is missing.`);
376
+ }
377
+ }
378
+ }
379
+
380
+ function valueMatchesType(value: unknown, expected: string | undefined): boolean {
381
+ switch (expected) {
382
+ case "string":
383
+ return typeof value === "string";
384
+ case "boolean":
385
+ return typeof value === "boolean";
386
+ case "number":
387
+ return typeof value === "number" && Number.isFinite(value);
388
+ case "integer":
389
+ return typeof value === "number" && Number.isInteger(value);
390
+ default:
391
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
392
+ }
393
+ }
394
+
395
+ function compareDeclarations(a: IntegrationDeclarationInput, b: IntegrationDeclarationInput): number {
396
+ const ra = ORIGIN_RANK[a.origin] ?? Number.MAX_SAFE_INTEGER;
397
+ const rb = ORIGIN_RANK[b.origin] ?? Number.MAX_SAFE_INTEGER;
398
+ if (ra !== rb) return ra - rb;
399
+ const ida = a.manifest.app.id;
400
+ const idb = b.manifest.app.id;
401
+ if (ida !== idb) return ida < idb ? -1 : 1;
402
+ const va = a.manifest.app.version;
403
+ const vb = b.manifest.app.version;
404
+ return va < vb ? -1 : va > vb ? 1 : 0;
405
+ }
406
+
407
+ function globalId(appId: string, version: string, local: string): string {
408
+ return `${appId}@${version}#${local}`;
409
+ }
410
+
411
+ /**
412
+ * Deterministic topological order over contribution nodes: phase order first,
413
+ * then canonical integration order, then declaration order, subject to the
414
+ * contribution dependency edges. Returns fewer ids than nodes only if a cycle
415
+ * survived manifest validation (treated as a fail-closed error by the caller).
416
+ */
417
+ function resolveOrder(nodes: readonly OrderNode[], edges: readonly OrderEdge[]): string[] {
418
+ const byId = new Map(nodes.map((n) => [n.id, n]));
419
+ const indegree = new Map<string, number>(nodes.map((n) => [n.id, 0]));
420
+ const dependents = new Map<string, string[]>();
421
+
422
+ for (const edge of edges) {
423
+ if (!byId.has(edge.dependent) || !byId.has(edge.prerequisite)) continue;
424
+ indegree.set(edge.dependent, (indegree.get(edge.dependent) ?? 0) + 1);
425
+ const list = dependents.get(edge.prerequisite);
426
+ if (list) list.push(edge.dependent);
427
+ else dependents.set(edge.prerequisite, [edge.dependent]);
428
+ }
429
+
430
+ const compare = (a: string, b: string): number => {
431
+ const na = byId.get(a)!;
432
+ const nb = byId.get(b)!;
433
+ const pa = phaseRank(na.phase);
434
+ const pb = phaseRank(nb.phase);
435
+ if (pa !== pb) return pa - pb;
436
+ if (na.integrationIndex !== nb.integrationIndex) return na.integrationIndex - nb.integrationIndex;
437
+ if (na.declIndex !== nb.declIndex) return na.declIndex - nb.declIndex;
438
+ return a < b ? -1 : a > b ? 1 : 0;
439
+ };
440
+
441
+ const ready = nodes.filter((n) => (indegree.get(n.id) ?? 0) === 0).map((n) => n.id);
442
+ const out: string[] = [];
443
+ while (ready.length > 0) {
444
+ ready.sort(compare);
445
+ const id = ready.shift()!;
446
+ out.push(id);
447
+ for (const dependent of dependents.get(id) ?? []) {
448
+ const next = (indegree.get(dependent) ?? 0) - 1;
449
+ indegree.set(dependent, next);
450
+ if (next === 0) ready.push(dependent);
451
+ }
452
+ }
453
+ return out;
454
+ }
455
+
456
+ function computePlanRevision(planBody: Omit<EffectivePlan, "planRevision">): string {
457
+ return `pr1-${fnv1a128Hex(stableStringify(planBody))}`;
458
+ }
459
+
460
+ function prefix(violations: readonly ContractViolation[], path: string): ContractViolation[] {
461
+ return violations.map((violation) => ({
462
+ ...violation,
463
+ path: violation.path ? `${path}.${violation.path}` : path,
464
+ }));
465
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Shared validation core for the browser-integration public contracts.
3
+ *
4
+ * Every public contract is CLOSED and FAIL-CLOSED: unknown fields, unknown enum
5
+ * members, malformed shapes, and duplicates produce structured violations
6
+ * rather than being ignored or assumed safe. Validators accumulate violations
7
+ * with a JSON path so callers (CLI, docs, admin, evidence) can render
8
+ * actionable failures.
9
+ */
10
+
11
+ export interface ContractViolation {
12
+ /** JSON path to the offending value, e.g. `browser.contributions[1].phase`. */
13
+ path: string;
14
+ /** Stable machine code, e.g. `unknown_field`, `unknown_enum`, `duplicate`. */
15
+ code: string;
16
+ message: string;
17
+ }
18
+
19
+ export type ContractResult<T> =
20
+ | { ok: true; value: T }
21
+ | { ok: false; violations: ContractViolation[] };
22
+
23
+ /**
24
+ * JSON-Schema annotation keywords permitted on any contract object. They carry
25
+ * no semantic contract data and never bear executable or secret material (a
26
+ * `$`-named secret is still caught by the secret scanner), so they are allowed
27
+ * without opening the closed field set.
28
+ */
29
+ export const ANNOTATION_KEYS: readonly string[] = ["$schema", "$comment", "$id"];
30
+
31
+ export class Violations {
32
+ readonly items: ContractViolation[] = [];
33
+ add(path: string, code: string, message: string): void {
34
+ this.items.push({ path, code, message });
35
+ }
36
+ get ok(): boolean {
37
+ return this.items.length === 0;
38
+ }
39
+ }
40
+
41
+ export function isPlainObject(value: unknown): value is Record<string, unknown> {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+
45
+ export function joinPath(path: string, key: string): string {
46
+ return path ? `${path}.${key}` : key;
47
+ }
48
+
49
+ export function rejectUnknownKeys(
50
+ obj: Record<string, unknown>,
51
+ allowed: readonly string[],
52
+ v: Violations,
53
+ path: string,
54
+ ): void {
55
+ const set = new Set([...allowed, ...ANNOTATION_KEYS]);
56
+ for (const key of Object.keys(obj)) {
57
+ if (!set.has(key)) {
58
+ v.add(
59
+ joinPath(path, key),
60
+ "unknown_field",
61
+ `Unknown field "${key}"; this contract is closed.`,
62
+ );
63
+ }
64
+ }
65
+ }
66
+
67
+ export function reqObject(
68
+ value: unknown,
69
+ v: Violations,
70
+ path: string,
71
+ ): Record<string, unknown> | undefined {
72
+ if (!isPlainObject(value)) {
73
+ v.add(path || "(root)", "type", "Expected an object.");
74
+ return undefined;
75
+ }
76
+ return value;
77
+ }
78
+
79
+ export function childObject(
80
+ obj: Record<string, unknown>,
81
+ key: string,
82
+ v: Violations,
83
+ path: string,
84
+ ): Record<string, unknown> | undefined {
85
+ return reqObject(obj[key], v, joinPath(path, key));
86
+ }
87
+
88
+ export function reqString(
89
+ obj: Record<string, unknown>,
90
+ key: string,
91
+ v: Violations,
92
+ path: string,
93
+ ): string | undefined {
94
+ const value = obj[key];
95
+ if (typeof value !== "string" || value.length === 0) {
96
+ v.add(joinPath(path, key), "type", `Expected a non-empty string at "${key}".`);
97
+ return undefined;
98
+ }
99
+ return value;
100
+ }
101
+
102
+ export function optString(
103
+ obj: Record<string, unknown>,
104
+ key: string,
105
+ v: Violations,
106
+ path: string,
107
+ ): string | undefined {
108
+ if (!(key in obj)) return undefined;
109
+ return reqString(obj, key, v, path);
110
+ }
111
+
112
+ export function reqEnum<T extends string>(
113
+ obj: Record<string, unknown>,
114
+ key: string,
115
+ allowed: readonly T[],
116
+ v: Violations,
117
+ path: string,
118
+ ): T | undefined {
119
+ const value = obj[key];
120
+ if (typeof value !== "string" || !(allowed as readonly string[]).includes(value)) {
121
+ v.add(
122
+ joinPath(path, key),
123
+ "unknown_enum",
124
+ `"${key}" must be one of: ${allowed.join(", ")}.`,
125
+ );
126
+ return undefined;
127
+ }
128
+ return value as T;
129
+ }
130
+
131
+ export function reqLiteral<T extends string | number | boolean>(
132
+ obj: Record<string, unknown>,
133
+ key: string,
134
+ expected: T,
135
+ v: Violations,
136
+ path: string,
137
+ ): T | undefined {
138
+ if (obj[key] !== expected) {
139
+ v.add(
140
+ joinPath(path, key),
141
+ "unsupported_version",
142
+ `"${key}" must equal ${JSON.stringify(expected)}.`,
143
+ );
144
+ return undefined;
145
+ }
146
+ return expected;
147
+ }
148
+
149
+ export function reqInt(
150
+ obj: Record<string, unknown>,
151
+ key: string,
152
+ v: Violations,
153
+ path: string,
154
+ ): number | undefined {
155
+ const value = obj[key];
156
+ if (typeof value !== "number" || !Number.isInteger(value)) {
157
+ v.add(joinPath(path, key), "type", `Expected an integer at "${key}".`);
158
+ return undefined;
159
+ }
160
+ return value;
161
+ }
162
+
163
+ export function reqBool(
164
+ obj: Record<string, unknown>,
165
+ key: string,
166
+ v: Violations,
167
+ path: string,
168
+ ): boolean | undefined {
169
+ const value = obj[key];
170
+ if (typeof value !== "boolean") {
171
+ v.add(joinPath(path, key), "type", `Expected a boolean at "${key}".`);
172
+ return undefined;
173
+ }
174
+ return value;
175
+ }
176
+
177
+ export function reqArray(
178
+ obj: Record<string, unknown>,
179
+ key: string,
180
+ v: Violations,
181
+ path: string,
182
+ ): unknown[] | undefined {
183
+ const value = obj[key];
184
+ if (!Array.isArray(value)) {
185
+ v.add(joinPath(path, key), "type", `Expected an array at "${key}".`);
186
+ return undefined;
187
+ }
188
+ return value;
189
+ }
190
+
191
+ export function reqStringArray(
192
+ obj: Record<string, unknown>,
193
+ key: string,
194
+ v: Violations,
195
+ path: string,
196
+ ): string[] | undefined {
197
+ const arr = reqArray(obj, key, v, path);
198
+ if (!arr) return undefined;
199
+ const out: string[] = [];
200
+ arr.forEach((item, i) => {
201
+ if (typeof item !== "string" || item.length === 0) {
202
+ v.add(`${joinPath(path, key)}[${i}]`, "type", "Expected a non-empty string.");
203
+ return;
204
+ }
205
+ out.push(item);
206
+ });
207
+ return out;
208
+ }
209
+
210
+ export function reqIsoTimestamp(
211
+ obj: Record<string, unknown>,
212
+ key: string,
213
+ v: Violations,
214
+ path: string,
215
+ ): string | undefined {
216
+ const value = reqString(obj, key, v, path);
217
+ if (value === undefined) return undefined;
218
+ if (!/\d{4}-\d{2}-\d{2}T/.test(value) || Number.isNaN(Date.parse(value))) {
219
+ v.add(joinPath(path, key), "format", `"${key}" must be an ISO-8601 timestamp.`);
220
+ return undefined;
221
+ }
222
+ return value;
223
+ }
224
+
225
+ /** Fail-closed on repeated values; duplicates are always rejected, never merged. */
226
+ export function assertUnique(
227
+ values: readonly string[],
228
+ v: Violations,
229
+ path: string,
230
+ label: string,
231
+ ): void {
232
+ const seen = new Set<string>();
233
+ values.forEach((value, i) => {
234
+ if (seen.has(value)) {
235
+ v.add(`${path}[${i}]`, "duplicate", `Duplicate ${label} "${value}"; duplicates are rejected.`);
236
+ }
237
+ seen.add(value);
238
+ });
239
+ }