@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
@@ -6,8 +6,8 @@
6
6
  * subscription mechanics; a tier's optional figure and CTA are display/handoff
7
7
  * only.
8
8
  *
9
- * Illustrative-figures affordance (decision compliance-widgets-platform-owned):
10
- * when any tier shows an illustrative figure the header carries a first-class
9
+ * Illustrative-figures affordance: when any tier shows an illustrative
10
+ * figure the header carries a first-class
11
11
  * <IllustrativeLabel> badge, each such figure gets an "e.g." affordance +
12
12
  * data-illustrative hook, and a footnote spells it out — an example number is
13
13
  * never presented as a live offer.
@@ -6,8 +6,8 @@
6
6
  * subscription mechanics — the CTA is a link into the tenant's own
7
7
  * (product-owned) subscribe/learn flow.
8
8
  *
9
- * Illustrative-figure affordance (decision compliance-widgets-platform-owned):
10
- * the figure is treated as an EXAMPLE by default (isIllustrative). When
9
+ * Illustrative-figure affordance: the figure is treated as an EXAMPLE by
10
+ * default (isIllustrative). When
11
11
  * illustrative, the figure carries an "e.g." affordance + a first-class
12
12
  * <IllustrativeLabel> badge/footnote so an example number is never presented
13
13
  * as a live offer. A tenant opts out only by asserting `real: true` on a
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Admin integration plane — arbitrary-tenant lookup provider (ext-kernel-09).
3
+ *
4
+ * The `/api/apps/admin/*` integration routes resolve a tenant named in the operator
5
+ * session (`auth.tenantId` — a vendor may administer a tenant that is NOT the
6
+ * request's own host scope, so `Astro.locals.tenant` does not apply). Materializing
7
+ * a tenant's OWN (tenant-local) declarations needs its full config (`compliance`,
8
+ * `siteType`, `integrations`), which requires the concrete resolver selection — and
9
+ * the `tenancy-resolver-fence` extractability invariant forbids pages/lib from
10
+ * importing that directly.
11
+ *
12
+ * This module lives in `src/config` (an approved location) and encapsulates
13
+ * `getTenantResolver()` + `staticTenants`, exposing exactly the by-id lookup the
14
+ * admin routes need, typed against the public `TenantConfig`. Mirrors
15
+ * `previewTenantLookup.ts`; the concrete resolver selection stays in `config`, so a
16
+ * constant single-tenant resolver remains a drop-in and the codebase stays
17
+ * always-extractable.
18
+ */
19
+ import type { TenantConfig } from "@tot/public-runtime";
20
+ import { getTenantResolver } from "@/config/resolver";
21
+ import { staticTenants } from "@/config/tenants";
22
+
23
+ /**
24
+ * Resolve the admin operator's tenant id (== appDomain for the built-in tenants) to
25
+ * its config. Strict domain match first (KV-backed in prod, static in dev), then the
26
+ * static registry by appDomain or tenant_id. Null for an unknown tenant.
27
+ */
28
+ export async function resolveAdminTenant(tenantId: string): Promise<TenantConfig | null> {
29
+ const resolver = await getTenantResolver();
30
+ return (
31
+ (await resolver.resolveStrict(tenantId)) ??
32
+ staticTenants.find((t) => t.appDomain === tenantId || t.tenant_id === tenantId) ??
33
+ null
34
+ );
35
+ }
@@ -27,14 +27,14 @@ import type { DeepPartial, TenantConfig, ThemeTokens } from "@tot/public-runtime
27
27
  /** Per-dir overrides the resolver derives from disk before synthesis. */
28
28
  export interface DevTenantHints {
29
29
  /**
30
- * (E3) The resolvable appDomain for a SLUG tenant whose dir name isn't its
30
+ * The resolvable appDomain for a SLUG tenant whose dir name isn't its
31
31
  * domain. Read from the checkout's `.tot/config.json` `scope` when that scope
32
32
  * looks like a hostname (has a dot) — mirrors scripts/tot-dev.mjs resolveDomain.
33
33
  * Undefined ⇒ the colocation convention (dir == appDomain == scope).
34
34
  */
35
35
  appDomain?: string;
36
36
  /**
37
- * (E2) True when catalog fixtures for this tenant's data scope are committed on
37
+ * True when catalog fixtures for this tenant's data scope are committed on
38
38
  * disk (the same `packages/migration/out/catalog/<scope>/` the FixtureToTClient
39
39
  * reads). When true we serve a real COMMERCE tenant so /collections + /products
40
40
  * render from them; when false we degrade to a marketing home (no catalog).
@@ -45,9 +45,9 @@ export interface DevTenantHints {
45
45
  /**
46
46
  * A servable TenantConfig for a local dev-loop checkout. Identity defaults to the
47
47
  * colocated dir name (dir == id == appDomain == tot_data_scope); a slug tenant can
48
- * override the resolvable appDomain via `hints.appDomain` (E3). No Storyblok space
48
+ * override the resolvable appDomain via `hints.appDomain`. No Storyblok space
49
49
  * (a "PLACEHOLDER" token → LocalContentProvider serves the checkout's content/).
50
- * Commerce is data-driven (E2): with catalog fixtures on disk the tenant is
50
+ * Commerce is data-driven: with catalog fixtures on disk the tenant is
51
51
  * COMMERCE (catalog on); without them it degrades to a marketing home (siteType
52
52
  * "marketing" → no catalog required, so the root renders instead of 404ing).
53
53
  * Theme tokens come from the checkout's theme.json.
@@ -57,7 +57,7 @@ export function synthesizeDevTenant(
57
57
  themeTokens: DeepPartial<ThemeTokens>,
58
58
  hints: DevTenantHints = {},
59
59
  ): TenantConfig {
60
- // (E3) Prefer the .tot/config.json scope for a slug tenant; else colocation.
60
+ // Prefer the .tot/config.json scope for a slug tenant; else colocation.
61
61
  const appDomain = hints.appDomain ?? id;
62
62
  const base: TenantConfig = {
63
63
  tenant_id: id,
@@ -74,7 +74,7 @@ export function synthesizeDevTenant(
74
74
  theme_tokens: themeTokens,
75
75
  };
76
76
  if (hints.hasCatalog) {
77
- // (E2) Catalog fixtures on disk → a real commerce tenant (siteType omitted ==
77
+ // Catalog fixtures on disk → a real commerce tenant (siteType omitted ==
78
78
  // commerce → catalog on). Cart wires to the shared ToT demo checkout store
79
79
  // (test mode, no secret), the same as the bundled demo tenants — so
80
80
  // /collections + /products resolve and add-to-cart works on localhost.
@@ -90,9 +90,9 @@ export function synthesizeDevTenant(
90
90
 
91
91
  /** Per-dir disk facts the resolver supplies so synthesis can go beyond colocation. */
92
92
  export interface DevTenantSeedOptions {
93
- /** (E3) Resolved appDomain for a slug tenant (from `.tot/config.json` scope). */
93
+ /** Resolved appDomain for a slug tenant (from `.tot/config.json` scope). */
94
94
  scopeFor?: (id: string) => string | undefined;
95
- /** (E2) Whether catalog fixtures are committed on disk for a given data scope. */
95
+ /** Whether catalog fixtures are committed on disk for a given data scope. */
96
96
  hasCatalog?: (scope: string) => boolean;
97
97
  }
98
98
 
@@ -100,7 +100,7 @@ export interface DevTenantSeedOptions {
100
100
  * The dev tenants to seed ALONGSIDE staticTenants: one synthesized config per
101
101
  * local tenant dir that isn't already a static tenant (those keep their richer
102
102
  * hardcoded config). `themeFor` supplies each dir's theme tokens; `opts` carries
103
- * the per-dir disk facts (E2 catalog presence, E3 slug scope) the resolver reads.
103
+ * the per-dir disk facts (catalog presence, slug scope) the resolver reads.
104
104
  *
105
105
  * @param localDirIds dir names discovered under `tenants/*` (dir == tenant id)
106
106
  * @param staticIds tenant_ids already in the static registry (skipped)
@@ -20,7 +20,7 @@
20
20
  * Changing that value flips which tenant a bare/unknown host serves — proof the
21
21
  * default is configurable, not hardcoded.
22
22
  */
23
- import type { TenantResolver } from "@tot/public-runtime";
23
+ import { tenantIdFromPath, type TenantResolver } from "@tot/public-runtime";
24
24
  import { readEnv, readKv } from "@/lib/env";
25
25
  import {
26
26
  tenantResolver as staticResolver,
@@ -32,30 +32,38 @@ import { getTenantTheme } from "@/themes/tenants";
32
32
  import { devTenantsToSeed } from "@/config/devTenantSeed";
33
33
  import { MemoryKv } from "@/lib/memoryKv";
34
34
 
35
- // LOCAL dev loop discovery: enumerate the colocated `tenants/<id>/` dirs on disk,
35
+ // LOCAL dev loop discovery: enumerate the colocated tenant dirs on disk,
36
36
  // INCLUDING a developer's checkout grafted in by `tot dev` (scripts/tot-dev.mjs).
37
37
  // Keys only — the loaders are never invoked. Vite resolves this glob at
38
38
  // dev-server boot (after the graft) and re-resolves on HMR, so a newly grafted
39
39
  // tenant dir is discovered. Mirrors the content/theme dir globs the app already
40
40
  // uses (provider.ts, themes/tenants/index.ts). Depth: from src/config/ the repo
41
41
  // root is four levels up (config→src→storefront→apps→root).
42
+ //
43
+ // Both tiers are globbed: real tenants at `tenants/<id>/`, platform test tenants
44
+ // one level deeper at `tenants/e2e/<id>/` (tenantDirRelative). Vite needs literal
45
+ // patterns, so the nesting is spelled out here and `tenantIdFromPath` — the one
46
+ // inverse of that mapping — recovers the id from either shape.
42
47
  const localTenantFiles = import.meta.glob([
43
48
  "../../../../tenants/*/theme.json",
44
49
  "../../../../tenants/*/content/home.html",
45
50
  "../../../../tenants/*/content/home.json",
51
+ "../../../../tenants/e2e/*/theme.json",
52
+ "../../../../tenants/e2e/*/content/home.html",
53
+ "../../../../tenants/e2e/*/content/home.json",
46
54
  ]);
47
55
 
48
- /** Dir names under `tenants/*` (dir == tenant id), from the discovery glob keys. */
56
+ /** Tenant ids with a local dir (dir == tenant id), from the discovery glob keys. */
49
57
  function localTenantDirIds(): string[] {
50
58
  const ids = new Set<string>();
51
59
  for (const key of Object.keys(localTenantFiles)) {
52
- const id = /\/tenants\/([^/]+)\//.exec(key)?.[1];
60
+ const id = tenantIdFromPath(key);
53
61
  if (id) ids.add(id);
54
62
  }
55
63
  return [...ids];
56
64
  }
57
65
 
58
- // (E2) Catalog fixtures committed on disk, keyed by data scope (== out/catalog dir
66
+ // Catalog fixtures committed on disk, keyed by data scope (== out/catalog dir
59
67
  // name). A dev tenant whose scope has fixtures is served as a real COMMERCE tenant
60
68
  // (see devTenantSeed); one without them degrades to a marketing home. Keys only —
61
69
  // the same fixtures the FixtureToTClient reads. Repo root is four up from config/.
@@ -73,14 +81,14 @@ function catalogScopes(): Set<string> {
73
81
  return scopes;
74
82
  }
75
83
 
76
- // (E3) A slug tenant (dir name != its domain) declares its resolvable appDomain in
84
+ // A slug tenant (dir name != its domain) declares its resolvable appDomain in
77
85
  // the checkout's `.tot/config.json` `scope`. Read eagerly (tiny JSON) so synthesis
78
86
  // can resolve it at `/<domain>/`; trusted only when it looks like a hostname (has a
79
87
  // dot), mirroring scripts/tot-dev.mjs resolveDomain. Only the DEV fallback consults
80
88
  // it. The explicit `.tot` literal in the pattern matches the dotdir; if the glob
81
89
  // sees nothing, synthesis simply falls back to the colocation convention.
82
90
  const tenantMarkerFiles = import.meta.glob<{ scope?: string }>(
83
- "../../../../tenants/*/.tot/config.json",
91
+ ["../../../../tenants/*/.tot/config.json", "../../../../tenants/e2e/*/.tot/config.json"],
84
92
  { eager: true, import: "default" },
85
93
  );
86
94
 
@@ -88,7 +96,7 @@ const tenantMarkerFiles = import.meta.glob<{ scope?: string }>(
88
96
  function slugScopes(): Map<string, string> {
89
97
  const map = new Map<string, string>();
90
98
  for (const [key, cfg] of Object.entries(tenantMarkerFiles)) {
91
- const id = /\/tenants\/([^/]+)\//.exec(key)?.[1];
99
+ const id = tenantIdFromPath(key);
92
100
  const scope = cfg?.scope;
93
101
  // A hostname scope that DIFFERS from the dir is the slug override; a scope
94
102
  // equal to the dir (the colocation norm) needs no override.
@@ -110,8 +118,8 @@ function slugScopes(): Map<string, string> {
110
118
  function devFallbackResolver(): TenantResolver {
111
119
  if (!import.meta.env.DEV) return staticResolver;
112
120
  const staticIds = new Set(staticTenants.map((t) => t.tenant_id));
113
- const withCatalog = catalogScopes(); // E2
114
- const scopes = slugScopes(); // E3
121
+ const withCatalog = catalogScopes();
122
+ const scopes = slugScopes();
115
123
  const dev = devTenantsToSeed(localTenantDirIds(), staticIds, getTenantTheme, {
116
124
  scopeFor: (id) => scopes.get(id),
117
125
  hasCatalog: (scope) => withCatalog.has(scope),
@@ -23,6 +23,9 @@ import { referenceTheme } from "@/themes/reference";
23
23
  import { mergeTheme } from "@/themes/schema";
24
24
  import { organizationLd, websiteLd } from "@/lib/jsonld";
25
25
  import { nonceInlineTags, CHROME_BODY_MARKER } from "@/lib/rawMarketingHtml";
26
+ import { resolveHeaderVariant, resolveFooterVariant } from "@/lib/chrome/variant";
27
+ import type { HeaderVariant, FooterVariant } from "@/lib/chrome/model";
28
+ import { renderIntegrationPhases } from "@tot/public-runtime";
26
29
  import { readViewerSession } from "@/lib/auth/route";
27
30
  import AppWidgetFrame from "@/components/apps/AppWidgetFrame.astro";
28
31
  import { getRegistryService } from "@/lib/apps/adminService.js";
@@ -40,7 +43,18 @@ interface Props {
40
43
  feeds?: { title: string; href: string }[];
41
44
  /** Wider pages (PLP/home) drop the prose max-width. */
42
45
  fullBleed?: boolean;
43
- // --- B1.6 article SEO hardening: pass-through to <Seo> (all additive) ---
46
+ /**
47
+ * Explicit per-page header/footer content-variant override (see
48
+ * lib/chrome/model.ts HeaderVariant/FooterVariant). NEVER inferred from the
49
+ * route/CSS/title — a page that wants e.g. a minimal checkout header sets
50
+ * this prop. Absent ⇒ the tenant's chrome-config default
51
+ * (chrome.header.variant / chrome.footer.variant). An unrecognized value
52
+ * (page-supplied or the config's own default) fails closed with an
53
+ * actionable error rather than silently falling back.
54
+ */
55
+ headerVariant?: HeaderVariant;
56
+ footerVariant?: FooterVariant;
57
+ // --- Article SEO hardening: pass-through to <Seo> (all additive) ---
44
58
  article?: import("@/lib/seo/meta.js").ArticleMeta;
45
59
  imageAlt?: string;
46
60
  imageWidth?: number;
@@ -48,7 +62,7 @@ interface Props {
48
62
  twitterSite?: string;
49
63
  twitterCreator?: string;
50
64
  canonicalOverride?: string;
51
- // --- I2 hreflang / <html lang> (additive) ---
65
+ // --- hreflang / <html lang> (additive) ---
52
66
  /** Per-variant `hreflang` alternates for a translated article; threaded to
53
67
  * <Seo>. Absent/empty ⇒ no alternate links (singleton unchanged). */
54
68
  alternates?: import("@/lib/seo/alternates.js").Alternate[];
@@ -75,10 +89,20 @@ const {
75
89
  canonicalOverride,
76
90
  alternates,
77
91
  lang,
92
+ headerVariant,
93
+ footerVariant,
78
94
  } = Astro.props;
79
95
  const { tenant, themeStyle, canonicalBase, basePath, cspNonce, features, capabilities } =
80
96
  Astro.locals;
81
97
 
98
+ // Fixed-phase integration engine (ext-kernel-04): ONE engine emits the
99
+ // renderer-owned phases (platform.environment, consent.defaults, head.early,
100
+ // body.start, document.deferred) into the head + body-start + body-deferred
101
+ // boundaries. `rawHtmlResponse` is the other mount of the SAME engine, so both
102
+ // documents carry byte-identical phase markup. The nonce is stamped per response
103
+ // from a placeholder by the middleware, never serialized (ADR 0018).
104
+ const integrationPhases = renderIntegrationPhases(Astro.locals.integrationPlan, cspNonce);
105
+
82
106
  // Organization JSON-LD is emitted site-wide (brief §8); pages add their own
83
107
  // graphs (Product, BreadcrumbList…) via the jsonLd prop.
84
108
  const pageGraphs = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : [];
@@ -92,6 +116,12 @@ const theme = mergeTheme(referenceTheme, tenant.theme_tokens);
92
116
 
93
117
  const chrome = await Astro.locals.content.getChrome();
94
118
 
119
+ // Explicit variant resolution (fail-closed): the page's override wins over
120
+ // the tenant's chrome-config default; an unrecognized value throws with an
121
+ // actionable message rather than silently falling back (lib/chrome/variant.ts).
122
+ const resolvedHeaderVariant = resolveHeaderVariant(headerVariant, chrome.header.variant);
123
+ const resolvedFooterVariant = resolveFooterVariant(footerVariant, chrome.footer.variant);
124
+
95
125
  // SHARED CHROME: if the tenant ships content/<id>/chrome.html (the pixel-perfect
96
126
  // header/footer the raw marketing pages use), render THAT chrome around the
97
127
  // Layout content too — so commerce pages (collections/products/search/…) share
@@ -99,7 +129,7 @@ const chrome = await Astro.locals.content.getChrome();
99
129
  // <!--PAGE_BODY-->: everything before = header, after = footer; pull its tenant
100
130
  // stylesheet links into <head>; nonce its inline tags for CSP. Tenants without a
101
131
  // chrome.html keep the token-driven Header/Footer (below).
102
- const chromeHtml = await Astro.locals.content.getChromeHtml();
132
+ const chromeHtml = await Astro.locals.content.getChromeHtml(path);
103
133
  let sharedChrome: { header: string; footer: string; css: string[] } | null = null;
104
134
  if (chromeHtml && chromeHtml.includes(CHROME_BODY_MARKER)) {
105
135
  const headEnd = chromeHtml.indexOf("</head>");
@@ -119,7 +149,7 @@ if (chromeHtml && chromeHtml.includes(CHROME_BODY_MARKER)) {
119
149
 
120
150
  const compliance = tenant.compliance;
121
151
 
122
- // Site-chrome data resolution (unit I4 — chrome/* is the canonical, prop-driven
152
+ // Site-chrome data resolution (chrome/* is the canonical, prop-driven
123
153
  // component set; this Layout is the ONE place that resolves session/feature/
124
154
  // theme state into their props. Skipped entirely when sharedChrome (a raw
125
155
  // content/<id>/chrome.html override) is present — that path wins outright.
@@ -153,25 +183,34 @@ if (features.catalog) {
153
183
  // white logo); a light header always shows the text wordmark (no logo image).
154
184
  const headerDark = theme.brand.headerVariant === "dark";
155
185
  const headerLogoSrc = headerDark ? theme.brand.logoLight : theme.brand.logoDark;
156
- const headerLogo =
186
+ const themeHeaderLogo =
157
187
  headerDark && headerLogoSrc && /^(https?:|\/)/.test(headerLogoSrc)
158
188
  ? { src: headerLogoSrc, alt: tenant.displayName }
159
189
  : undefined;
160
190
 
191
+ // chrome.header.brand (lib/chrome/model.ts ChromeBrand) is the governed,
192
+ // schema-validated brand identity — SOURCE OF TRUTH per field when set,
193
+ // falling back to the theme-derived values above only where a field is
194
+ // absent, so a tenant that hasn't authored a brand config is unaffected.
195
+ const brand = chrome.header.brand;
196
+ const brandLogoValid = brand?.logo && /^(https?:|\/)/.test(brand.logo.src) ? brand.logo : undefined;
197
+ const headerLogo = brandLogoValid ?? themeHeaderLogo;
198
+ const headerWordmark = brand?.label ?? tenant.displayName;
199
+ const headerBrandHref = brand?.href;
200
+
161
201
  // Footer bottom bar: a "Legal"-titled column's links win; otherwise every
162
202
  // column's links flatten into the policy-link row (mirrors the retired
163
203
  // nav/Footer.astro exactly).
164
204
  const legalCol = chrome.footer.columns.find((c) => /legal/i.test(c.title));
165
205
  const bottomLinks = legalCol ? legalCol.links : chrome.footer.columns.flatMap((c) => c.links);
166
- const legalLine = `© ${new Date().getFullYear()} ${tenant.displayName}${
167
- chrome.footer.legal ? ` · ${chrome.footer.legal}` : ""
168
- }`;
206
+ const legalBase = chrome.footer.bottom?.copyright ?? `© ${new Date().getFullYear()} ${tenant.displayName}`;
207
+ const legalLine = `${legalBase}${chrome.footer.bottom?.note ? ` · ${chrome.footer.bottom.note}` : ""}`;
169
208
 
170
209
  const analyticsToken = import.meta.env.PUBLIC_CF_ANALYTICS_TOKEN;
171
210
  const analyticsEnabled =
172
211
  analyticsToken && !/REPLACE|PLACEHOLDER/i.test(analyticsToken);
173
212
 
174
- // Private-app widgets (D5) — global.footer slot. Resolved once here so it
213
+ // Private-app widgets — global.footer slot. Resolved once here so it
175
214
  // renders for BOTH footer paths below (the tenant-owned chrome.html splice
176
215
  // and the generic <Footer>) instead of being owned by either. No dark-mode
177
216
  // preference infra exists yet — "light" is the only theme hint until it does.
@@ -194,6 +233,14 @@ const footerWidgets = await renderWidgetSlot(
194
233
  <head>
195
234
  <meta charset="utf-8" />
196
235
  <meta name="viewport" content="width=device-width, initial-scale=1" />
236
+
237
+ {/* Renderer-owned head phases (platform.environment → consent.defaults →
238
+ head.early), emitted before any tenant/app code — the Layout half of "one
239
+ engine, two mounts" (rawHtmlResponse is the other). Same engine output, so
240
+ both mounts are byte-identical. Carries only public values; the nonce is a
241
+ script attribute stamped per response, never serialized (ADR 0018). */}
242
+ <Fragment set:html={integrationPhases.head} />
243
+
197
244
  <link rel="icon" type="image/svg+xml" href={theme.brand.favicon} />
198
245
 
199
246
  {/* Per-tenant theme tokens injected at the edge (brief §7). The id is the
@@ -241,6 +288,10 @@ const footerWidgets = await renderWidgetSlot(
241
288
  }
242
289
  </head>
243
290
  <body>
291
+ {/* body.start fallback boundary (ext-kernel-04) — the renderer-owned mount
292
+ point for body-start contributions, before any page content. */}
293
+ <Fragment set:html={integrationPhases.bodyStart} />
294
+
244
295
  <a
245
296
  href="#main"
246
297
  class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:m-3 focus:rounded-md focus:bg-[var(--color-primary)] focus:px-4 focus:py-2 focus:text-[var(--color-primary-contrast)]"
@@ -257,16 +308,20 @@ const footerWidgets = await renderWidgetSlot(
257
308
  ) : (
258
309
  <>
259
310
  <div class="sticky top-0 z-40">
260
- {chrome.announcement && <AnnouncementBar text={chrome.announcement} variant="static" />}
311
+ {chrome.header.announcement?.messages[0] && (
312
+ <AnnouncementBar text={chrome.header.announcement.messages[0]} variant="static" />
313
+ )}
261
314
  <SiteHeader
262
315
  logo={headerLogo}
263
- wordmark={tenant.displayName}
264
- nav={chrome.nav}
316
+ wordmark={headerWordmark}
317
+ brandHref={headerBrandHref}
318
+ nav={chrome.header.nav}
265
319
  variant={headerDark ? "dark" : "light"}
266
- announcement={chrome.announcement}
320
+ layout={resolvedHeaderVariant}
321
+ announcement={chrome.header.announcement?.messages[0]}
267
322
  searchAction={features.productSearch ? "/search" : undefined}
268
323
  savedItemsHref={features.savedItems ? "/saved" : undefined}
269
- headerCtas={chrome.headerCtas}
324
+ headerCtas={chrome.header.ctas}
270
325
  authLink={authLink}
271
326
  sticky={false}
272
327
  />
@@ -287,8 +342,9 @@ const footerWidgets = await renderWidgetSlot(
287
342
  <SiteFooter
288
343
  brand={{
289
344
  wordmark: tenant.displayName,
290
- contact: chrome.footer.tagline ? [chrome.footer.tagline] : undefined,
345
+ contact: chrome.footer.brand?.text ? [chrome.footer.brand.text] : undefined,
291
346
  }}
347
+ variant={resolvedFooterVariant}
292
348
  columns={chrome.footer.columns}
293
349
  newsletter={features.newsletter ? { title: "Newsletter", placeholder: "you@example.com" } : undefined}
294
350
  social={chrome.footer.social}
@@ -298,7 +354,7 @@ const footerWidgets = await renderWidgetSlot(
298
354
  </>
299
355
  )}
300
356
 
301
- {/* Private-app widgets (D5, global.footer) — sandboxed per install; one
357
+ {/* Private-app widgets (global.footer) — sandboxed per install; one
302
358
  resolution above covers both footer paths. */}
303
359
  {footerWidgets.map((w) => (
304
360
  <div class="container-prose py-6">
@@ -426,7 +482,7 @@ const footerWidgets = await renderWidgetSlot(
426
482
  </script>
427
483
  )}
428
484
 
429
- {/* Dev-loop theme hot-patch (D1): tenant-hot-reload.mjs broadcasts a
485
+ {/* Dev-loop theme hot-patch: tenant-hot-reload.mjs broadcasts a
430
486
  `tot:theme-vars` HMR custom event on every theme.json save instead of
431
487
  forcing a full reload; swap the CSS var block in place so edits repaint
432
488
  instantly with no lost scroll position. import.meta.hot is undefined in
@@ -442,7 +498,7 @@ const footerWidgets = await renderWidgetSlot(
442
498
  </script>
443
499
  )}
444
500
 
445
- {/* Private-app widget postMessage bridge (D5) — mounted unconditionally
501
+ {/* Private-app widget postMessage bridge — mounted unconditionally
446
502
  (cheap no-op with zero tracked iframes); scans for
447
503
  `[data-tot-widget-origin]` frames itself, so it doesn't need to know
448
504
  which slots rendered above. */}
@@ -476,14 +532,38 @@ const footerWidgets = await renderWidgetSlot(
476
532
  }
477
533
  </script>
478
534
 
479
- {/* Field Web Vitals (F14 RUM) — measures LCP/INP/CLS for real visitors and
535
+ {/* Field Web Vitals (RUM) — measures LCP/INP/CLS for real visitors and
480
536
  beacons per-route samples to the RUM sink. ~1KB, no framework; no-ops
481
537
  where PerformanceObserver is absent. This is the per-route-template
482
- signal F16's `core-web-vitals` monitor consumes (the CF Web Analytics
538
+ signal the `core-web-vitals` monitor consumes (the CF Web Analytics
483
539
  beacon above only gives aggregate field CWV). */}
484
540
  <script>
485
541
  import { startWebVitals } from "@/lib/analytics/webVitals";
486
542
  startWebVitals();
487
543
  </script>
544
+
545
+ {/* Protected-surface tamper sentinel (ext-policy-09, D-R7) — a DETECTIVE
546
+ runtime control. Full-parent third-party code shares this document and its
547
+ DOM reach can't be enforceably narrowed, so this never prevents mutation: it
548
+ detects tampering of the platform-owned compliance/commerce surfaces
549
+ (policy-04 PROTECTED_SURFACES), restores safe state, evidences it, and
550
+ suspends the attributable integration. No-ops when no integration plan is
551
+ present. */}
552
+ <script>
553
+ import { bootTamperSentinel } from "@/lib/compliance/tamper-sentinel-boot";
554
+ bootTamperSentinel();
555
+ </script>
556
+
557
+ {/* document.deferred global-entry boundary (ext-kernel-04) — the last phase,
558
+ the renderer-owned mount point for the deferred integration entry. */}
559
+ <Fragment set:html={integrationPhases.bodyDeferred} />
560
+
561
+ {/* document.deferred lifecycle boot (ext-kernel-05) — initializes the plan's
562
+ integrations exactly once per document, wires the reserved completed-
563
+ navigation event, and disposes on teardown; a no-op for an empty plan. */}
564
+ <script>
565
+ import { bootIntegrationLifecycleFromDocument } from "@tot/public-runtime";
566
+ bootIntegrationLifecycleFromDocument();
567
+ </script>
488
568
  </body>
489
569
  </html>
@@ -0,0 +1,87 @@
1
+ import type { D1Like } from "@/lib/d1/catalog";
2
+
3
+ const DIAGNOSTIC_STUCK_AFTER_MS = 5 * 60 * 1000;
4
+
5
+ /**
6
+ * Atomically reserve one tenant/change across authorization, Gitea, and finalization.
7
+ * `expires_at` is an operator-visible stuck-work deadline, never an automatic takeover:
8
+ * a live but slow submit cannot lose ownership underneath its forge mutation.
9
+ */
10
+ export async function acquireCandidateSubmitLease(
11
+ db: D1Like,
12
+ tenantId: string,
13
+ changeId: string,
14
+ authorizationId: string,
15
+ now = new Date(),
16
+ ): Promise<boolean> {
17
+ const acquiredAt = now.toISOString();
18
+ const expiresAt = new Date(now.getTime() + DIAGNOSTIC_STUCK_AFTER_MS).toISOString();
19
+ const row = await db.prepare(`
20
+ INSERT INTO candidate_submit_leases
21
+ (tenant_id, change_id, authorization_id, status, acquired_at, expires_at)
22
+ VALUES (?, ?, ?, 'active', ?, ?)
23
+ ON CONFLICT(tenant_id, change_id) DO UPDATE SET
24
+ authorization_id = excluded.authorization_id,
25
+ status = 'active',
26
+ acquired_at = excluded.acquired_at,
27
+ expires_at = excluded.expires_at
28
+ WHERE candidate_submit_leases.status <> 'active'
29
+ OR candidate_submit_leases.authorization_id = excluded.authorization_id
30
+ RETURNING authorization_id
31
+ `).bind(tenantId, changeId, authorizationId, acquiredAt, expiresAt).first() as
32
+ { authorization_id?: string } | null;
33
+ return row?.authorization_id === authorizationId;
34
+ }
35
+
36
+ export type CandidateSubmitLeaseState = "active" | "completed" | "missing";
37
+
38
+ /** Atomically prove ownership for the callback work. Active leases are never stolen. */
39
+ export async function renewCandidateSubmitLease(
40
+ db: D1Like,
41
+ tenantId: string,
42
+ changeId: string,
43
+ authorizationId: string,
44
+ now = new Date(),
45
+ ): Promise<CandidateSubmitLeaseState> {
46
+ const current = await db.prepare(`
47
+ SELECT authorization_id, status FROM candidate_submit_leases
48
+ WHERE tenant_id = ? AND change_id = ?
49
+ `).bind(tenantId, changeId).first() as
50
+ { authorization_id?: string; status?: string } | null;
51
+ if (current?.authorization_id !== authorizationId) return "missing";
52
+ if (current.status === "completed") return "completed";
53
+ const expiresAt = new Date(now.getTime() + DIAGNOSTIC_STUCK_AFTER_MS).toISOString();
54
+ const row = await db.prepare(`
55
+ UPDATE candidate_submit_leases SET expires_at = ?
56
+ WHERE tenant_id = ? AND change_id = ? AND authorization_id = ? AND status = 'active'
57
+ RETURNING authorization_id
58
+ `).bind(expiresAt, tenantId, changeId, authorizationId).first() as
59
+ { authorization_id?: string } | null;
60
+ return row?.authorization_id === authorizationId ? "active" : "missing";
61
+ }
62
+
63
+ /** Mark the attempt immutable/idempotent while allowing a later submit to acquire. */
64
+ export async function completeCandidateSubmitLease(
65
+ db: D1Like,
66
+ tenantId: string,
67
+ changeId: string,
68
+ authorizationId: string,
69
+ ): Promise<void> {
70
+ await db.prepare(`
71
+ UPDATE candidate_submit_leases SET status = 'completed'
72
+ WHERE tenant_id = ? AND change_id = ? AND authorization_id = ? AND status = 'active'
73
+ `).bind(tenantId, changeId, authorizationId).run();
74
+ }
75
+
76
+ /** Release only this attempt; it can never unlock a newer submit. */
77
+ export async function releaseCandidateSubmitLease(
78
+ db: D1Like,
79
+ tenantId: string,
80
+ changeId: string,
81
+ authorizationId: string,
82
+ ): Promise<void> {
83
+ await db.prepare(`
84
+ DELETE FROM candidate_submit_leases
85
+ WHERE tenant_id = ? AND change_id = ? AND authorization_id = ? AND status = 'active'
86
+ `).bind(tenantId, changeId, authorizationId).run();
87
+ }