@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/storefront/astro.config.mjs +3 -3
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +12 -14
- package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
- package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
- package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
- package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
- package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
- package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
- package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
- package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
- package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
- package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
- package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
- package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
- package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
- package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
- package/apps/storefront/public/shared/commerce-chrome.css +20 -2
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
- package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
- package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
- package/apps/storefront/src/components/Img.astro +1 -1
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
- package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
- package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
- package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
- package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
- package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
- package/apps/storefront/src/components/membership/TierCards.astro +2 -2
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
- package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
- package/apps/storefront/src/config/devTenantSeed.ts +9 -9
- package/apps/storefront/src/config/resolver.ts +4 -4
- package/apps/storefront/src/layouts/Layout.astro +100 -20
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
- package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
- package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/apps/adminService.ts +10 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
- package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
- package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +264 -10
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
- package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/blog/collection.ts +97 -10
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +57 -3
- package/apps/storefront/src/lib/blog/types.ts +9 -9
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
- package/apps/storefront/src/lib/chrome/model.ts +48 -253
- package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
- package/apps/storefront/src/lib/chrome/variant.ts +39 -0
- package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
- package/apps/storefront/src/lib/content/index.ts +5 -5
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
- package/apps/storefront/src/lib/content-edit/route.ts +1 -1
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
- package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
- package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
- package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
- package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
- package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
- package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
- package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
- package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
- package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
- package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +18 -6
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
- package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
- package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
- package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
- package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
- package/apps/storefront/src/lib/membership/model.ts +1 -2
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
- package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
- package/apps/storefront/src/lib/newsletter/send.ts +3 -3
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
- package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
- package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
- package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
- package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
- package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
- package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/seo/alternates.ts +1 -1
- package/apps/storefront/src/lib/social/notify.ts +4 -4
- package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
- package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
- package/apps/storefront/src/lib/subscription/model.ts +4 -4
- package/apps/storefront/src/lib/the-build/provider.ts +9 -4
- package/apps/storefront/src/middleware/index.ts +273 -51
- package/apps/storefront/src/pages/[...slug].astro +9 -4
- package/apps/storefront/src/pages/admin/review-release.astro +265 -0
- package/apps/storefront/src/pages/admin/select.astro +96 -0
- package/apps/storefront/src/pages/admin.astro +16 -2
- package/apps/storefront/src/pages/api/activity.ts +8 -8
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
- package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
- package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
- package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
- package/apps/storefront/src/pages/blog/[slug].astro +5 -5
- package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
- package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
- package/apps/storefront/src/pages/blog/index.astro +2 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
- package/apps/storefront/src/pages/cockpit.astro +61 -1362
- package/apps/storefront/src/pages/index.astro +10 -5
- package/apps/storefront/src/pages/llms.txt.ts +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +7 -6
- package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
- package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
- package/apps/storefront/src/pages/the-build/index.astro +2 -2
- package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
- package/apps/storefront/src/styles/admin.css +27 -1
- package/apps/storefront/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +2 -1
- package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
- package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
- package/packages/public-runtime/src/activity/catalog.ts +41 -37
- package/packages/public-runtime/src/activity/event.ts +6 -6
- package/packages/public-runtime/src/activity/index.ts +2 -2
- package/packages/public-runtime/src/activity/redaction.ts +4 -4
- package/packages/public-runtime/src/candidate-index.ts +42 -8
- package/packages/public-runtime/src/checkout-orders.ts +2 -2
- package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
- package/packages/public-runtime/src/checkout-style.ts +7 -7
- package/packages/public-runtime/src/checkout.ts +3 -3
- package/packages/public-runtime/src/chrome.ts +777 -0
- package/packages/public-runtime/src/compliance-obligations.ts +310 -0
- package/packages/public-runtime/src/csp.ts +99 -0
- package/packages/public-runtime/src/customization-runtime.ts +32 -1
- package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
- package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
- package/packages/public-runtime/src/embed-catalog.mjs +40 -0
- package/packages/public-runtime/src/evidence.ts +288 -0
- package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
- package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
- package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
- package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
- package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
- package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
- package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
- package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
- package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
- package/packages/public-runtime/src/index.ts +16 -1
- package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
- package/packages/public-runtime/src/integration/assurance.ts +228 -0
- package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
- package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
- package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
- package/packages/public-runtime/src/integration/consent-config.ts +260 -0
- package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
- package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
- package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
- package/packages/public-runtime/src/integration/csp.ts +105 -0
- package/packages/public-runtime/src/integration/digest.ts +43 -0
- package/packages/public-runtime/src/integration/environment.ts +228 -0
- package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
- package/packages/public-runtime/src/integration/events.ts +701 -0
- package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
- package/packages/public-runtime/src/integration/index.ts +90 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
- package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
- package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
- package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
- package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
- package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
- package/packages/public-runtime/src/integration/manifest.ts +470 -0
- package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
- package/packages/public-runtime/src/integration/observability.ts +451 -0
- package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
- package/packages/public-runtime/src/integration/phases.ts +182 -0
- package/packages/public-runtime/src/integration/plan.ts +112 -0
- package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
- package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
- package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
- package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
- package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
- package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
- package/packages/public-runtime/src/integration/render-phases.ts +267 -0
- package/packages/public-runtime/src/integration/resolve.ts +465 -0
- package/packages/public-runtime/src/integration/result.ts +239 -0
- package/packages/public-runtime/src/integration/secrets.ts +92 -0
- package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
- package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
- package/packages/public-runtime/src/integration/version.ts +92 -0
- package/packages/public-runtime/src/integration-capabilities.ts +520 -0
- package/packages/public-runtime/src/membership.ts +2 -2
- package/packages/public-runtime/src/raw-html-policy.ts +2 -4
- package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
- package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
- package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
- package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
- package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
- package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
- package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
- package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
- package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
- package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
- package/packages/public-runtime/src/static-bundle.ts +5 -5
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +45 -9
- package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
- package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
- package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
- package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
- package/scripts/dev/file-browser.mjs +2 -2
- package/scripts/dev/file-writer.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/tenant-source-root.mjs +1 -1
- package/scripts/tot-dev.mjs +2 -2
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser-side integration lifecycle RUNTIME (ADR 0014 invariant 9, S1/S2;
|
|
3
|
+
* ext-kernel-05). This is the orchestrator that fulfils the {@link IntegrationLifecycleModule}
|
|
4
|
+
* contract at runtime: it initializes every integration exactly once per traditional
|
|
5
|
+
* document, delivers in-place `update`s (consent change, config change, rollback),
|
|
6
|
+
* disposes on teardown, isolates one integration's error from the others and from the
|
|
7
|
+
* page, and owns the reserved completed-navigation event — the single semantic seam a
|
|
8
|
+
* FUTURE client router must emit through, gated by the event contract before any router
|
|
9
|
+
* exists.
|
|
10
|
+
*
|
|
11
|
+
* The core is DOM-free and dependency-injected (a module loader, an event sink, a clock,
|
|
12
|
+
* an id factory, an observer), so it is edge-safe and fully testable in node; the thin
|
|
13
|
+
* browser binding that wires it to `window`/`document` lives in
|
|
14
|
+
* `./lifecycle-boot.ts` and is a guarded no-op off the browser.
|
|
15
|
+
*/
|
|
16
|
+
import type {
|
|
17
|
+
IntegrationLifecycleModule,
|
|
18
|
+
LifecycleUpdateReason,
|
|
19
|
+
IntegrationLifecycleContext,
|
|
20
|
+
IntegrationEventSubscribe,
|
|
21
|
+
} from "./lifecycle.js";
|
|
22
|
+
import { LIFECYCLE_UPDATE_REASONS } from "./lifecycle.js";
|
|
23
|
+
import type { CommerceEventType } from "./events.js";
|
|
24
|
+
import type { CommerceEventBroker } from "./commerce-broker.js";
|
|
25
|
+
import type { IntegrationBootDescriptor, BootDescriptorIntegration } from "./render-phases.js";
|
|
26
|
+
import type { NavigationCompletedEvent, NavigationKind } from "./events.js";
|
|
27
|
+
import { validateNavigationCompletedEvent } from "./events.js";
|
|
28
|
+
import { COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION, ENVIRONMENT_SCHEMA_VERSION } from "./version.js";
|
|
29
|
+
import type { BrowserEnvironment } from "./environment.js";
|
|
30
|
+
import type { ConsentSnapshot } from "./consent-broker.js";
|
|
31
|
+
import { defaultConsentSnapshot } from "./consent-broker.js";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A fail-closed {@link BrowserEnvironment} to hand `init`/`update` contexts when
|
|
35
|
+
* no environment was injected (a DOM-free test, or a document whose
|
|
36
|
+
* `__TOT_INTEGRATION_ENV__` global is absent/tampered): reuse the descriptor's
|
|
37
|
+
* own resolved stage/surface — already server-derived fail-closed (never
|
|
38
|
+
* spuriously `live`) — with an empty host/revision.
|
|
39
|
+
*/
|
|
40
|
+
export function defaultBrowserEnvironment(descriptor: IntegrationBootDescriptor): BrowserEnvironment {
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
schemaVersion: ENVIRONMENT_SCHEMA_VERSION,
|
|
43
|
+
siteSurface: descriptor.siteSurface,
|
|
44
|
+
environment: descriptor.environment,
|
|
45
|
+
canonicalHost: "",
|
|
46
|
+
revision: "",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Loads the entry module for one integration. Injected so the core stays DOM/asset free. */
|
|
51
|
+
export type IntegrationModuleLoader = (
|
|
52
|
+
integration: BootDescriptorIntegration,
|
|
53
|
+
) => Promise<IntegrationLifecycleModule>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The lifecycle stage an observation belongs to. `load` is the module-fetch step
|
|
57
|
+
* before `init`; `gate` is a consent deferral — a non-essential integration held
|
|
58
|
+
* back because its declared purposes are not yet eligible (it never loaded).
|
|
59
|
+
*/
|
|
60
|
+
export type LifecycleStage = "gate" | "load" | "init" | "update" | "dispose";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A structured, non-sensitive lifecycle signal for observability. It names WHICH
|
|
64
|
+
* integration and WHAT happened — never a payload, stack, or secret: `error` is a
|
|
65
|
+
* message string only, so an observation can never leak candidate wrappers or tokens.
|
|
66
|
+
*/
|
|
67
|
+
export interface LifecycleObservation {
|
|
68
|
+
stage: LifecycleStage;
|
|
69
|
+
integration: BootDescriptorIntegration;
|
|
70
|
+
ok: boolean;
|
|
71
|
+
reason?: LifecycleUpdateReason;
|
|
72
|
+
error?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type LifecycleObserver = (observation: LifecycleObservation) => void;
|
|
76
|
+
|
|
77
|
+
/** The completed-navigation the runtime is told about — the seam a future router calls. */
|
|
78
|
+
export interface NavigationInput {
|
|
79
|
+
from: string;
|
|
80
|
+
to: string;
|
|
81
|
+
kind: NavigationKind;
|
|
82
|
+
/** An aborted navigation never completed, so it emits no event (fail closed). */
|
|
83
|
+
aborted?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface IntegrationLifecycleOptions {
|
|
87
|
+
descriptor: IntegrationBootDescriptor;
|
|
88
|
+
loadModule: IntegrationModuleLoader;
|
|
89
|
+
/**
|
|
90
|
+
* The consent gate consulted BEFORE an integration's entry is loaded: `false`
|
|
91
|
+
* holds it back until its declared purposes become eligible, so a non-essential
|
|
92
|
+
* loader never runs under the default-deny snapshot (ext-policy-03). Absent ⇒ no
|
|
93
|
+
* gate (every integration loads) — the injection the browser binding always
|
|
94
|
+
* supplies, kept optional only so the DOM-free core stays testable in isolation.
|
|
95
|
+
* A held-back integration is (re-)evaluated on the next `update` and loads then.
|
|
96
|
+
*/
|
|
97
|
+
isEligible?: (integration: BootDescriptorIntegration) => boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Emits the reserved completed-navigation event. This is the ONLY navigation seam;
|
|
100
|
+
* the runtime hands it a CONTRACT-VALIDATED {@link NavigationCompletedEvent} and never
|
|
101
|
+
* an ad-hoc bus message, so a future router cannot ship an untyped navigation signal.
|
|
102
|
+
*/
|
|
103
|
+
emitNavigation?: (event: NavigationCompletedEvent) => void;
|
|
104
|
+
observe?: LifecycleObserver;
|
|
105
|
+
/**
|
|
106
|
+
* The renderer-owned four-stage browser environment for this document — handed
|
|
107
|
+
* to every integration's `init`/`update` {@link IntegrationLifecycleContext} verbatim.
|
|
108
|
+
* Absent ⇒ {@link defaultBrowserEnvironment} (fail closed), the injection the
|
|
109
|
+
* browser binding always supplies from `__TOT_INTEGRATION_ENV__`.
|
|
110
|
+
*/
|
|
111
|
+
environment?: BrowserEnvironment;
|
|
112
|
+
/**
|
|
113
|
+
* Reads the CURRENT consent snapshot, called fresh for every `init`/`update` so a
|
|
114
|
+
* consent change is reflected immediately in the handed {@link IntegrationLifecycleContext}.
|
|
115
|
+
* Absent ⇒ the default-deny snapshot (fail closed) — the injection the browser
|
|
116
|
+
* binding always supplies, kept optional only so the DOM-free core stays testable.
|
|
117
|
+
*/
|
|
118
|
+
getConsentSnapshot?: () => ConsentSnapshot;
|
|
119
|
+
/**
|
|
120
|
+
* The platform-owned commerce-event broker whose scoped, receive-only subscribe
|
|
121
|
+
* handle is placed on every integration's {@link IntegrationLifecycleContext} (F2
|
|
122
|
+
* wire). When present, an entry that declared at least one browser-deliverable event
|
|
123
|
+
* can subscribe to it — bounded to its declared set and consent-filtered by the
|
|
124
|
+
* broker. Absent ⇒ no `subscribe` on the context (fail closed to no delivery), the
|
|
125
|
+
* shape a DOM-free unit test uses when it wires no broker.
|
|
126
|
+
*/
|
|
127
|
+
commerceBroker?: CommerceEventBroker;
|
|
128
|
+
/** ISO-8601 timestamp source for the navigation event; defaults to wall clock. */
|
|
129
|
+
now?: () => string;
|
|
130
|
+
/** Opaque per-event id source; defaults to a monotonic, non-secret local counter. */
|
|
131
|
+
nextEventId?: () => string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The document-scoped state machine. Init is exactly-once; dispose is terminal. */
|
|
135
|
+
type LifecycleState = "idle" | "initializing" | "initialized" | "disposing" | "disposed";
|
|
136
|
+
|
|
137
|
+
/** A live, initialized integration and the module handle its update/dispose route to. */
|
|
138
|
+
interface ActiveIntegration {
|
|
139
|
+
integration: BootDescriptorIntegration;
|
|
140
|
+
module: IntegrationLifecycleModule;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function messageOf(error: unknown): string {
|
|
144
|
+
if (error instanceof Error) return error.message;
|
|
145
|
+
return typeof error === "string" ? error : "unknown error";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Reduce a navigation URL to the PUBLIC surface a completed-navigation event may carry:
|
|
150
|
+
* the pathname only (plus the fragment for a hash navigation, where the fragment IS the
|
|
151
|
+
* destination). The query string is dropped entirely — that is where preview/candidate
|
|
152
|
+
* wrappers and tokens live — and the origin is dropped as non-identifying. The result is
|
|
153
|
+
* still secret-scanned by {@link validateNavigationCompletedEvent} as defense in depth.
|
|
154
|
+
*/
|
|
155
|
+
export function sanitizeNavigationUrl(raw: string, keepHash: boolean): string {
|
|
156
|
+
let pathname = raw;
|
|
157
|
+
let hash = "";
|
|
158
|
+
try {
|
|
159
|
+
const url = new URL(raw, "https://storefront.invalid");
|
|
160
|
+
pathname = url.pathname;
|
|
161
|
+
hash = url.hash;
|
|
162
|
+
} catch {
|
|
163
|
+
// A non-URL string: strip any query/fragment tail by hand, keep the leading path.
|
|
164
|
+
const q = raw.indexOf("?");
|
|
165
|
+
const h = raw.indexOf("#");
|
|
166
|
+
const cut = [q, h].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
167
|
+
if (h >= 0) hash = raw.slice(h);
|
|
168
|
+
pathname = cut === undefined ? raw : raw.slice(0, cut);
|
|
169
|
+
}
|
|
170
|
+
return keepHash ? pathname + hash : pathname;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export class IntegrationLifecycle {
|
|
174
|
+
private state: LifecycleState = "idle";
|
|
175
|
+
private readonly active: ActiveIntegration[] = [];
|
|
176
|
+
/** Integrations held back by the consent gate; re-evaluated on each `update`. */
|
|
177
|
+
private deferred: BootDescriptorIntegration[] = [];
|
|
178
|
+
private eventCounter = 0;
|
|
179
|
+
private readonly opts: IntegrationLifecycleOptions;
|
|
180
|
+
|
|
181
|
+
constructor(options: IntegrationLifecycleOptions) {
|
|
182
|
+
this.opts = options;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** True once init has completed and dispose has not yet run. */
|
|
186
|
+
get isInitialized(): boolean {
|
|
187
|
+
return this.state === "initialized";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Initialize every integration EXACTLY ONCE per document, in the plan's resolved order.
|
|
192
|
+
* A second call — including one from a nested layout that re-runs the boot island — is a
|
|
193
|
+
* no-op, and a call after dispose is a no-op. Each integration's load+init is isolated:
|
|
194
|
+
* a thrown loader or a thrown `init()` is caught, observed, and does not stop the
|
|
195
|
+
* remaining integrations or surface to the page.
|
|
196
|
+
*/
|
|
197
|
+
async init(): Promise<void> {
|
|
198
|
+
if (this.state !== "idle") return;
|
|
199
|
+
this.state = "initializing";
|
|
200
|
+
for (const integration of this.opts.descriptor.integrations) {
|
|
201
|
+
if (!this.eligible(integration)) {
|
|
202
|
+
// Consent precedes the loader: hold it back, do not load, and record why.
|
|
203
|
+
this.deferred.push(integration);
|
|
204
|
+
this.observe({ stage: "gate", integration, ok: false });
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
await this.initOne(integration);
|
|
208
|
+
}
|
|
209
|
+
// A dispose that raced in during an await leaves us terminal; otherwise we are live.
|
|
210
|
+
if (this.state === "initializing") this.state = "initialized";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Whether the consent gate (when injected) permits loading this integration now. */
|
|
214
|
+
private eligible(integration: BootDescriptorIntegration): boolean {
|
|
215
|
+
return this.opts.isEligible ? this.opts.isEligible(integration) : true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Load any consent-deferred integration that has since become eligible, in the
|
|
220
|
+
* plan's resolved order. Called from `update` so a consent grant loads the
|
|
221
|
+
* previously-gated entry (once), rather than re-initializing anything already live.
|
|
222
|
+
*/
|
|
223
|
+
private async loadNewlyEligible(): Promise<void> {
|
|
224
|
+
if (this.deferred.length === 0) return;
|
|
225
|
+
const stillDeferred: BootDescriptorIntegration[] = [];
|
|
226
|
+
for (const integration of this.deferred) {
|
|
227
|
+
if (this.eligible(integration)) await this.initOne(integration);
|
|
228
|
+
else stillDeferred.push(integration);
|
|
229
|
+
}
|
|
230
|
+
this.deferred = stillDeferred;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async initOne(integration: BootDescriptorIntegration): Promise<void> {
|
|
234
|
+
let module: IntegrationLifecycleModule;
|
|
235
|
+
try {
|
|
236
|
+
module = await this.opts.loadModule(integration);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.observe({ stage: "load", integration, ok: false, error: messageOf(error) });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (this.state === "disposing" || this.state === "disposed") return; // torn down mid-load: do not init.
|
|
242
|
+
try {
|
|
243
|
+
await module.init(this.buildContext(integration));
|
|
244
|
+
this.active.push({ integration, module });
|
|
245
|
+
this.observe({ stage: "init", integration, ok: true });
|
|
246
|
+
} catch (error) {
|
|
247
|
+
this.observe({ stage: "init", integration, ok: false, error: messageOf(error) });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Build the typed context handed to `init`/`update`: this integration's own
|
|
253
|
+
* resolved public config (never another integration's), the document's
|
|
254
|
+
* environment, and the current consent snapshot (read fresh so `update`
|
|
255
|
+
* reflects the latest state). Frozen so no entry can mutate what the runtime
|
|
256
|
+
* or any other integration observes.
|
|
257
|
+
*/
|
|
258
|
+
private buildContext(integration: BootDescriptorIntegration): IntegrationLifecycleContext {
|
|
259
|
+
return Object.freeze({
|
|
260
|
+
appId: integration.appId,
|
|
261
|
+
tenantPublicId: this.opts.descriptor.tenantPublicId,
|
|
262
|
+
config: { ...(this.opts.descriptor.publicConfig[integration.appId] ?? {}) },
|
|
263
|
+
environment: this.opts.environment ?? defaultBrowserEnvironment(this.opts.descriptor),
|
|
264
|
+
consent: this.opts.getConsentSnapshot ? this.opts.getConsentSnapshot() : defaultConsentSnapshot(),
|
|
265
|
+
subscribe: this.buildSubscribe(integration),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Build this integration's scoped, receive-only commerce-event subscribe handle
|
|
271
|
+
* (F2 wire). Returns `undefined` — no `subscribe` on the context — when no broker is
|
|
272
|
+
* wired or the integration declared no events, so an entry that cannot legitimately
|
|
273
|
+
* receive anything is handed no handle at all (fail closed).
|
|
274
|
+
*
|
|
275
|
+
* The handle CANNOT emit and CANNOT widen scope: the runtime clamps every requested
|
|
276
|
+
* type to the intersection with the integration's validated declared set, and pins
|
|
277
|
+
* the subscription to one stable, per-entry id so a re-subscribe REPLACES rather than
|
|
278
|
+
* stacks. Consent filtering and server-owned stamping stay entirely inside the broker.
|
|
279
|
+
*/
|
|
280
|
+
private buildSubscribe(integration: BootDescriptorIntegration): IntegrationEventSubscribe | undefined {
|
|
281
|
+
const broker = this.opts.commerceBroker;
|
|
282
|
+
const declared = integration.events ?? [];
|
|
283
|
+
if (!broker || declared.length === 0) return undefined;
|
|
284
|
+
const declaredSet = new Set<CommerceEventType>(declared);
|
|
285
|
+
return (request) => {
|
|
286
|
+
const scoped: CommerceEventType[] = request.types
|
|
287
|
+
? request.types.filter((type) => declaredSet.has(type))
|
|
288
|
+
: [...declared];
|
|
289
|
+
return broker.subscribe({
|
|
290
|
+
// One stable slot per entry — a re-subscribe replaces it (broker semantics),
|
|
291
|
+
// so a vendor cannot register unbounded listeners.
|
|
292
|
+
id: `entry:${integration.appId}`,
|
|
293
|
+
types: scoped,
|
|
294
|
+
consentPurpose: request.consentPurpose,
|
|
295
|
+
sink: request.sink,
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Deliver an in-place `update(reason)` to every initialized integration that implements
|
|
302
|
+
* it. Used for a consent change, a config change, or a rollback — never a re-init. A no-op
|
|
303
|
+
* before init or after dispose. One integration's throwing `update` is isolated.
|
|
304
|
+
*/
|
|
305
|
+
async update(reason: LifecycleUpdateReason): Promise<void> {
|
|
306
|
+
if (!LIFECYCLE_UPDATE_REASONS.includes(reason)) return;
|
|
307
|
+
if (this.state !== "initialized") return;
|
|
308
|
+
// A consent/config change may make a gated integration eligible: load it now
|
|
309
|
+
// (init), then deliver the update only to integrations that were already live —
|
|
310
|
+
// a just-loaded entry gets `init`, not an immediate redundant `update`.
|
|
311
|
+
const alreadyActive = [...this.active];
|
|
312
|
+
await this.loadNewlyEligible();
|
|
313
|
+
for (const { integration, module } of alreadyActive) {
|
|
314
|
+
if (!module.update) continue;
|
|
315
|
+
try {
|
|
316
|
+
await module.update(reason, this.buildContext(integration));
|
|
317
|
+
this.observe({ stage: "update", integration, ok: true, reason });
|
|
318
|
+
} catch (error) {
|
|
319
|
+
this.observe({ stage: "update", integration, ok: false, reason, error: messageOf(error) });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Dispose every initialized integration on teardown. Idempotent — a second dispose is a
|
|
326
|
+
* no-op — and terminal: after it, `update` and a late `init` do nothing. One integration's
|
|
327
|
+
* throwing `dispose` is isolated; every other integration is still disposed.
|
|
328
|
+
*/
|
|
329
|
+
async dispose(): Promise<void> {
|
|
330
|
+
if (this.state === "disposed" || this.state === "disposing") return;
|
|
331
|
+
this.state = "disposing";
|
|
332
|
+
for (const { integration, module } of this.active) {
|
|
333
|
+
if (!module.dispose) continue;
|
|
334
|
+
try {
|
|
335
|
+
await module.dispose();
|
|
336
|
+
this.observe({ stage: "dispose", integration, ok: true });
|
|
337
|
+
} catch (error) {
|
|
338
|
+
this.observe({ stage: "dispose", integration, ok: false, error: messageOf(error) });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
this.active.length = 0;
|
|
342
|
+
this.deferred = []; // gated integrations never loaded — nothing to dispose.
|
|
343
|
+
this.state = "disposed";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Announce a completed navigation through the reserved event contract. This is the
|
|
348
|
+
* router seam: it BUILDS a {@link NavigationCompletedEvent}, VALIDATES it against the
|
|
349
|
+
* versioned contract (which strips nothing but rejects any candidate wrapper/token that
|
|
350
|
+
* slipped through), and only then emits. An aborted navigation, or one that fails
|
|
351
|
+
* validation, emits nothing (fail closed). It never re-initializes integrations — in an
|
|
352
|
+
* MPA a real document load re-inits by construction; a same-document navigation does not.
|
|
353
|
+
*/
|
|
354
|
+
notifyNavigationCompleted(nav: NavigationInput): NavigationCompletedEvent | null {
|
|
355
|
+
if (nav.aborted) return null;
|
|
356
|
+
const keepHash = nav.kind === "hash";
|
|
357
|
+
const event: NavigationCompletedEvent = {
|
|
358
|
+
specversion: "1.0",
|
|
359
|
+
id: this.newEventId(),
|
|
360
|
+
source: `//${this.opts.descriptor.tenantPublicId}`,
|
|
361
|
+
type: "storefront.navigation.completed.v1",
|
|
362
|
+
time: this.now(),
|
|
363
|
+
storefront: {
|
|
364
|
+
schemaVersion: COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION,
|
|
365
|
+
environment: this.opts.descriptor.environment,
|
|
366
|
+
siteSurface: this.opts.descriptor.siteSurface,
|
|
367
|
+
},
|
|
368
|
+
data: {
|
|
369
|
+
from: sanitizeNavigationUrl(nav.from, keepHash),
|
|
370
|
+
to: sanitizeNavigationUrl(nav.to, keepHash),
|
|
371
|
+
kind: nav.kind,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
const result = validateNavigationCompletedEvent(event);
|
|
375
|
+
if (!result.ok) return null;
|
|
376
|
+
this.opts.emitNavigation?.(result.value);
|
|
377
|
+
return result.value;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private now(): string {
|
|
381
|
+
return this.opts.now ? this.opts.now() : new Date().toISOString();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private newEventId(): string {
|
|
385
|
+
if (this.opts.nextEventId) return this.opts.nextEventId();
|
|
386
|
+
this.eventCounter += 1;
|
|
387
|
+
return `nav-${this.opts.descriptor.planRevision}-${this.eventCounter}`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private observe(observation: LifecycleObservation): void {
|
|
391
|
+
this.opts.observe?.(observation);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MPA integration lifecycle contract (ADR 0014 invariant 9, S1/S2).
|
|
3
|
+
*
|
|
4
|
+
* Existing multi-page-app behavior is the baseline: an integration entry
|
|
5
|
+
* initializes exactly once per traditional document, may be updated (e.g. on a
|
|
6
|
+
* consent change or rollback), and is disposed on teardown. The reserved
|
|
7
|
+
* completed-navigation event (`storefront.navigation.completed.v1`, defined in
|
|
8
|
+
* ./events.ts) is the versioned contract that must ship before any client
|
|
9
|
+
* router is enabled — this file names the lifecycle hooks and update reasons
|
|
10
|
+
* that engine (ext-kernel-05) implements against.
|
|
11
|
+
*/
|
|
12
|
+
import type { BrowserEnvironment } from "./environment.js";
|
|
13
|
+
import type { ConsentSnapshot } from "./consent-broker.js";
|
|
14
|
+
import type { CommerceEventType } from "./events.js";
|
|
15
|
+
import type { CommerceEventSink } from "./commerce-broker.js";
|
|
16
|
+
import type { ConsentPurpose } from "../integration-capabilities.js";
|
|
17
|
+
|
|
18
|
+
/** The three lifecycle hooks an integration entry module fulfills. */
|
|
19
|
+
export const LIFECYCLE_HOOKS = ["init", "update", "dispose"] as const;
|
|
20
|
+
export type LifecycleHook = (typeof LIFECYCLE_HOOKS)[number];
|
|
21
|
+
|
|
22
|
+
/** Why an already-initialized integration is updated in place rather than re-initialized. */
|
|
23
|
+
export const LIFECYCLE_UPDATE_REASONS = [
|
|
24
|
+
"consent-changed",
|
|
25
|
+
"config-changed",
|
|
26
|
+
"rollback",
|
|
27
|
+
] as const;
|
|
28
|
+
export type LifecycleUpdateReason = (typeof LIFECYCLE_UPDATE_REASONS)[number];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What an entry hands its scoped subscribe handle: the delivery sink, an optional
|
|
32
|
+
* narrowing to a SUBSET of the events its manifest declared, and the consent purpose
|
|
33
|
+
* its destination depends on. The handle is RECEIVE-ONLY — an entry gets no `emit`
|
|
34
|
+
* power — and the runtime clamps `types` to the integration's validated declared set,
|
|
35
|
+
* so a vendor can neither widen its subscription nor author an event.
|
|
36
|
+
*/
|
|
37
|
+
export interface IntegrationEventSubscribeRequest {
|
|
38
|
+
/** Where the delivered, contract-validated events go (in-page callback or a sandboxed iframe). */
|
|
39
|
+
sink: CommerceEventSink;
|
|
40
|
+
/** Narrow to a subset of this integration's declared events; omitted ⇒ every declared event. A type outside the declared set is dropped, never widened to. */
|
|
41
|
+
types?: readonly CommerceEventType[];
|
|
42
|
+
/** The consent purpose the destination requires; the broker withholds delivery until it is eligible (default-deny). */
|
|
43
|
+
consentPurpose?: ConsentPurpose;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The scoped commerce-event subscribe handle on the lifecycle context (design-partner
|
|
48
|
+
* finding F2 wire): register a receive-only subscription bounded to this integration's
|
|
49
|
+
* declared events, returning an unsubscribe. A re-subscribe REPLACES the prior
|
|
50
|
+
* registration (broker semantics), so an entry cannot stack unbounded listeners.
|
|
51
|
+
* Absent from the context when the host wired no broker (e.g. a DOM-free unit test).
|
|
52
|
+
*/
|
|
53
|
+
export type IntegrationEventSubscribe = (
|
|
54
|
+
request: IntegrationEventSubscribeRequest,
|
|
55
|
+
) => () => void;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The typed, frozen context `init`/`update` receive — sourced from the SAME
|
|
59
|
+
* renderer-published globals (`__TOT_INTEGRATION_PLAN__`, `__TOT_INTEGRATION_ENV__`,
|
|
60
|
+
* `__TOT_INTEGRATION_CONSENT__`) an entry previously had to reach into by hand, so
|
|
61
|
+
* an integration never string-matches a `window` global. Carries public values
|
|
62
|
+
* only — no candidate wrapper, token, nonce, or secret — consistent with the plan
|
|
63
|
+
* and event contracts.
|
|
64
|
+
*/
|
|
65
|
+
export interface IntegrationLifecycleContext {
|
|
66
|
+
/** This integration's own id, matching its manifest/plan `appId`. */
|
|
67
|
+
appId: string;
|
|
68
|
+
/** Server-resolved tenant identity from the validated boot descriptor. */
|
|
69
|
+
tenantPublicId: string;
|
|
70
|
+
/** This integration's own resolved public config (the plan's `publicConfig[appId]`). */
|
|
71
|
+
config: Record<string, unknown>;
|
|
72
|
+
/** The renderer-owned four-stage browser environment for this document. */
|
|
73
|
+
environment: BrowserEnvironment;
|
|
74
|
+
/** The current consent snapshot; read fresh for every `init`/`update` call. */
|
|
75
|
+
consent: ConsentSnapshot;
|
|
76
|
+
/**
|
|
77
|
+
* A receive-only, declared-scoped commerce-event subscribe handle (F2 wire). Present
|
|
78
|
+
* when the host wired a broker AND this integration declared at least one event; a
|
|
79
|
+
* vendor subscribes to its post-purchase (or other declared) signal through this,
|
|
80
|
+
* instead of reaching for raw emit power. Absent otherwise (fail closed to no delivery).
|
|
81
|
+
*/
|
|
82
|
+
subscribe?: IntegrationEventSubscribe;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The shape of the module an entry asset exports. Init runs once per document;
|
|
87
|
+
* update/dispose are optional. Handlers receive only browser-safe context — no
|
|
88
|
+
* candidate wrapper or token — consistent with the plan and event contracts.
|
|
89
|
+
*/
|
|
90
|
+
export interface IntegrationLifecycleModule {
|
|
91
|
+
init(ctx: IntegrationLifecycleContext): void | Promise<void>;
|
|
92
|
+
update?(reason: LifecycleUpdateReason, ctx: IntegrationLifecycleContext): void | Promise<void>;
|
|
93
|
+
dispose?(): void | Promise<void>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared value-canary sources for the managed analytics runtime and JSON Schema.
|
|
3
|
+
* Plain JavaScript lets the TypeScript runtime and the Node schema generator
|
|
4
|
+
* consume one flag-free ECMAScript pattern without mirroring definitions.
|
|
5
|
+
*/
|
|
6
|
+
export const MANAGED_ANALYTICS_SECRET_VALUE_PATTERN =
|
|
7
|
+
"(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)";
|
|
8
|
+
|
|
9
|
+
export const MANAGED_ANALYTICS_PII_VALUE_PATTERN =
|
|
10
|
+
"(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))";
|