@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
@@ -31,10 +31,11 @@
31
31
  * reference files that don't exist. The Worker-plane build never sets this, so
32
32
  * R2 shadowing semantics are unchanged there.
33
33
  */
34
- import { cp, mkdir, readdir, rm } from "node:fs/promises";
35
- import { existsSync } from "node:fs";
34
+ import { cp, mkdir, rm } from "node:fs/promises";
35
+ import { existsSync, readdirSync, statSync } from "node:fs";
36
36
  import { join, resolve, dirname } from "node:path";
37
37
  import { fileURLToPath } from "node:url";
38
+ import { listTenantDirIds, tenantDirSegments } from "../lib/tenant-dirs.mjs";
38
39
 
39
40
  const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
40
41
  const src = resolve(repoRoot, "tenants");
@@ -53,15 +54,21 @@ const versionedEnv = new Set(
53
54
  .filter(Boolean),
54
55
  );
55
56
  const bakeVersioned = process.env.TENANT_ASSETS_BAKE_VERSIONED === "1";
57
+ const tenantDir = (id) => join(repoRoot, ...tenantDirSegments(id));
56
58
  const isVersioned = (id) =>
57
59
  !bakeVersioned &&
58
60
  (versionedEnv.has(id) ||
59
- existsSync(join(src, id, ".tot", "assets-versioned")));
61
+ existsSync(join(tenantDir(id), ".tot", "assets-versioned")));
62
+
63
+ // Both tiers: real tenants directly under tenants/, platform test tenants one
64
+ // level deeper under tenants/e2e/. The destination stays FLAT — the served URL
65
+ // is `/tenants/<id>/…` for every tenant, whichever tier its source sits in.
66
+ const ids = listTenantDirIds(src, readdirSync, (p) => statSync(p).isDirectory(), join);
60
67
 
61
68
  let copied = 0;
62
69
  let skipped = 0;
63
- for (const id of await readdir(src)) {
64
- const pub = join(src, id, "public");
70
+ for (const id of ids) {
71
+ const pub = join(tenantDir(id), "public");
65
72
  if (!existsSync(pub)) continue;
66
73
  // Versioned tenants are served from R2 — baking would shadow the R2 route.
67
74
  if (isVersioned(id)) {
@@ -24,12 +24,13 @@ import { join, dirname } from "node:path";
24
24
  import { createMcpClient } from "../../packages/cli/src/mcp.mjs";
25
25
  import { resolveDeveloperSession, AuthUnavailableError } from "../../packages/cli/src/auth.mjs";
26
26
  import { applyUnifiedDiff } from "./unified-diff.mjs";
27
+ import { tenantDirSegments } from "../lib/tenant-dirs.mjs";
27
28
 
28
29
  const DEFAULT_MCP_URL = "https://mcp.tokenoftrust.com";
29
30
 
30
31
  /** Where a tenant's editable files + AI-edit history live (monorepo layout). */
31
32
  export function tenantPaths(repoRoot, tenantId) {
32
- const dir = join(repoRoot, "tenants", tenantId);
33
+ const dir = join(repoRoot, ...tenantDirSegments(tenantId));
33
34
  return {
34
35
  dir,
35
36
  home: join(dir, "content", "home.html"),
@@ -109,8 +109,8 @@ export function readFile(sourceRoot, relPath) {
109
109
  }
110
110
 
111
111
  const norm = relPath.split(sep).join("/");
112
- // The viewer needs the on-disk version (C6 baseVersion) + whether the file is in
113
- // the write allowlist (C5 editable vs read-only) — surfaced on every ok read.
112
+ // The viewer needs the on-disk version (baseVersion) + whether the file is in
113
+ // the write allowlist (editable vs read-only) — surfaced on every ok read.
114
114
  const mtimeMs = st.mtimeMs;
115
115
  const writable = matchesWriteAllowlist(norm);
116
116
  if (BINARY_EXT.has(extname(norm).toLowerCase())) {
@@ -15,7 +15,7 @@
15
15
  * lands on disk (and C1's Monitor timestamps the save→reload) — this module never
16
16
  * sends its own reload.
17
17
  *
18
- * Stale-write guard (C6): writeSourceFile/handleWrite accept an optional
18
+ * Stale-write guard: writeSourceFile/handleWrite accept an optional
19
19
  * baseVersion — the mtimeMs the client captured when it opened or last saved the
20
20
  * file. If the file on disk has since changed (someone edited it via $EDITOR, git,
21
21
  * or the AI ask box while autosave was armed), the write is refused with
@@ -74,7 +74,7 @@ export function writeSourceFile(sourceRoot, relPath, content, opts = {}) {
74
74
  } catch {
75
75
  /* leaf doesn't exist yet — a new file, which is fine */
76
76
  }
77
- // Stale-write guard (C6): if the client supplied the version it last saw and the
77
+ // Stale-write guard: if the client supplied the version it last saw and the
78
78
  // file has since changed on disk (or now exists when it expected none), refuse
79
79
  // rather than clobber. Omit baseVersion to force-save.
80
80
  const baseVersion = opts && opts.baseVersion;
@@ -0,0 +1,498 @@
1
+ /**
2
+ * integration-guardrails.mjs — make the epic's integration discipline routine and
3
+ * VISIBLE before a unit branch is folded into the umbrella. One command answers
4
+ * the four questions the lead integrator otherwise asks (and hand-writes a note
5
+ * about) for every unit:
6
+ *
7
+ * 1. Ownership — does this branch only touch files inside its declared area,
8
+ * or did it wander into files another in-flight unit owns?
9
+ * 2. Base drift — has the umbrella moved far enough ahead of this branch's
10
+ * merge-base that a refresh/rebase is due before folding?
11
+ * 3. Sibling — do any files this branch touches ALSO appear on a concurrent
12
+ * overlap issue/ fix/ wip/ impl/ branch (a hot-file collision, e.g.
13
+ * the admin Publish module the whole epic keeps colliding on)?
14
+ * 4. Focused — do the scoped lint/typecheck/test commands for the touched
15
+ * checks files actually PASS (not just "did you run something")?
16
+ *
17
+ * It then prints a structured **integrator handoff note** (files touched grouped
18
+ * by area, drift status, overlaps, check results) that the lead can paste straight
19
+ * into an integration commit/PR instead of composing by hand.
20
+ *
21
+ * Design mirrors scripts/preview/*-doctor.sh: findings are ordered so the FIRST
22
+ * ✗ (blocker) or ⚠ (warning) is the thing to deal with, each carries its remedy,
23
+ * and the exit code is non-zero when a blocker (a failed focused check) is present
24
+ * so it is safe to drop into a pre-fold gate.
25
+ *
26
+ * The git/exec-driven parts live in `main()`; everything checkable — glob→area
27
+ * matching, drift classification, sibling-overlap join, check derivation, note
28
+ * formatting — is a pure exported function with no I/O so it unit-tests under
29
+ * `node --test` (the scripts/dev convention), with no build step and no deps
30
+ * beyond node: builtins + the `git` CLI.
31
+ *
32
+ * Ownership convention: a branch's area is a list of path globs, declared either
33
+ * • inline: --area 'scripts/dev/**' --area 'docs/dev/**' (repeatable), or
34
+ * • in a manifest `.tot-ownership.json` at the repo root, keyed by branch name:
35
+ * { "areas": { "issue/integration-conflict-guardrails": ["scripts/dev/**"] } }
36
+ * With no area declared the ownership check is SKIPPED (reported as such) — it
37
+ * never fabricates a verdict it can't back.
38
+ *
39
+ * Usage:
40
+ * node scripts/dev/integration-guardrails.mjs [--umbrella <ref>] [--area <glob>]...
41
+ * [--manifest <path>] [--no-siblings] [--no-checks] [--json]
42
+ * # defaults: --umbrella feature/8500-storefront-integration, manifest .tot-ownership.json
43
+ */
44
+ import { execFileSync } from "node:child_process";
45
+
46
+ /** Umbrella branch this epic folds into. */
47
+ export const DEFAULT_UMBRELLA = "feature/8500-storefront-integration";
48
+
49
+ /** Branch-name prefixes that denote a concurrent, in-flight unit. */
50
+ export const UNIT_BRANCH_PREFIXES = ["issue/", "fix/", "wip/", "impl/"];
51
+
52
+ /**
53
+ * Compile a path glob to a RegExp. Supports `**` (any chars incl. `/`), `*`
54
+ * (any chars except `/`), and a trailing `/` as shorthand for a directory prefix
55
+ * (`scripts/dev/` ≡ `scripts/dev/**`). All other regex metacharacters are escaped.
56
+ * @param {string} glob
57
+ * @returns {RegExp}
58
+ */
59
+ export function globToRegExp(glob) {
60
+ let g = String(glob).replace(/^\.\//, "");
61
+ if (g.endsWith("/")) g += "**";
62
+ let re = "";
63
+ for (let i = 0; i < g.length; i++) {
64
+ const c = g[i];
65
+ if (c === "*") {
66
+ if (g[i + 1] === "*") {
67
+ re += ".*";
68
+ i++;
69
+ } else {
70
+ re += "[^/]*";
71
+ }
72
+ } else if ("\\^$.|?+()[]{}".includes(c)) {
73
+ re += "\\" + c;
74
+ } else {
75
+ re += c;
76
+ }
77
+ }
78
+ return new RegExp("^" + re + "$");
79
+ }
80
+
81
+ /**
82
+ * True iff `file` (a posix repo-relative path) matches any of `globs`.
83
+ * @param {string} file
84
+ * @param {string[]} globs
85
+ * @returns {boolean}
86
+ */
87
+ export function matchesAny(file, globs) {
88
+ const norm = String(file).replace(/^\.\//, "");
89
+ return (globs || []).some((g) => globToRegExp(g).test(norm));
90
+ }
91
+
92
+ /**
93
+ * Partition touched files into those inside the declared ownership area and those
94
+ * outside it (cross-area edits that warrant integrator review).
95
+ * @param {string[]} files
96
+ * @param {string[]} areaGlobs
97
+ * @returns {{ declared: boolean, inArea: string[], crossArea: string[] }}
98
+ */
99
+ export function classifyOwnership(files, areaGlobs) {
100
+ const declared = Array.isArray(areaGlobs) && areaGlobs.length > 0;
101
+ if (!declared) return { declared: false, inArea: [...(files || [])], crossArea: [] };
102
+ const inArea = [];
103
+ const crossArea = [];
104
+ for (const f of files || []) {
105
+ if (matchesAny(f, areaGlobs)) inArea.push(f);
106
+ else crossArea.push(f);
107
+ }
108
+ return { declared: true, inArea, crossArea };
109
+ }
110
+
111
+ /**
112
+ * Classify how stale a branch's base is from the count of umbrella commits landed
113
+ * since the branch's merge-base. `warn` recommends a refresh; `stale` strongly
114
+ * recommends one before folding.
115
+ * @param {number} behind umbrella commits ahead of the merge-base
116
+ * @param {{ warnAt?: number, staleAt?: number }} [opts]
117
+ * @returns {{ level: "fresh"|"warn"|"stale", behind: number, recommend: string }}
118
+ */
119
+ export function classifyBaseDrift(behind, opts = {}) {
120
+ const warnAt = opts.warnAt ?? 10;
121
+ const staleAt = opts.staleAt ?? 30;
122
+ const n = Number.isFinite(behind) ? behind : 0;
123
+ if (n >= staleAt) {
124
+ return {
125
+ level: "stale",
126
+ behind: n,
127
+ recommend: `umbrella is ${n} commits ahead of this branch's base — refresh before folding (git merge ${DEFAULT_UMBRELLA}, or rebase) so conflicts surface here, not in the integrator's fold`,
128
+ };
129
+ }
130
+ if (n >= warnAt) {
131
+ return {
132
+ level: "warn",
133
+ behind: n,
134
+ recommend: `umbrella is ${n} commits ahead — a refresh (git merge ${DEFAULT_UMBRELLA}) is recommended before folding`,
135
+ };
136
+ }
137
+ return { level: "fresh", behind: n, recommend: "base is fresh — no refresh needed" };
138
+ }
139
+
140
+ /**
141
+ * Join this branch's touched files against each sibling branch's touched files,
142
+ * returning the files that appear on at least one sibling (hot-file collisions).
143
+ * @param {string[]} ourFiles
144
+ * @param {Record<string, string[]>} siblingFilesByBranch
145
+ * @returns {Array<{ file: string, branches: string[] }>}
146
+ */
147
+ export function computeSiblingOverlaps(ourFiles, siblingFilesByBranch) {
148
+ const ours = new Set(ourFiles || []);
149
+ /** @type {Map<string, string[]>} */
150
+ const hits = new Map();
151
+ for (const [branch, files] of Object.entries(siblingFilesByBranch || {})) {
152
+ for (const f of files || []) {
153
+ if (!ours.has(f)) continue;
154
+ const list = hits.get(f) || [];
155
+ if (!list.includes(branch)) list.push(branch);
156
+ hits.set(f, list);
157
+ }
158
+ }
159
+ return [...hits.entries()]
160
+ .map(([file, branches]) => ({ file, branches: branches.sort() }))
161
+ .sort((a, b) => a.file.localeCompare(b.file));
162
+ }
163
+
164
+ /**
165
+ * Derive the focused check commands implied by a set of touched files — scoped to
166
+ * what changed rather than the whole `pnpm run check`. Pure/deterministic so the
167
+ * mapping is unit-tested; the runner in `main()` executes each returned command.
168
+ *
169
+ * • touched `scripts/**` (or storefront dev-plugins/integrations) `*.test.mjs`,
170
+ * plus the `.test.mjs` sibling of any touched non-test `.mjs`, → one `node --test`
171
+ * over exactly those files.
172
+ * • touched `scripts/**` `.mjs`/`.js` → `typecheck:scripts` (checkJs over scripts/).
173
+ * • touched `apps/storefront/src/**` `.ts`/`.astro`/`.tsx` → the storefront package typecheck.
174
+ *
175
+ * @param {string[]} files
176
+ * @param {(p: string) => boolean} [existsFn] whether a repo-relative path exists (for the .test.mjs sibling probe); defaults to always-true
177
+ * @returns {Array<{ name: string, command: string, args: string[] }>}
178
+ */
179
+ export function deriveChecks(files, existsFn) {
180
+ const has = typeof existsFn === "function" ? existsFn : () => true;
181
+ const list = files || [];
182
+ const isScript = (f) => /^scripts\//.test(f) || /^apps\/storefront\/(dev-plugins|integrations)\//.test(f);
183
+
184
+ /** @type {Set<string>} */
185
+ const testFiles = new Set();
186
+ for (const f of list) {
187
+ if (!isScript(f)) continue;
188
+ if (f.endsWith(".test.mjs")) {
189
+ testFiles.add(f);
190
+ } else if (f.endsWith(".mjs") || f.endsWith(".js")) {
191
+ const sibling = f.replace(/\.(mjs|js)$/, ".test.mjs");
192
+ if (has(sibling)) testFiles.add(sibling);
193
+ }
194
+ }
195
+
196
+ /** @type {Array<{ name: string, command: string, args: string[] }>} */
197
+ const checks = [];
198
+ if (testFiles.size > 0) {
199
+ checks.push({ name: "dev-script tests", command: "node", args: ["--test", ...[...testFiles].sort()] });
200
+ }
201
+ if (list.some((f) => /^scripts\/.*\.(mjs|js)$/.test(f))) {
202
+ checks.push({ name: "scripts typecheck", command: "pnpm", args: ["run", "typecheck:scripts"] });
203
+ }
204
+ if (list.some((f) => /^apps\/storefront\/src\/.*\.(ts|tsx|astro)$/.test(f))) {
205
+ checks.push({ name: "storefront typecheck", command: "pnpm", args: ["--filter", "@tot/storefront", "typecheck"] });
206
+ }
207
+ return checks;
208
+ }
209
+
210
+ /**
211
+ * @typedef {Object} GuardrailResult
212
+ * @property {string} branch
213
+ * @property {string} umbrella
214
+ * @property {string} mergeBase
215
+ * @property {string[]} files
216
+ * @property {{ level: "fresh"|"warn"|"stale", behind: number, recommend: string }} drift
217
+ * @property {{ declared: boolean, inArea: string[], crossArea: string[] }} ownership
218
+ * @property {Array<{ file: string, branches: string[] }>} overlaps
219
+ * @property {Array<{ name: string, ok: boolean, skipped?: boolean, detail?: string }>} checks
220
+ */
221
+
222
+ /**
223
+ * Render the integrator handoff note (markdown) from a result. This is the note
224
+ * the lead otherwise writes by hand when folding a unit.
225
+ * @param {GuardrailResult} r
226
+ * @returns {string}
227
+ */
228
+ export function formatHandoffNote(r) {
229
+ const lines = [];
230
+ lines.push(`### Integration handoff — ${r.branch}`);
231
+ lines.push("");
232
+ lines.push(`- **Umbrella:** ${r.umbrella} (merge-base ${short(r.mergeBase)})`);
233
+ lines.push(`- **Base drift:** ${r.drift.level} (${r.drift.behind} commit(s) behind umbrella)`);
234
+
235
+ const own = r.ownership;
236
+ if (!own.declared) {
237
+ lines.push(`- **Ownership:** not declared — ${r.files.length} file(s) touched, area unverified`);
238
+ } else {
239
+ lines.push(
240
+ `- **Ownership:** ${own.inArea.length} in-area, ${own.crossArea.length} cross-area` +
241
+ (own.crossArea.length ? ` ⚠ (${own.crossArea.join(", ")})` : ""),
242
+ );
243
+ }
244
+
245
+ if (r.overlaps.length) {
246
+ lines.push(`- **Hot-file overlap:** ${r.overlaps.length} file(s) also on sibling branches ⚠`);
247
+ for (const o of r.overlaps) lines.push(` - \`${o.file}\` ↔ ${o.branches.join(", ")}`);
248
+ } else {
249
+ lines.push(`- **Hot-file overlap:** none`);
250
+ }
251
+
252
+ if (r.checks.length) {
253
+ const parts = r.checks.map((c) => `${c.skipped ? "skipped" : c.ok ? "✓" : "✗"} ${c.name}`);
254
+ lines.push(`- **Focused checks:** ${parts.join(", ")}`);
255
+ } else {
256
+ lines.push(`- **Focused checks:** none derived (no checkable files touched)`);
257
+ }
258
+
259
+ lines.push("");
260
+ lines.push(`**Files touched (${r.files.length}):**`);
261
+ for (const f of r.files) {
262
+ const tag = own.declared && own.crossArea.includes(f) ? " ⚠ cross-area" : "";
263
+ lines.push(`- \`${f}\`${tag}`);
264
+ }
265
+ return lines.join("\n");
266
+ }
267
+
268
+ /** @param {string} sha */
269
+ function short(sha) {
270
+ return String(sha || "").slice(0, 9);
271
+ }
272
+
273
+ // ─── git-driven CLI (not covered by unit tests — the pure logic above is) ─────
274
+
275
+ /** Run git in the repo; returns trimmed stdout, or "" on failure. @param {string[]} args */
276
+ function git(args) {
277
+ try {
278
+ return execFileSync("git", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }).trim();
279
+ } catch {
280
+ return "";
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Resolve the umbrella ref to fold against. A worktree's LOCAL umbrella branch is
286
+ * routinely behind what was last fetched, so prefer `origin/<ref>` when it exists
287
+ * and is not behind the local ref. If `ref` already names a remote/explicit rev,
288
+ * it is used as-is.
289
+ * @param {string} ref
290
+ * @returns {string}
291
+ */
292
+ function resolveUmbrella(ref) {
293
+ if (ref.includes("/") && git(["rev-parse", "--verify", "--quiet", ref])) {
294
+ // already a remote-tracking or fully-qualified ref the caller chose
295
+ if (ref.startsWith("origin/") || ref.startsWith("refs/")) return ref;
296
+ }
297
+ const remote = `origin/${ref}`;
298
+ const remoteSha = git(["rev-parse", "--verify", "--quiet", remote]);
299
+ const localSha = git(["rev-parse", "--verify", "--quiet", ref]);
300
+ if (!remoteSha) return ref; // no remote-tracking ref — use whatever we were given
301
+ if (!localSha) return remote;
302
+ // Use the remote unless the local ref is strictly ahead of it (local has commits
303
+ // origin lacks — e.g. an integrator working locally ahead of a push).
304
+ const localAhead = Number(git(["rev-list", "--count", `${remote}..${localSha}`]) || "0");
305
+ return localAhead > 0 ? ref : remote;
306
+ }
307
+
308
+ /**
309
+ * Union of the files this unit changes: commits since the umbrella merge-base,
310
+ * plus working-tree changes vs HEAD (staged + unstaged), plus untracked files.
311
+ * Uses prefix-free plumbing (`diff --name-only`, `ls-files --others`) rather than
312
+ * `status --porcelain` so there is no status-column slicing to get wrong.
313
+ * @param {string} base
314
+ * @returns {string[]}
315
+ */
316
+ function touchedFiles(base) {
317
+ const set = new Set();
318
+ const add = (out) => {
319
+ for (const l of out.split("\n")) if (l.trim()) set.add(l.trim());
320
+ };
321
+ if (base) add(git(["diff", "--name-only", `${base}..HEAD`]));
322
+ add(git(["diff", "--name-only", "HEAD"])); // staged + unstaged vs HEAD
323
+ add(git(["ls-files", "--others", "--exclude-standard"])); // untracked
324
+ return [...set].sort();
325
+ }
326
+
327
+ /** @param {string} branch @param {string} umbrella */
328
+ function branchTouchedFiles(branch, umbrella) {
329
+ const base = git(["merge-base", branch, umbrella]);
330
+ if (!base) return [];
331
+ const out = git(["diff", "--name-only", `${base}..${branch}`]);
332
+ return out ? out.split("\n").filter(Boolean) : [];
333
+ }
334
+
335
+ /** @param {string[]} argv */
336
+ function parseArgs(argv) {
337
+ const opts = {
338
+ umbrella: DEFAULT_UMBRELLA,
339
+ areas: /** @type {string[]} */ ([]),
340
+ manifest: ".tot-ownership.json",
341
+ siblings: true,
342
+ checks: true,
343
+ json: false,
344
+ };
345
+ for (let i = 0; i < argv.length; i++) {
346
+ const a = argv[i];
347
+ if (a === "--umbrella") opts.umbrella = argv[++i];
348
+ else if (a === "--area") opts.areas.push(argv[++i]);
349
+ else if (a === "--manifest") opts.manifest = argv[++i];
350
+ else if (a === "--no-siblings") opts.siblings = false;
351
+ else if (a === "--no-checks") opts.checks = false;
352
+ else if (a === "--json") opts.json = true;
353
+ else if (a === "-h" || a === "--help") opts.help = true;
354
+ else throw new Error(`unknown argument: ${a}`);
355
+ }
356
+ return opts;
357
+ }
358
+
359
+ async function main() {
360
+ const { readFileSync, existsSync } = await import("node:fs");
361
+ let opts;
362
+ try {
363
+ opts = parseArgs(process.argv.slice(2));
364
+ } catch (e) {
365
+ process.stderr.write(`${e.message}\n`);
366
+ process.exit(2);
367
+ return;
368
+ }
369
+ if (opts.help) {
370
+ process.stdout.write(
371
+ "usage: node scripts/dev/integration-guardrails.mjs [--umbrella <ref>] [--area <glob>]... " +
372
+ "[--manifest <path>] [--no-siblings] [--no-checks] [--json]\n",
373
+ );
374
+ return;
375
+ }
376
+
377
+ const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]) || "HEAD";
378
+ // Fold against the SHARED umbrella tip, not a possibly-stale local ref: prefer
379
+ // origin/<umbrella> when it exists AND is at least as advanced as the local ref
380
+ // (a local umbrella branch in a worktree is routinely behind what was fetched).
381
+ const umbrella = resolveUmbrella(opts.umbrella);
382
+ const mergeBase = git(["merge-base", "HEAD", umbrella]);
383
+ if (!mergeBase) {
384
+ process.stderr.write(`✗ no merge-base between HEAD and ${umbrella} — is the umbrella fetched?\n`);
385
+ process.exit(1);
386
+ return;
387
+ }
388
+ const behind = Number(git(["rev-list", "--count", `${mergeBase}..${umbrella}`]) || "0");
389
+ const files = touchedFiles(mergeBase);
390
+
391
+ // Ownership area: explicit --area wins; else manifest keyed by branch name.
392
+ let areas = opts.areas;
393
+ if (areas.length === 0 && existsSync(opts.manifest)) {
394
+ try {
395
+ const manifest = JSON.parse(readFileSync(opts.manifest, "utf8"));
396
+ const declared = manifest && manifest.areas && manifest.areas[branch];
397
+ if (Array.isArray(declared)) areas = declared;
398
+ } catch {
399
+ /* a malformed manifest just means "no declared area" */
400
+ }
401
+ }
402
+
403
+ const drift = classifyBaseDrift(behind);
404
+ const ownership = classifyOwnership(files, areas);
405
+
406
+ /** @type {Record<string, string[]>} */
407
+ const siblingFiles = {};
408
+ if (opts.siblings) {
409
+ const raw = git(["for-each-ref", "--format=%(refname:short)", "refs/heads"]);
410
+ for (const b of raw.split("\n").filter(Boolean)) {
411
+ if (b === branch) continue;
412
+ if (!UNIT_BRANCH_PREFIXES.some((p) => b.startsWith(p))) continue;
413
+ const bf = branchTouchedFiles(b, umbrella);
414
+ if (bf.length) siblingFiles[b] = bf;
415
+ }
416
+ }
417
+ const overlaps = computeSiblingOverlaps(files, siblingFiles);
418
+
419
+ /** @type {Array<{ name: string, ok: boolean, skipped?: boolean, detail?: string }>} */
420
+ const checkResults = [];
421
+ const derived = deriveChecks(files, (p) => existsSync(p));
422
+ if (!opts.checks) {
423
+ for (const c of derived) checkResults.push({ name: c.name, ok: true, skipped: true });
424
+ } else {
425
+ for (const c of derived) {
426
+ try {
427
+ execFileSync(c.command, c.args, { stdio: "pipe", encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
428
+ checkResults.push({ name: c.name, ok: true });
429
+ } catch (e) {
430
+ const tail = String((e && (e.stdout || "")) + (e && (e.stderr || ""))).trim().split("\n").slice(-6).join("\n");
431
+ checkResults.push({ name: c.name, ok: false, detail: tail });
432
+ }
433
+ }
434
+ }
435
+
436
+ /** @type {GuardrailResult} */
437
+ const result = { branch, umbrella, mergeBase, files, drift, ownership, overlaps, checks: checkResults };
438
+
439
+ if (opts.json) {
440
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
441
+ } else {
442
+ renderReport(result);
443
+ process.stdout.write("\n" + formatHandoffNote(result) + "\n");
444
+ }
445
+
446
+ const blocker = checkResults.some((c) => !c.ok && !c.skipped);
447
+ process.exit(blocker ? 1 : 0);
448
+ }
449
+
450
+ /** @param {GuardrailResult} r */
451
+ function renderReport(r) {
452
+ const ok = (s) => process.stdout.write(` ✓ ${s}\n`);
453
+ const bad = (s) => process.stdout.write(` ✗ ${s}\n`);
454
+ const warn = (s) => process.stdout.write(` ⚠ ${s}\n`);
455
+ const note = (s) => process.stdout.write(` → ${s}\n`);
456
+
457
+ process.stdout.write(`\n== integration guardrails: ${r.branch} → ${r.umbrella} ==\n`);
458
+
459
+ // 1. base drift
460
+ if (r.drift.level === "fresh") ok(`base fresh (${r.drift.behind} behind umbrella)`);
461
+ else warn(`base drift: ${r.drift.behind} behind umbrella`), note(r.drift.recommend);
462
+
463
+ // 2. ownership
464
+ if (!r.ownership.declared) {
465
+ warn(`ownership area not declared for ${r.branch}`);
466
+ note("declare it in .tot-ownership.json (areas[<branch>]) or pass --area <glob> to enable the check");
467
+ } else if (r.ownership.crossArea.length === 0) {
468
+ ok(`ownership clean — all ${r.ownership.inArea.length} touched file(s) inside declared area`);
469
+ } else {
470
+ warn(`${r.ownership.crossArea.length} cross-area edit(s) outside declared area`);
471
+ for (const f of r.ownership.crossArea) note(f);
472
+ note("confirm these are intended, or move them to their owner's branch before folding");
473
+ }
474
+
475
+ // 3. sibling overlap
476
+ if (r.overlaps.length === 0) ok("no hot-file overlap with sibling unit branches");
477
+ else {
478
+ warn(`${r.overlaps.length} file(s) also touched by concurrent unit branch(es)`);
479
+ for (const o of r.overlaps) note(`${o.file} ↔ ${o.branches.join(", ")}`);
480
+ note("coordinate fold order with the integrator; expect a conflict on these — refresh base first");
481
+ }
482
+
483
+ // 4. focused checks
484
+ if (r.checks.length === 0) ok("no focused checks derived (no checkable files touched)");
485
+ for (const c of r.checks) {
486
+ if (c.skipped) note(`check skipped (--no-checks): ${c.name}`);
487
+ else if (c.ok) ok(`focused check passed: ${c.name}`);
488
+ else {
489
+ bad(`focused check FAILED: ${c.name}`);
490
+ if (c.detail) for (const l of c.detail.split("\n")) note(l);
491
+ }
492
+ }
493
+ }
494
+
495
+ // Run only when invoked directly (not when imported by the test).
496
+ if (import.meta.url === `file://${process.argv[1]}`) {
497
+ main();
498
+ }
@@ -90,8 +90,8 @@ export function isSafePath(root, relPath) {
90
90
  }
91
91
 
92
92
  /**
93
- * Strict WRITE allowlist for C5's `POST /__tot/write` — only the raw-HTML
94
- * content surface is writable. Read/tree endpoints (C3) do NOT use this; they
93
+ * Strict WRITE allowlist for the `POST /__tot/write` endpoint — only the raw-HTML
94
+ * content surface is writable. Read/tree endpoints do NOT use this; they
95
95
  * rely on `resolveSafePath` containment alone so the tree can show everything.
96
96
  * Kept here so the write path reuses the one guard rather than reinventing it.
97
97
  */
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * CRITICAL (standalone write-through): in `--workspace` mode `tot dev` COPIES the
6
6
  * developer's checkout into an in-tree graft and re-syncs on save. The file tree,
7
- * viewer, diff and (C5) writer must operate on the developer's REAL CHECKOUT —
7
+ * viewer, diff and writer must operate on the developer's REAL CHECKOUT —
8
8
  * NOT the in-tree graft — or edits made in the browser are silently discarded on
9
9
  * the next resync. `tot dev` therefore sets TOT_DEV_SOURCE_ROOT to the checkout
10
10
  * in standalone mode and to `tenants/<id>/` in monorepo mode; this module is the
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { existsSync } from "node:fs";
21
21
  import { resolve } from "node:path";
22
+ import { tenantDirSegments } from "../lib/tenant-dirs.mjs";
22
23
 
23
24
  /**
24
25
  * @returns {{ok:true, tenantId:string, sourceRoot:string, gitCwd:string}
@@ -72,5 +73,5 @@ export function resolveTenantSourceRoot(env = process.env) {
72
73
  export function resolveDevSourceRoot(workspacePath, repoRoot, tenant) {
73
74
  return workspacePath
74
75
  ? resolve(workspacePath)
75
- : resolve(repoRoot, "tenants", tenant);
76
+ : resolve(repoRoot, ...tenantDirSegments(tenant));
76
77
  }
@@ -62,6 +62,7 @@ import {
62
62
  import { dirname, resolve, join } from "node:path";
63
63
  import { fileURLToPath } from "node:url";
64
64
  import { resolveDevSourceRoot } from "./dev/tenant-source-root.mjs";
65
+ import { tenantDirRelative, tenantDirSegments } from "./lib/tenant-dirs.mjs";
65
66
  import { firstFreePort } from "./dev/port-check.mjs";
66
67
  import { resync, startWatch } from "./dev/checkout-watch.mjs";
67
68
 
@@ -244,7 +245,7 @@ if (workspacePath) {
244
245
  // structurally (ignoring any legacy `repo` field in the checkout's .tot/config.json).
245
246
  const wsRel = String(map.workspace ?? "").replace(/\/+$/, "");
246
247
  if (!wsRel) continue;
247
- const repoRel = join("tenants", tenant, wsRel);
248
+ const repoRel = join(tenantDirRelative(tenant), wsRel);
248
249
 
249
250
  const source = resolve(wsRoot, wsRel); // real files in the checkout
250
251
  const target = resolve(repoRoot, repoRel); // where the app globs them
@@ -288,7 +289,7 @@ if (workspacePath) {
288
289
  cleanupGrafts();
289
290
  process.exit(1);
290
291
  }
291
- } else if (!existsSync(resolve(repoRoot, "tenants", tenant, "content"))) {
292
+ } else if (!existsSync(resolve(repoRoot, ...tenantDirSegments(tenant), "content"))) {
292
293
  console.warn(`⚠ no tenants/${tenant}/content/ found — the tenant may not be registered yet.`);
293
294
  }
294
295
 
@@ -342,11 +343,11 @@ console.log(` Private local preview — nothing is published. Server binds all
342
343
  // tells Astro "you are already the managed child → run foreground" (see astro's
343
344
  // cli/dev/{index,background}.js), so our SIGINT/exit lifecycle and symlink cleanup
344
345
  // stay correct. Monorepo mode is left untouched (current behavior).
345
- // TOT_DEV_TENANT tells the inline-AI edit loop (WS5 G1 — astro.config.mjs's
346
+ // TOT_DEV_TENANT tells the inline-AI edit loop (astro.config.mjs's
346
347
  // inlineAiEditPlugin, scripts/dev/ai-edit.mjs) which tenant's content/theme to
347
348
  // read+write: one `tot dev <tenant>` run always serves exactly one tenant, so
348
349
  // there's no per-request domain lookup to do.
349
- // TOT_DEV_SOURCE_ROOT tells the /dev file tools (C3 tree/viewer, C4 diff, C5
350
+ // TOT_DEV_SOURCE_ROOT tells the /dev file tools (tree/viewer, diff,
350
351
  // writer — scripts/dev/tenant-source-root.mjs) WHERE the developer's editable
351
352
  // source lives. STANDALONE: the developer's REAL checkout (wsRoot) — the tools
352
353
  // MUST read+write it, NOT the in-tree graft, or browser edits are silently lost